簡単なバッファオーバーフローを再現してみる(2)

前回、途中までこの記事を書いて、問題発生しちゃってうまく再現できなかったので、書き直し。

ざっくりと内容を説明します。

こちらの技術書に書いてあるBOFの項目の復習と再現記録です。

環境は今回に限りUbuntu7.0.2 + gcc 3.3.6(前回の日記に書いた通り、当初の環境だとうまく再現できなかったため)

今回は関数の戻りアドレスの上書きを検証します。

コードはこちら。

最初はpassword_bufferへ確保されているサイズ以上の文字列を与えることで、auth_flagを上書きする、というものでしたが、今回はメモリ上でauth_flagがアドレスが小さいため、その手法が利用できません。

今回は再現と検証に時間かかりすぎました・・・。バージョンの違いでずいぶんと挙動が変わりますね・・・。

ざっくりといきます。まずはいつもどおりブレイクポイント仕掛けて、メモリの位置を確認してあげます。
まずは実行

Screen Shot 2013-03-01 at 19.00.14

 

 

次はメモリアドレスを調べてあげます。Screen Shot 2013-03-01 at 19.01.30auth_flagがpassword_bufferより小さいメモリアドレスになってるのがわかります。

つまり、単純にpassword_bufferに大量の文字列を挿入して上書きしても、auth_flagnには何の影響も出ないことがわかります。

そのまま実行を続けても・・・

Screen Shot 2013-03-01 at 19.04.49メイン関数に戻る前にSegmentation faultになって強制終了しちゃいます。

なので、今度はアセンブラにして見てみます。main()関数のほうをアセンブラに変換してみます。

Screen Shot 2013-03-01 at 19.07.40注目するべきはアドレス 0x08484e4 <main+66>の箇所です。

ここでcheck()関数を呼び出しています。関数呼び出しが行われると、その前のmov命令で、メモリ上のスタックセグメントに、引数とか、callした関数から処理がmain()関数に戻ってくるためのアドレスなどが、プッシュされます。(この辺の仕組みも復習のため別途記事書きます。今決めました。)すごーくざっくり言うと、この場合check()関数が終わった後に、次の行である0x08484e9へ戻るために、そのアドレスをスタックに格納する仕組みになっています。もちろん、呼び出された関数内より先にスタックにpushされるので、check()関数内のpassword_bufferやauth_flagより先にスタックに格納されていることになります。

つまりは、アドレスの小さい順にこの3つを書くと

auth_flag, password_buffer, main()関数への戻りアドレス

になっているはずなんです。(他にも格納されているものはありますが、今は分かりやすくこの3つだけを取り上げます。)

確認してみます。今回は引数にわかりやすい文字列を、16文字ぴったり与えてみます。 Screen Shot 2013-03-01 at 19.18.36ブレイクポイントのところで、引数をpassword_bufferへコピーしているので、もうひとつだけ進めます(stepコマンドでステップ実行)

その状態で、メモリアドレス内を確認してみます。

Screen Shot 2013-03-01 at 19.19.20

引数で与えた通りに、0xbffff820から16byte分にhogeが4回格納されていますね(リトルエンディアンなので、4byteごとに逆に格納されていることに注意してください。)

そして、その後、12byte後(0xbffff83c)から4byteに、 main()関数の戻りアドレスである0x08484e9が格納されているのが確認できます。

つまり、ここを認証後の処理が格納されているメモリアドレスに書き換えてしまえば良さそうです。認証後の処理がどのアドレスに格納されているかを確認していきます。またアセンブラで処理を表示させます。( disassemble main )

Screen Shot 2013-03-01 at 19.27.25

ぶっちゃけこれはサンプル用に書いたプログラムなんで、一目瞭然と言っていいくらいに見やすくなっちゃってますが、check()関数から帰ってきて2行目、0x08484ebで何かしらの比較を行なって、その後0x08484513にジャンプして、そのあと、print()関数を呼び出しています。

これってif文がelseになった時の処理じゃないか・・・とアタリをつけます。(キッチリ追っていけばもちろん根拠を持ってそうなりますが)

つまりそれより前にある、print()関数を三回コールしているところが、if文がtrueだったときの処理ですね。最初のprint()関数が呼ばれているひとつ前の0x080484edへ、戻りアドレスを書き換えれば、認証を回避できるかもしれないので、やってみます。

引数にこのアドレスを突っ込んであげればOKです。リトルエンディアンなので、1byteづつ後ろから並び替えて与えます。xedx84x04x08ですね。

Screen Shot 2013-03-03 at 1.48.36引数にperlでxedx84x04x08を10回繰り返して与えます。それによってpassword_bufferをBOFさせます。その状態でpassword_buffer周辺のメモリ内容を確認してみます。

Screen Shot 2013-03-03 at 1.52.18ご覧の通り、先ほどの場所のアドレスが0x980484edで上書きされてますね!これで、check()関数が終わったあと、そのままそのアドレスへ戻るようになります。その状態で、動作させてみます。

Screen Shot 2013-03-03 at 1.55.19認証を回避し、authentication is success!が表示されていますね。

こんな感じです。

この手法を使うことによって、auth_flagがメモリ上にBOFさせるアドレスより前にある場合でも認証を回避しています。つまり任意のアドレスにジャンプさせることができます。(本当は各アセンブラの処理内容についてもまとめようと思いましたが、ちょっと長くなりすぎるので、またの機会にまとめます。)

次回のBOFの記事では、いよいよ任意のコードを挿入する手法について書きたいと思いますが、自分の環境で再現し、理解してからなので、時間かかっちゃいそうです・・・。

その前にnullcon Battle Undergroundで自分が担当した問題のwriteupの記事書きます。今日は早めに寝よう・・・。

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です