Pintool Denial of Service
As described in an earlier blogposts, Pintool is a
Dynamic Binary Instrumentation framework. In this blogpost we analyze a
simple Denial of Service approach which is tested for Pintool, but should also
work with other Dynamic Binary Instrumentation frameworks.
Internal Workings of Pin
When instrumenting a binary using Pin, Pin will translate every executed
basic block into their own version. That is, the translated block of
instructions will do the same as before, but the new basic
block will also execute additional code. For example, if the original basic
block calls another function, Pin will check if it should translate the new
basic block, or if it has already been translated (and if so, it will execute
the existing translation, rather than translating the basic block again and
executing it from there.)
Abusing Pin Internals
As just-in-time compilation is a fairly expensive operation (rather
than executing a set of instructions, such as a basic block, Pin has to
disassemble the instructions, check if any adjustments should be made,
translate them into the new block, and execute the translated block), we can
get Pin to perform expensive operations in order to trigger a Denial of
That being said, the expensive part of executing a new basic block under Pin,
is the translation. In other words, if we want to exhaust Pin, we will want to
have Pin translate many basic blocks. We do this by making sure that Pin
executes as many new basic blocks as possible.
Denial of Service Idea
In order to have Pin translate as many basic blocks as possible, without
continously altering memory of basic blocks in the process, we execute a
sequence of nop instructions in a backwards fashion. So imagine we have a
nopsled of length ten (that is, ten consecutive nop instructions), now if we
execute the last nop instruction, and then jump back to the second last nop
instruction, Pin will have to translate the new basic block. At this point
of time, there will be two basic blocks, the one starting at the last nop
instruction, and the one starting at the second last nop instruction.
Continuing like this we can have Pin translate N “unique” basic blocks from
a nopsled containing N nop instructions.
Proof of Concept
In the Proof of Concept we show two similar ways to mess around with
Pin. The first by creating a nopsled, as described above, with N = 0×10000
(in other words, there will be 65536 nop instructions, resulting in the
execution of 2147516416 nop instructions in total.) This takes Pin give or
take a 100mb of ram and a few minutes to finish.
The second way is by using N = 0×1000000 (or, 16777216 nop instruction),
and not by stepping one nop instruction back every iteration (because that
would result into a lot of nop instructions), but 0×10000. So if the first
iteration would execute the last nop instruction, then the second iteration
would execute the last 0×10001 nop instructions (0×10000 + 1, the last.) In
theory this almost equals in the amount of nop instructions that will be
executed compared to the first way, namely 2155872256. In practice, however,
Pin will have to translate 0×10000 * i nop instructions for every iteration.
I didn’t even wait for the second method to finish when running under Pin, but
after ten minutes or so, it’s using over one gigabyte of memory, and it will
probably run out of memory or so..
Note that this behavior is by design, and that it is probably rather hard if
not impossible (in my opinion), to solve. Besides that, this behavior is seen
when using a plain pintool. It might be possible, however, to fix
this in a specific pintool, but I’ll leave this as an exercise to the reader.
When running poc.exe under normal circumstances, i.e., not under Pin, we get
something like the following output:
$ poc.exe nop-instructions: 65536 executing nops: 2147516416 that took 0.296000 milliseconds nop-instructions: 16777216 executing nops: 2155872256 that took 0.406000 milliseconds
Running the poc.exe under Pin, we get something like the following:
$ ../pintool/ia32/bin/pin.exe -t pindos-x32.dll -- poc.exe nop-instructions: 65536 executing nops: 2147516416 that took 40.186000 milliseconds nop-instructions: 16777216 executing nops: 2155872256 ...
It is obviously taking a little extra time under Pin.. Other than that
this is not really that interesting.. So, enjoy your day doing something
Btw, the source of a bare Pintool and the source of the Proof of Concept can
be found in the following repository: pindos.