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に置かれること。

です。入出力等でバッファオーバーランなどの脆弱性は見つかりませんでした。


gist537e607ab1fb1c8e0ad745b85f662487

脆弱性

とりあえず、セグフォらせる入力を探します。銃を持っていない状態で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できます。

f:id:autotaker:20170417182118p:plain

libcアドレスのleak

libcのアドレスは実行バイナリの.plt.gotセクションにあるはずなので適当なアドレスを上と同じ方法でleakさせます。
つまりstruct gun { vtable = DUMMY, name = free@plt, ... }とします。

f:id:autotaker:20170417182349p:plain

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")のようなことになります。||の前の部分は不正な文字列ですが、コマンド名が見つからないだけなので特に困りません。
f:id:autotaker:20170417185604p:plain

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'