1月26日(金)1、2コマ目
今日、やったこと
バッファオーバーフロー
今日のホワイトボード
メモリは
プログラム実行時にコンピュータのメモリは以下の3つの領域に分けて使われる。
![]() |
| 図 メモリの使われ方 |
[おまけ情報]C言語のif文
C言語にはtrue、falseのbool型がない。
if()もカッコ内が0なら条件不成立、0以外なら条件成立として扱われる。
![]() |
| 図 C言語のif() |
サンプルプログラムでバッファオーバーフロー(パターン1)
サンプルプログラムはcheck()関数のstrcpy()でバッファオバーフローを引き起こす可能性がある。
引数passwordの内容を変数password_bufferにコピーしているが、コピー先のpassword_bufferのサイズに関係なくコピーしているため、バッファオーバーフローが発生する。
デバッガでcheck()関数のローカル変数を確認すると、下図のようにメモリ上に配置されていることがわかる。
![]() |
| 図 2つの変数のアドレス |
![]() |
| 図 2つの変数のメモリ上での状態 |
このメモリ配置からpassword_bufferの先頭から29バイト以上書き込めば、変数auth_flagのエリアに書き込むことになる。
バッファオーバーフローが発生するシナリオ
バッファオーバーフローで誤ったパスワード("SamplePassword"ではない)でも認証成功になるには以下の原因から。
①コマンドライン引数で29文字以上のパスワードを指定
②check()関数にコマンドライン引数のパスワードが渡される
③strcpy()で変数password_bufferにパスワードをコピー
このとき、パスワードの29文字目以降が変数auth_flagのエリアにコピーされる。
これが主原因。
④変数password_bufferが”SamplePassword”と等しいかチェック
等しければ変数auth_flagに1を代入
これも原因の1つ。本来なら等しくなれば0を代入すべき。
⑤check()関数は変数auth_flagの値を返す
パスワードが”SamplePassword”なら1を返す
それ以外なら初期値の0が返ると思っている。 => 実際は③で上書きされている
⑥main()関数ではcheck()関数の戻り値で認証の成否を判断
戻り値が0以外なら認証成功。
これも原因の1つ。戻り値が1なら認証成功にすべき。
[おまけ情報]デバッガのxコマンド
デバッガのxコマンドで変数がメモリ上のどこにあるか、その中身を確認することができる。
xコマンドは/以降のオプションで表示方式(数値として表示、文字として表示等)や表示サイズの指定ができる。
![]() |
| 図 デバッガのxコマンド |
サンプルプログラムでバッファオーバーフロー(パターン2)
パターン1のプログラムでパスワードが”SamplePassword”と等しくないときは変数auth_flagに0を代入するように改造。これでstrcpy()でバッファオーバーフローさせてもcheck()関数の戻り値は
- パスワードが"SamplePassword"なら戻り値は1
- パスワードが"SamplePassword"以外なら戻り値は0
になる。
パターン1と同じように29文字のパスワードで実行し、strcpy()実行後のメモリをデバッガで確認すると下図のようになっている。
![]() |
| 図 2つの変数のアドレス |
![]() |
| 図 2つの変数のメモリ上での状態 |
パターン1と同じようにバッファオーバーフローを引き起こし、変数auth_flagに29文字目の値が書き込まれている。
ただ、プログラムを修正したおかげでcheck()関数は0を返す。よって、認証失敗と表示される。
バッファオーバーフローは発生しているものの、プログラムは期待とおりに動いている。
スタック上のリターンアドレスを書き換える
関数が呼び出されるとき、スタック上にはローカル変数や引数のエリアが確保される。このとき、関数実行後に戻るべきアドレス(リターンアドレス)もスタック上に保存される。
サンプルプログラムを実行すると、実行ファイルが下図のようにメモリ上のコード領域にロードされる。ロードされた命令を順に1つづつ実行していく。
![]() |
| 図 コード領域にロードされた命令 |
0x0…4007a0番地のcallq命令が0x…4006b6番地の命令を呼び出すことで、check()関数を呼び出す。
0x…4006b6番地以降にロードされているcheck()関数を実行すると、main()関数に戻る。戻るべきアドレスは0x0…4007a5番地だが、この番地をcheck()実行時にスタック上に保存する。
デバッガで確認すると、下図のようになる。
![]() |
| 図 スタック上のリターンアドレスの位置 |
バッファオーバーフローで変数pass_buffの先頭から41バイト以降にリターンアドレスを書き込めば、check()関数実行後に戻る命令を変えることができる。
次回は
- バッファオーバーフローのつづき
- テスト
をします。








