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

自分の復習用に書いておきます。

CodeGate2012 Vuln300を解いていたんですが、他の方が解いたwriteupを見るとBOFを利用したシェルコードの実行が必要不可欠なんですが、正直さっぱりでしたので、手元にある技術書を参考にBOFの脆弱性を持ったプログラムと、その脆弱性を利用したシェルコードを実行してみたいと思います。今回の内容再現に参考・利用させていただいている技術書

本当に基礎的な部分なので、退屈かもしれません。申し訳ないです。原理はわかっていても、実際問題を解いてみると全く理解できていなかったのがよくよくわかりました・・・orz

実行環境はCentOS 6.3です。バージョンの確認は ”$cat /etc/redhat-release”で確認できます

とりあえず書いたコード。読んでる本に出たサンプルを少し変えただけです。

引数に与えられた文字列を”SamplePassword”と等しければ、認証成功の旨が書かれたメッセージが出力される単純なプログラムですね。それぞれ変数の値が見えるように、文字列比較処理の前後に標準出力に変数を表示するようにしてあります。

以下、通常に実行した場合の出力です。いつもは普段使ってるMacでスクリーンショットとってましたが、今回はローカル限定の仮想環境なんで、ホスト名とかそのままのっけちゃいます。

Screen Shot 2013-02-26 at 12.44.54

引数なしで、利用方法が出力。引数に文字列を与えると、それが正しいパスワードであるかをチェックして結果を出力しています。

しかし、以下のように実行してあげると

Screen Shot 2013-02-26 at 13.55.03

正しいパスワード文字列を引数として与えてないにも関わらず、認証成功の出力が表示されてしまいます。なぜこのような処理が実行されてしまているのか。チェックしていきます。

Linux上で動作するgdbコマンド(デバッガ)を利用します。ざっくりとした使い方は、調査を進めながら随時紹介します。

その前にデバッグをしやすいように、コンパイル時にオプションを付加してコンパイルし直します。(これはあくまで勉強用にオプションを付加しているだけで、CTFで問題として与えられるファイルは既にコンパイルされており、このオプションを付加することはまずできません。)-gオプションをつけると実行ファイルにデバッグ情報を付加することができます。

Screen Shot 2013-02-26 at 14.11.58

gdbは引数にデバッグしたい実行ファイルのパスを与えるだけです。dbgのコンソールが実行され、入力待ち状態になります。

コンパイル時にデバッグ情報を付加したおかげでlistコマンドが使えます。list <整数n>でn行目から始まるソースを10行ずつ表示できます。

Screen Shot 2013-02-26 at 14.15.36

さきほどコンパイルした実行ファイルのソースが表示されてますね。ちょっと参考書と全く同じ流れを再現しているだけなので、退屈ですが、仕方ない・・・・。11行目のpassword_bufferへの文字列コピーと、check()関数のreturnでブレイクポイントを仕込んでおきます。(password_bufferの状態を、strcpy()関数の前後でどう変化しているかを確認するため)

break <整数n>でn行目にブレイクポイントを設定できます。

Screen Shot 2013-02-26 at 14.33.44この状態からプログラムを実行してみます。実行はrunコマンドです。(runのあとに任意の文字列を与えることで、通常に実行するときの引数を与える動作と同じになります。)

Screen Shot 2013-02-26 at 20.05.44

runコマンドを使って実行すると、最初のブレイクポイントで処理が止まります。この状態で、現在の状態を確認していきます。

x/fmtコマンドを利用することで、現在のメモリの状態を確認できます。(fmtは表示フォーマットを指定します。gdbコマンドに関しては、完全に忘れてしまったので、改めてまとめブログ書こうと思います。)

ひとまず x/fmtコマンドだけまとめ

こんな感じですね。addrにはコード中の変数名なんかも適用できます。Screen Shot 2013-02-26 at 20.27.10

つまり、

char型[16]配列のpassword_bufferはメモリのアドレス0x7fffffffe3d0にあり、値は”20345377377177″

int型 auth_flagはメモリのアドレス0x7fffffffe3ecですね。

つまり、メモリ上にint型auth_flagはchar型[16]配列password_bufferの28バイト後ろに格納されていることがわかります。

Screen Shot 2013-02-26 at 20.37.38それから、password_bufferから29バイト文、メモリの中身を覗いてみます。これでpassword_bufferとauth_flagの両方の状態が見えてますね。ちなみにさっき、” x/s password_buffer”で、6byteしか表示されていなかったのは7byte目にasciiコードでNULがあったからですね。xコマンドのフォーマットsはnull文字までの文字列を表示するので、計16byteの配列で宣言していたのにも関わらず、7byte目までが表示されました。

password_bufferは初期化していないので、NULLがいっぱい入っていたり、文字じゃないコードが入っていたりと、まぁめちゃくちゃですね。初期化していない変数とかをprintfすると変な文字が出てくるっていうのは、プログラミングの勉強始めた頃、誰もがやりますよね(笑)要するにこういうことですね。初期化前は適当なよくわからないもんが勝手に入ってます。しかし、autu_flagのほうはしっかり0で初期化しているので、そのとおりの値が入ってます。(上の画像の一番最後の0x00がそうです。アドレスは0x7fffffffe3ec)

それぞれの変数の状態が確認できました。それでは次のブレイクポイントまで処理を進めてみます。処理を継続するにはcontinueコマンドを実行します。Screen Shot 2013-02-26 at 20.58.06

 

処理がソースコードの20行目でストップしたことがわかります。

この時点で、checkが想定外の動作をしているため、認証を通過してしまうので、この状態で各変数をチェックしていきましょう。(デバッグ用に書いたprintf文で既に変数の中身がどうなってるかは出力されてしまっていますが、メモリ上でどのように保存されているかを確認します。int型は4byteなので28byte+4byteで32byte分表示しています。さっきミスって29byteb分しかスクショとってなかったけど、全部撮り直すのめんどくさいや・・・w)

Screen Shot 2013-02-26 at 21.36.55

こんな感じになってます。auth_flagのアドレスである、0x7fffffffe3ecは0x41(10進数で65、asciiコードで’A’)になってますね。

なぜこうなってしまうか、というと

  1. password_bufferがchar型[16]の配列(16byte)で宣言されメモリアドレス0x7fffffffe3d0に確保される(初期化していないため、値は不定)
  2. auth_flagがint型(4byte)で宣言され、0x7fffffffe3ecに確保される(0で初期化されているため、内容は0x00000000)
  3. strcpy(password_buffer, password)が実行され、password_bufferのアドレス0x7fffffffe3d0から引数の文字列がコピーされる(Aが29個で29byte)つまり、password_bufferは16byte しか確保されてないが、0x7fffffffe3d0から29byteに’A’が書き込まれる
  4. auth_flagのアドレスは0x7fffffffe3ecはpassword_bufferから28byteなので、無論ここも’A’で上書きされる。
  5. check()関数からmain()関数にauth_flagの値がreturnされる
  6. main()関数のif文で、auth_flagが0(c++やjavaでいう、bool型false)かそれ以外かをチェック
  7. check()関数から返却された値は10進整数(int)で65(asciiコードで’A’)なのでif文はtrueで処理
  8. 認証OKの出力が表示される

こういうことですね。これがBOFの基本ですか。。。

しかし、これだけだと、例えばauth_flagとpassword_bufferの宣言を逆にするだけで、この手法は使えなくなります。(スタックセグメントに関しても復習のために別途ブログ書きます)プログラム中に宣言された変数は、スタック状にメモリ中に確保されるため、先に宣言したほうが、アドレス値としては大きくなります。(password_bufferを先に宣言すればアドレスはauth_flagより、password_bufferが後ろにくる、ということ)これが逆になると、password_bufferの配列より大きい引数を渡して、BOFさせてもauth_flagが上書きされません。

そういったプログラムに対するBOFは次回また自分の環境で実証して、ブログに書きたいと思います。勉強になりました・・・。

ちなみに、macos上で再現しようとしたところ、abort trap:6と表示され、再現できませんでした・・・。メモリ上に命令として有効でない値が入ると、そのように処理が止まる、みたいな内容をぐぐったら出てきたような気がしますが、確認はしていません。

 

 

 

コメントを残す

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