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()関数実行後に戻る命令を変えることができる。


次回は

  • バッファオーバーフローのつづき
  • テスト

をします。





このブログの人気の投稿

1月12日(金)1、2コマ目

1月29日(月)3、4コマ目

1月9日(火)1、2コマ目