バッファオーバーフローを起こして、バッファオーバーランさせようとしたけど駄目だった話
どうも、今週はセキュリティ関連の講座に出ており、朝から晩までずっと教室に缶詰の状態でした。
本来参加しようとすると非常に高額な参加費が取られるのですが、学生枠ということで無料で参加させて頂きました。
講義の内容などを記事にはしたいと思うのですが、内容は喋ってはいけないという約束なので話せないです。
とりあえず、自分のセキュリティの勉強として「バッファオーバーラン」くらい起こしてみようと思って試行錯誤してみましたが、ちょっと知識不足でした。
来週までには任意のコードが入力を通してOS側にぶち込めるように出来たらいいな、と思うが出来なかったら考える。
プログラムの基本的な動作
基本的なプログラムを実行するデータ構造は以下のようになっている。
IPA/ISEC から引用
この画像の様に、プログラムの実行コード自体はコンパイル時に計算され、実行時には仮想メモリ領域の先頭に配置される。
その後、new,malloc()などの部分で確保する領域はヒープ領域と言われ、逆に関数自体に使用するデータを格納する部分をスタック領域と呼ぶ。
バッファオーバーフロー
スタック領域では以下の様な関数自体に固定されたデータ、つまりは自動変数が割り当てられるということを上で説明した。
void hoge() { char fuga[16]; // これが自動変数として、関数がロードされた時にスタック領域に追加される /** **/ return; }
しかし、本来この領域は必要なデータ長の部分だけ確保されており、予定外の長さのデータが挿入された場合は、他の自動変数部分やリターンアドレスを汚染することになる。
これが今回述べるバッファオーバーフローという問題だ。
以前でもブログで記述したが、バッファオーバーフローという脆弱性は非常に深刻なバグで任意のコードを実行させることが出来るという脅威の対象にさらされてしまう。
一例を上げるなら、
- 任意のプログラムをネット上からダウンロードさせられる
- プログラムの実行権限でファイルの変更・削除・新規作成などが行われる
などという具合だ。
バッファオーバーフローの実例
今回、残念ながらバッファオーバーフローという脆弱性を突いて任意のコードを実行する「バッファオーバーラン」という攻撃を行うことが出来なかったので、少なからずバッファオーバーフローがどのような時に発生するか、どのような影響があるのかという部分をコードと実例を元に紹介したいと思う。
実験コード
#include<stdio.h> int main(int argc, char** argv) { char buf1[15], buf2[10]; memset(buf1, '\0', 15); memset(buf2, '\0', 10); scanf("%s", buf2); // ここが脆弱性 printf("buf1:%s\n", buf1); printf("buf2:%s\n", buf2); printf("%d %x", argc, argv); return 0; }
以上のコードは見て分かる方も多いと思うが、バッファオーバーフローの脆弱性を持っている。
scanfで任意長のデータをbuf2に入力出来るのが伺えると思う。
では、まずはこのプログラムを正常に動かして見たいと思う。
$ gcc bufferOverFlow.c -o bufferOverFlow $ ./bufferOverFlow test # こちらが入力文字列 buf1: # buf1は初期化された後は何も代入されていないので表示されなくて正常 buf2:test # 入力された文字列が 1 3f1738 # コマンドライン引数は無いので値は1が正常
これが正常な状態だ。
では、これをギリギリまで(buf2の限界の9+1文字*1まで入力してみる)
$ ./bufferOverFlow 123456789 buf1: buf2:123456789 1 8b1738 # argvのアドレスが変わるのは正常
では、さらにここに5文字追加してみる。
$ ./bufferOverFlow 123456789012345 buf1:12345 # 空文字のはずのbuf1に12345が溢れている!! buf2:123456789012345 # 本来10文字で設定しているはずなのにそれ以上の文字を出している 1 5d1738
ここで何故buf2は配列数が10という大きさであるにも関わらず出力しているのか?
それはC言語はヌル文字\0という文字が見つかるまで、文字を出力し続けるようになっているからだ。
つまり、\0が出るまでは文字列、という認識を行っている。
さらに、多くの情報を入力してみよう。
$ ./bufferOverFlow 123456789012345678901234567890123456789012345678901234567890 # 50文字を入力 buf1:12345678901234567890123456789012345678901234567890 buf2:123456789012345678901234567890123456789012345678901234567890 892613426 39383736
完全にargc, argvの値までも崩壊させてしまった。
これを説明するにはどのような順番でデータが積まれているのかを想像して欲しい。
変数の出てくる順番だが、
argc, argv, buf1, buf2
という順番だ。
スタックというデータ構造が分かっていれば、このように積み重なっているのが想像出来る。
buf2
buf1others
argv
argc
そのため、最初はbuf2の漏れた情報がbuf1に侵食した。
そして、更に大きな値を渡すとothersの情報を侵食し、更に引数まで汚染した結果になった。
ちなみに気づいた人もいると思うが、
39383736 → 0x39 | 0x38 | 0x37 | 0x36 → '9' | '8' | '7' | '6'
となっている。
もちろん、このプログラムは既にリターンアドレスの侵食も発生しているため、正常に動作出来なくなり終了してしまう。
後はこのリターンアドレスとそれ以下の部分を任意のコードに書き換えることにより、不正なコードを実行させることが出来るようになるという訳だ。
(それが出来なかったので悲しい)
*1:ヌル文字