FrontPage > Armadillo-9 > exploit gv
gv へのバッファオーバーフロー攻撃†
- CPU: ARM9
- OS: Debian Sarge(2.4.31-a9-2)
- アットマークテクノのページで配布されているものを使った
- target: gv-3.5.8
- Debian のはパッチが当たってるので独自ビルド
- 最適化を無効にするために config.Unix に
CDEBUGFLAGS = -fno-strict-aliasing -g --static
を追加した(--static はスタティックリンクのとき必要)
- 備考: sh からやると、スタックが低すぎてだめかも。bash でやってくれ。
- スタティックリンクについて
- gcc -o gv -fno-strict-aliasing -g --static -fsigned-char -L/usr/X11R6/lib Aaa.o Aaa_bison.o Aaa_lex.o Button.o Clip.o FileSel.o Frame.o Ghostview.o MButton.o Switch.o Vlist.o actions.o callbacks.o confirm.o dialog.o doc_misc.o error.o file.o info.o magmenu.o main.o media.o misc.o miscmenu.o misc_private.o note.o options.o options_fs.o options_gs.o options_gv.o options_setup.o process.o popup.o ps.o resource.o save.o scale.o signal.o version.o widgets_misc.o zoom.o Scrollbar.o -lm -lXaw3d -lXmu -lXt -lSM -lICE -lXpm -lXext -lX11
- ↑がエラーを起こすので、-lm の前に -lpthread をつけると進む
実行結果†
攻撃対象、条件†
攻撃する箇所†
- ps.c にある psscan 関数の sscanf にバッファオーバーフロー脆弱性がある。
- 脆弱性のある箇所は ps.c の 590行目あたりである。
- %%PageOrder という命令を解釈する sscanf に用いられる固定長バッファ(text という 257 要素の char 型配列)をオーバーフローさせる攻撃である。
攻撃のための条件†
- glibc(もしくは ld.so か?) に stack randomization の機能がないことが前提である。
- スタック領域が固定された位置に配置される必要がある。/proc/[pid]/maps で、
bffff000-c0000000 rwxp 00000000 00:00 0
のようにならないと恐らく駄目。
- したがって最近のシステムは攻撃できない。
- スタティックリンクすると pthread が組み込まれてしまうらしく、攻撃が成功したりしなかったりしてしまう。
攻撃コードの記述における注意点†
- sscanf を攻撃するため、攻撃コードおよびデータに 0x00(nul), 0x09(水平 tab), 0x0a(LF), 0x0d(CR) などの空白文字は使えない。使うとその地点で読み込みが止まってしまう。他の制御文字なら許されるが、できれば使わない方が良い。
- もしどうしても 0x00 などが必要ならば、適当に数値を加えたデータを書き込んでおいて、攻撃コード内で減算して使用する。
- 今回は swi 0x0090000b(sys_execve の呼び出し)に 0x00 が含まれるため、この手法を用いて動的にコードを生成した。
- その際、パイプライン処理に乗ってしまった命令は書き換えても読み直されない(たぶん)ため、自分の次の命令にジャンプする命令(b 4)を大量に突っ込んでおいて、その先に書き込むようにした。
- これは CPU 依存で、かなり不安定な方法です。改善の余地あり。
- 絶対アドレスを使用しない方が良い。
- 攻撃コードを更新したときに、アドレスを再計算して、書き換えるのが面倒くさい。
- 今回は攻撃コードの先頭部分で、pc を r5 にバックアップし、基準アドレスとした。データを参照するときはそこからの相対アドレスを使う。
- 従って、絶対アドレスを使っているのはリターンアドレスの上書きでどの位置にジャンプさせるか、という部分(0xbffff704 にジャンプさせている)だけである。
- この攻撃を作る元となった Intel 版の攻撃コードは非常に洗練されていて、制御文字が一切出てこないように工夫されていた。見ると感動だよ!
- スタックに push と pop しながら巧みに sys_execve の引数を作成する。すごいんだけど、ここでは関係ないので割愛。
使用したレジスタ一覧†
- r0: sys_execve の第一引数
- ldr 命令に r0 を指定すると nul 文字が出るため、ldmia (r5+n+68), {r0, r1, r2, r10} で読む。r10 は nul 文字出現を防止するために指定。
- r1: sys_execve の第二引数(r0 とともに使う)
- r2: sys_execve の第三引数(r1 とともに使う)
- r3: 0 に初期化して、0 を代入する必要があるときに使用した。
- r5: 攻撃データを参照するための相対アドレス(攻撃データの切れ目においた命令で mov r5, pc としている)
- r10: r0 〜 r2 に sys_execve の引数をセットするときに、nul 文字出現を防止するために指定している、実際には使用しない。
r0 〜 r2 のロード†
ldr r0, [r5+n+0]
としたい(n は攻撃コードサイズ、後ほど説明)が、nul 文字がでてしまう。そのためにメモリ上に工夫して置いて ldmia で一気に読む方法を使う。
| 0x00000000
[r0] r5+n+68
[r1] r5+n+72
[r2] r5+n+76
| 0xffffffff
メモリ上にはこのように配置する。
mov r6, r5
add r6, r6, #n+68
ldmia r6, {r0, r1, r2, r10}
このように読み込む。nul 文字を避けるため r10 が余計に追加されているが、副作用は r5+n+80 にあるゴミデータを読み出すだけなので問題ない。r10 でなくても構わないが、nul 文字だけには気をつける。
ldmid だと上から読んでくれないのでレジスタリストが変更されると、メモリとレジスタの対応位置が変わってしまって困る??ARM は 4つのモードがあって、よくわからん…。
今回作成した攻撃データの詳細†
ここでは
- 攻撃データの配置イメージ
- リターンアドレスについて説明
- 攻撃で使うデータについて説明
- 攻撃コードの掲載
をいたします。
今回作成した攻撃データの配置イメージ†
0x000: [バッファをあふれさせるための適当なデータ]
0x1b9: [上書きするリターンアドレスの値]
0x1bd: [nop 代わりの mov r5, r5 で埋めた領域]
0x4e1: [mov r5, pc] <- この地点の pc からの相対位置~
(r5 に入れる)で攻撃に用いるデータを参照する
0x4e5: [sub r5, r5, #8]
0x4e5: [攻撃コード]
0x5cd: [パディング]
0x60d: [攻撃データ]
- スタックの開始位置がゆれても、0x1bd からの nop のどこかから実行されるので、比較的ゆれに強い配置である。
上書きするリターンアドレスの値について†
コンパイルの条件などで変わる可能性があります。基本的には自分で探すものだと思います。
- リターンアドレス
- バッファオーバーフローによって、攻撃コードへのアドレスに上書きする。
- 今回の攻撃では exploit.pdf の 0x1e9 の位置に書かれている数値で上書きする。
- 0xbffff200: 今回の攻撃コードであふれさせる text のあるアドレスをこの位置とみなしている。
- バッファを埋めて、リターンアドレスを書き換えるまでに必要なデータ: 308バイト
- そこから nop で埋めてある領域の真ん中まで: 340バイト
- つまりリターンアドレスの値を、0xbffff200 + 308 + 340 で上書きする。
- ゴミデータ(スタティック版)
- line と doc を使っているところで引っかかるので、スタックの底の方を適当に指しておけば良いと思う。
リターンアドレスの格納位置の探し方†
参考までに、私が探した方法を掲載いたします。攻撃対象となる関数がどこにリターンするかを考えれば、比較的簡単に見つかるはずです。
- リターンアドレスの格納位置を探す方法
- psscan は doc_scanFile という関数の中で(場所は 0x1b3cc)呼ばれているため、関数が終了したときに制御が帰る場所は 0x1b3cc+0x04 = 0x1b3d0 のはずである。スタックからこの数値を探すと、0xbffff3dc にそれらしき数値がある。
- このように当たりをつける。gcc がスタックにどう配置するか、から計算しても良いけど、めんどくさいし、そのルールを知らん。
攻撃データの配置†
offset
+0 ["/bin"]["//sh"][NULL]
+12 ["-ppi"][NULL]
+20 ["touc"]["h${I"]["FS}/"]["tmp/"]["itwo"]["rked"][NULL]
+48 [argv[0]][argv[1]][argv[2]][argv[3]]
+64 [env]
+68 [r0][r1][r2]
+80 [swi]
- アクセス方法は、[r5 + 300(攻撃コードのサイズ) + offset] である。攻撃コードが長いなら、+300 の部分を増やして対応する。
- r5 自体は mov r5, pc の命令があるアドレスを指している。
各フィールドの説明
- +300 の部分は省いて、オフセットだけを示す。
- +0 〜 +44: sys_execve の第一引数に渡すデータ
- +0(8b): "/bin//sh"
- +8(4b): 上の文字列の後に 0x00 を置くための領域(攻撃コード内でここに 0x00000000 が書き込まれる)
- +12(4b): "-ppi"
- +16(4b): 上の文字列の後に 0x00 を置くための領域(攻撃コード内でここに 0x00000000 が書き込まれる)
- +20(12b): "touch${IFS}/tmp/itworked"
- +44(4b): 上の文字列の後に 0x00 を置くための領域(攻撃コード内でここに 0x00000000 が書き込まれる)
- +48 〜 +60: sys_execve の第一引数に指定するポインタ配列を置く領域
- +48(4b): argv[0] = "/bin//sh" へのポインタ(r5-48)
- +52(4b): argv[1] = "-ppi" へのポインタ(r5-56)
- +56(4b): argv[2] = "touch..." へのポインタ(r5-84)
- +60(4b): argv[3] = NULL(0x00000000)
- +64(4b): env の引数を指定するところ、NULL が入る
- +68 〜 +80: 作業領域
- +68(4b): r0, r1, r2 に sys_execve の引数を設定するときに使う作業領域
- +72(4b): 同上
- +76(4b): 同上
- +80(8b): swi 0x0090399b(後ほど加工されて swi 0x009000b にされ、攻撃コードに追加され、呼び出される)
攻撃コード†
載せる?
参考文献†