2014年5月19日月曜日

DEF CON 22 CTF Qualifiers heap Writeup

CTF初心者ですが、DEFCON 22 CTF 予選会に参戦しました。
解けた問題のWriteupを書きたいと思います。

問題名: heap
問題ファイル:babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c


とりあえず、問題ファイルをwgetしてfileしてみる。

# file babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c
babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c: ELF 32-bit LSB executable, Intel 80386,
 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24,
BuildID[sha1]=0x00884e1b18ca134c90ac78efe2c198b2e5d4c147, not stripped
--------------------------

32bit ELFっぽい。

実行してみる



--------------------------
# ./babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c

Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
[ALLOC][loc=804D008][size=1246]
[ALLOC][loc=804D4F0][size=1121]
[ALLOC][loc=804D958][size=947]
[ALLOC][loc=804DD10][size=741]
[ALLOC][loc=804E000][size=706]
[ALLOC][loc=804E2C8][size=819]
[ALLOC][loc=804E600][size=673]
[ALLOC][loc=804E8A8][size=1004]
[ALLOC][loc=804EC98][size=952]
[ALLOC][loc=804F058][size=755]
[ALLOC][loc=804F350][size=260]
[ALLOC][loc=804F458][size=877]
[ALLOC][loc=804F7D0][size=1245]
[ALLOC][loc=804FCB8][size=1047]
[ALLOC][loc=80500D8][size=1152]
[ALLOC][loc=8050560][size=1047]
[ALLOC][loc=8050980][size=1059]
[ALLOC][loc=8050DA8][size=906]
[ALLOC][loc=8051138][size=879]
[ALLOC][loc=80514B0][size=823]
Write to object [size=260]:
susono             ← ユーザ(俺)の入力
Copied 7 bytes.
[FREE][address=804D008]
[FREE][address=804D4F0]
[FREE][address=804D958]
[FREE][address=804DD10]
[FREE][address=804E000]
[FREE][address=804E2C8]
[FREE][address=804E600]
[FREE][address=804E8A8]
[FREE][address=804EC98]
[FREE][address=804F058]
[FREE][address=804F350]
[FREE][address=804F458]
[FREE][address=804F7D0]
[FREE][address=804FCB8]
[FREE][address=80500D8]
[FREE][address=8050560]
[FREE][address=8050980]
[FREE][address=8050DA8]
[FREE][address=8051138]
[FREE][address=80514B0]
Did you forget to read the flag with your shellcode?
Exiting


なんかいっぱいだらだら出てきた。

1行目、親切にもこんな表記があります。
 
 Welcome to your first heap overflow...

ヒープオーバーフローの脆弱性が有るよって言ってますね。
今回のDEFCONは初心者にやさしい問題です。

「スタックバッファオーバーフロー攻撃」や「フォーマットストリング攻撃」は
何度か攻略したことは有ります。

でも、ヒープ系はめんどくさいイメージが有りっていうか、
どうやって攻撃が成り立つのかかわからなかったので今まで避けてきました。

というわけで、今回初挑戦の問題です。
楽しく学ぶ、初めてのヒープオーバーフロー


攻略の方針:ヒープ領域にシェルコードを置いて、どうにかしてそのコードを実行してやる。

スタック領域のオーバーフローと同じ感じのやり方で攻めていこうと。


初心者らしくIDA Freeを使って逆アセしてみました。

まず、知りたいこととしては、入力データがどういうふうに扱われているか と言う部分。

値が入力されているところを探す。
 

プログラム内のどこで、どこの領域に、どのようにコピーされるのかを知る。
 とりあえず、__IO_getcの実体を呼び出す所でxref機能を使って呼び出し元を特定する。
 
xrefs機能

位置特定。


なんか、1文字ずつ読み取ってる雰囲気が伝わってきます。
この関数はどうやら、get_my_line関数から呼ばれているらしいです。
さらに、get_my_lineはmain関数から呼ばれているらしいです。


しかも、入力の後に、memcpyで入力された文字数分をどこかにコピーしているようです。
コピー先のバッファは、mallocで確保したメモリの11番目です。

(19回mallocでメモリを確保している内の11個目の領域。)


mallocしてる所。


対して、mallocしたらfreeしないといけないので、一応freeの方も。

freeしてる所。

それとほぼ同時に関数ポインタのようなもので関数をコールしてます。


もしかして、この関数ポインタを書き換えるのかな?
これを、ヒープのシェルコードの先頭の番地にすればいいんじゃないか?
 後に、この方法がダメなことを知る。

この関数ポインタはどこで初期化されているのかですが・・・main関数内で初期化されてました。
グローバルなポインタ変数に関数のアドレスを入れてます。




そしてこれが、その対象の関数。

すべての処理が終了したら、メッセージを表示して終わるらしいです。


とりあえず、ヒープオーバーフローというからには、
メモリがオーバーフローするということなのでオーバーフローさせてみましょう。
何バイト書き込めばいいのかと言うと、このプログラムは特別なことをしなくても良いように
画面に表示してくれてます。
 
 Write to object [size=260]

260バイト以上書き込むとオーバーフローするっぽいです。
初心者用の問題バンザイ。

# python -c 'print "A"* 260 
AAAA・・・AAAAAAAAAAA・・・・AAAAAAA・・・・A

実行結果をコピーして貼り付けて実行してみると、Segmentation Faultで落ちました。
どこで落ちるかというと、このメモリをFreeする処理の中です。

ということは、バッファオーバーフローで、管理データを変えてしまったために
Freeの中で何らかのアクセス例外が起こっていると推測できます。

というわけで、原因を探っていきます。

gdbでデバッグすると、クラッシュする番地が出ます
0x80493f6
ここの命令で、メモリにアクセスしています。

具体的な命令は
mov     eax, [ebp+var_28]
mov     edx, [ebp+var_24]
mov     [eax+8], edx     <-----0x80493f6の命令(ここで例外発生)

実際にどこにアクセスしているのかを確認するために、info registerでレジスタを確認。
eax            0x41414141 0x41414141
ecx            0x804d004 0x804d004
edx            0x41414141 0x41414141
ebx            0xf7fbeff4 0xf7fbeff4
esp            0xffffc130 0xffffc130
ebp            0xffffc168 0xffffc168
esi            0x0 0x0
edi            0x0 0x0
(略)

ということは、
0x41414141+8 (=0x41414149) 番地 に 0x41414141 を入れる
と言う動作をしているため、Memory access violationで落ちるわけです。

長くなるので、説明を省きますが、この 0x80493f6 の命令の時は
eaxには 0x804F350 に有る値が入り
edxには 0x804F354 にある値が入ります。

つまり、
mov [eax+8] , edx
この命令では、仮に 0x1234番地に0xdeadbeefという値を入れたかったら

0x0000122C 0xdeadbeef という並びを作ってあげれば良いのです。

しかし、この次の命令で、順番が入れ替わるので

mov     eax, [ebp+var_24]
mov     edx, [ebp+var_28]
mov     [eax+4], edx

0xdeadbef3 (=0xdeadbeef+4) 番地に0x1234を入れる と言う命令になり、
0xdeadbef3番地の中身も書き換えられます
(この一連の命令で、双方向リストの繋ぎ変えを行っているっぽい(?)です。)

というわけで、どちらも書き換え可能なメモリでないといけません。
どちらかが、.textだったり.rdataだったらこの時点で落ちます。


一応仕組みがわかったところで、結論を。

関数ポインタの書き換えでの呼び出しは無理でした。
printfのgotをシェルコードの先頭番地に書き換えて実行すると言う方法に変更。

理由はというと、11回目のメモリの開放の瞬間、ヒープが壊れます。
次の12回目のメモリの開放で壊れたヒープの管理データを使うため
読み書き禁止のデータ領域にぶつかります。(状況によっては違うかもしれません。不定。)

その結果例外が発生して、シェルコードが実行されずに止まってしまいます。
とても残念です。

と言うわけで呼び出し方を変更して、freeした後に、更にprintfする所があるので
そのgotのアドレスを書き換えてやることで、呼び出す仕組みを作ります。


では、シェルコード。

シェルを開いてその中で、とりあえず " ls / " コマンドを発行する攻撃コードを書く。
---応答ここから---
bin
boot
dev
etc
home
initrd.img
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
vmlinuz

---応答ここまで---

なんか、攻撃が成功してボロボロっと出てきました。
ディレクトリ一覧を取得できたので、次は/homeディレクトリの一覧を取得してみたいと思います。
Exploitの 36行目を
に変更して、実行します。

---応答ここから---
/home:
  babyfirst-heap
  ubuntu

/home/babyfirst-heap:
  babyfirst-heap
  flag

---応答ここまで---

/home/babyfirst-heapディレクトリに、flagと言うフラグっぽい名前のファイルがあるので
次も、Exploitの35行目を に変更して実行します。

---応答ここから---
The flag is: Good job on that doubly linked list. Why don't you try something harder!!OMG!!
---応答ここまで---

Flag: Good job on that doubly linked list. Why don't you try something harder!!OMG!!


というわけで、攻略完了です。
なかなか面白い問題で、楽しくヒープバッファオーバーフローが学べる良問でした。

 コンテスト自体の感想:今年はバイナリしか出なかったので、例年よりも気が散らずに集中して取り組めたと思います。

以上!