Check out the new USENIX Web site. next up previous
Next: Conclusions Up: Engineering Heap Overflow Exploits Previous: Trigger Jump to Shellcode

Case Study

Our research into reliable JavaScript heap exploitation was motivated by a vulnerability we found in WebKit's PCRE (Perl-Compatible Regular Expression) parsing. It was an integer overflow that allowed for an arbitrary overflow size past a buffer holding a compiled regular expression that could be allocated to be any size up to 65535 bytes. However, the overflow occurred very soon after the buffer was allocated, such that we often ran up against unallocated memory during the overflow. In other instances, we overwrote important data, but what data was there changed between runs and seemed totally unpredictable. The technique described in Section 2 solved these problems for us and allowed for reliable exploitation.

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 = 0x5ac000
By 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 = 0x17169000
After 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 = 0x17168000
So 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 +0x52
which 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).


next up previous
Next: Conclusions Up: Engineering Heap Overflow Exploits Previous: Trigger Jump to Shellcode
jake 2008-07-14