BCTF2017 writeup: babyuse
今週末はBCTFに参加してました。一人で参加してpwn一問だけ解けたので記録しておきます。
問題概要
配布されたzipファイルにはバイナリbabyuseとlibc.soがもらえます。
$ unzip -l e1b84982-14dc-45f3-a41b-fb80b4805bd1.zip Archive: e1b84982-14dc-45f3-a41b-fb80b4805bd1.zip Length Date Time Name -------- ---- ---- ---- 0 04-10-17 13:37 babyuse/ 13712 04-10-17 13:32 babyuse/babyuse 1786484 04-10-17 13:34 babyuse/libc.so -------- ------- 1800196 3 files
バイナリを実行すると、AAのロゴ(Passion leads Amy?)が表示され、銃を買ったり売ったりできるみたいです。
$ ./babyuse _ |_)_. _ _o _ ._ | _ _. _| _ /\ ._ _ | (_|_>_>|(_)| | |_(/_(_|(_|_> /--\| | |\/ / Menu: 1. Buy a Gun 2. Select a Gun 3. List Guns 4. Rename a Gun 5. Use a Gun 6. Drop a Gun 7. Exit
解析
まず基本的な情報から確認します。バイナリは32-bit ELFでFull RELRO, Canary, NX, PIE全マシです。すごく厳しいです。
$ file babyuse/babyuse babyuse/babyuse: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, stripped $ checksec.sh/checksec -f ./babyuse RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 0 2 ./babyuse
PIEのバイナリはいままで読んだことなかったので解析に色々と苦労しました。
(callとかのアドレスはロード時に書き換えられるとか、ebxがgotセクションへのポインタになっているとか、初めて知りました。)
参考になったページ
Technovelty - PLT and GOT - the key to code sharing and dynamic libraries
ELF実行ファイルのメモリ配置はどのように決まるのか - ももいろテクノロジー
radare2を使って雰囲気でデコンパイルします。(ついでにalarmの時間を延ばすようバイナリを変更しました。)
ポイントは
- 次のグローバル変数が存在する。
- booleanの配列guns
- gunの構造体のポインタを持っておくgun_data,
- 現在選択した銃のインデックスselected_gun
- いくつかのアクションが実行できる
- Buy(type, n, str) : for( i = 0, i <= 3 && guns[i], i++ ); guns[i] := true; gun_data[i] = new Gun(type); gun_data[i]->name = new String(str)
- Select(i): selected_gun := i (if guns[i] == true)
- List(): show the list of current guns
- Rename(i, str): if( guns[i] ) { gun_data[i]->name = new String(str); free(old_str); }
- Use(): printf("...%s...", gun_data[selected_gun]->name)...
- Drop(i): if(guns[i]) { free(gun_data[i]->name); free(gun_data[i]) }
- gun構造体はmallocによってheapに置かれること。
です。入出力等でバッファオーバーランなどの脆弱性は見つかりませんでした。
脆弱性
とりあえず、セグフォらせる入力を探します。銃を持っていない状態で5. Use a Gunを選択したらどうなるでしょうか。
$ ./babyuse _ |_)_. _ _o _ ._ | _ _. _| _ /\ ._ _ | (_|_>_>|(_)| | |_(/_(_|(_|_> /--\| | |\/ / Menu: 1. Buy a Gun 2. Select a Gun 3. List Guns 4. Rename a Gun 5. Use a Gun 6. Drop a Gun 7. Exit 5 Segmentation fault (core dumped)
セグフォりました。Use a gunのコードを見てみます。
use_a_gun() { struct gun *gun = gun_data[selected_gun]; printf("Select gun %s", gun->name); puts("1. Shoot"); puts("2. Reload"); puts("3. Info"); puts("4. Main menu"); char buf[0x20]; while(true){ read_until(stdin, buf, 0x20, '\n'); int n = atoi(buf); switch(n) { case 1: gun->vtable->op_shoot(gun); break; case 2: gun->vtable->op_reload(gun); break; case 3: gun->vtable->op_info(gun); break; case 4: return; default: puts("Wrong input"); } } }
gun_data[selected_gun]の初期値はNULLですからprintf("Select gun %s", gun->name);のところでセグフォったのだと推測できます。
本来ならばguns[selected_gun]がtrueであることを確かめなければならないはずです。
また、選択した銃を捨てた場合、その銃の構造体の領域はfreeされますが、gun_data[selected_gun]はそのままなのでfreeされた領域が参照できることがわかります。
gun->nameもmallocで管理された領域なので、文字列がfreeされた後にgun構造体として再利用されれば、その構造体のデータをleakできそうと言うことがわかります。
exploit
heapのアドレスleak
色々実験した結果、次のような入力でheapのアドレスがleakできることがわかりました。
$ cat exploit_1.txt 1 1 30 hogeeeeeeeeeeeeeeeee 1 1 15 01234 2 1 6 0 6 1 5 4 $ ./babyuse < exploit_1.txt | grep -a 'Select gun' | hexdump -C 00000000 53 65 6c 65 63 74 20 67 75 6e 20 08 3a 01 57 34 |Select gun .:.W4| 00000010 0a |.|
リトルエンディアンなので0x57013a08がheap上のアドレスです。
どうしてこの入力でleakできるのかはコンテスト時はわからなかったのですが、libcのfreeの実装を調べたところ、
freeされた領域にはlinked listになっていて、一つ前にfreeされた領域へのポインタが書き込まれるためのようです。
malloc(3)のメモリ管理構造 | VA Linux Systems Japan株式会社
executableアドレスのleak
今回のバイナリはPIEなので実行時にバイナリがどこに配置されているかがわかりません。なのでアドレスをleakさせます。
New(0,"hoge") -> New(0,"hoge") -> Select(1) -> Drop(1) -> Rename(0,data) とすると、Dropした銃の構造体があったheap領域に任意のdataを書き込むことが出来ます。
その領域に対してUse()を実行すると、gun_data[selected_gun]->nameが表示出来ます。
今、heapのアドレスはすでにleakされたので一番最初のNew()で作られたgun構造体のアドレスAが計算できます。
そこで、struct gun { vtable = DUMMY, name = A, ... }のようなデータを書き込むと、validなgun構造体のデータを表示させることができます。
したがって、t.vtableのアドレスとして実行バイナリのアドレスがleakできます。
libcアドレスのleak
libcのアドレスは実行バイナリの.plt.gotセクションにあるはずなので適当なアドレスを上と同じ方法でleakさせます。
つまりstruct gun { vtable = DUMMY, name = free@plt, ... }とします。
shellの起動
最後にshellを起動させます。Use()の後でShootを選択すると、gun_data[selected_gun]->vtable->op_shoot(gun_data[selected_gun])が実行できることを用います。
まず、{ op_shoot = system@libc, op_reload = ANY, op_info = ANY }となるデータをheap上に配置します。そのアドレスをAとします。
つぎに、{ vtable = A, name = A, max_bullets = "||sh", bullets = 0x0}となるデータをgun_data[selected_gun]のアドレスに再利用させます。
nameをAとしているのは、セグフォを防ぐためです。
するとsystem(&{ vtable = A, name = A, max_bullets = "||sh", bullets = 0x0})が実行できます。つまりsystem("~~~~||sh")のようなことになります。||の前の部分は不正な文字列ですが、コマンド名が見つからないだけなので特に困りません。
exploit
gistbbc00350a0919027da07c8846de3515c
$ python3 exploit.py .... RECV: b'/\n' RECV: b'babyuse\n' RECV: b'bin\n' RECV: b'dev\n' RECV: b'flag\n' RECV: b'lib\n' RECV: b'lib32\n' RECV: b'lib64\n' RECV: b'wrapper\n' RECV: b'bctf{ec1c977319050b85e3a9b50d177a7746}\n'