自分の復習用に書いておきます。
CodeGate2012 Vuln300を解いていたんですが、他の方が解いたwriteupを見るとBOFを利用したシェルコードの実行が必要不可欠なんですが、正直さっぱりでしたので、手元にある技術書を参考にBOFの脆弱性を持ったプログラムと、その脆弱性を利用したシェルコードを実行してみたいと思います。今回の内容再現に参考・利用させていただいている技術書
本当に基礎的な部分なので、退屈かもしれません。申し訳ないです。原理はわかっていても、実際問題を解いてみると全く理解できていなかったのがよくよくわかりました・・・orz
実行環境はCentOS 6.3です。バージョンの確認は ”$cat /etc/redhat-release”で確認できます
とりあえず書いたコード。読んでる本に出たサンプルを少し変えただけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <stdio.h> #include <stdlib.h> #include <string.h> int check(char *password){ int auth_flag=0; char password_buffer[16]; printf("auth_flag:%dn", auth_flag); printf("password_buffer:%sn", password_buffer); strcpy(password_buffer, password); printf("auth_flag:%dn", auth_flag); printf("password_buffer:%sn", password_buffer); if(strcmp(password_buffer, "SamplePassword") == 0){ auth_flag = 1; } return auth_flag; } int main(int argc, char *argv[]) { if(argc < 2){ printf("usage: %s <password>n", argv[0]); exit(0); } if(check(argv[1])){ printf("--------------------------n"); printf("authentication is success!n"); printf("--------------------------n"); }else{ printf("login failed...n"); } return 0; } |
引数に与えられた文字列を”SamplePassword”と等しければ、認証成功の旨が書かれたメッセージが出力される単純なプログラムですね。それぞれ変数の値が見えるように、文字列比較処理の前後に標準出力に変数を表示するようにしてあります。
以下、通常に実行した場合の出力です。いつもは普段使ってるMacでスクリーンショットとってましたが、今回はローカル限定の仮想環境なんで、ホスト名とかそのままのっけちゃいます。
引数なしで、利用方法が出力。引数に文字列を与えると、それが正しいパスワードであるかをチェックして結果を出力しています。
しかし、以下のように実行してあげると
正しいパスワード文字列を引数として与えてないにも関わらず、認証成功の出力が表示されてしまいます。なぜこのような処理が実行されてしまているのか。チェックしていきます。
Linux上で動作するgdbコマンド(デバッガ)を利用します。ざっくりとした使い方は、調査を進めながら随時紹介します。
その前にデバッグをしやすいように、コンパイル時にオプションを付加してコンパイルし直します。(これはあくまで勉強用にオプションを付加しているだけで、CTFで問題として与えられるファイルは既にコンパイルされており、このオプションを付加することはまずできません。)-gオプションをつけると実行ファイルにデバッグ情報を付加することができます。
gdbは引数にデバッグしたい実行ファイルのパスを与えるだけです。dbgのコンソールが実行され、入力待ち状態になります。
コンパイル時にデバッグ情報を付加したおかげでlistコマンドが使えます。list <整数n>でn行目から始まるソースを10行ずつ表示できます。
さきほどコンパイルした実行ファイルのソースが表示されてますね。ちょっと参考書と全く同じ流れを再現しているだけなので、退屈ですが、仕方ない・・・・。11行目のpassword_bufferへの文字列コピーと、check()関数のreturnでブレイクポイントを仕込んでおきます。(password_bufferの状態を、strcpy()関数の前後でどう変化しているかを確認するため)
break <整数n>でn行目にブレイクポイントを設定できます。
この状態からプログラムを実行してみます。実行はrunコマンドです。(runのあとに任意の文字列を与えることで、通常に実行するときの引数を与える動作と同じになります。)
runコマンドを使って実行すると、最初のブレイクポイントで処理が止まります。この状態で、現在の状態を確認していきます。
x/fmtコマンドを利用することで、現在のメモリの状態を確認できます。(fmtは表示フォーマットを指定します。gdbコマンドに関しては、完全に忘れてしまったので、改めてまとめブログ書こうと思います。)
ひとまず x/fmtコマンドだけまとめ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[gdb] x コマンド ・書式 x [[/nfu] addr] addrにある値を表示する。 nfuはnが繰り返し回数、fが表示フォーマット、uが表示するデータの単位バイト数を指定できる ・フォーマットオプション a アドレスとして表示 c 文字定数として表示 d 符号付き10進数として表示 f 実数として表示 i 機械語として表示 o 8進数として表示 s NUL文字で終端する文字列として表示 t 2進整数として表示 u 符号なし10進数として表示 x 16進数として表示 ・単位バイト数オプション b バイト g ジャイアントワード 8byte h ハーフワード 2byte w ワード 4byte |
こんな感じですね。addrにはコード中の変数名なんかも適用できます。
つまり、
char型[16]配列のpassword_bufferはメモリのアドレス0x7fffffffe3d0にあり、値は”