
■ 強制終了されないように作り変える ■
前回はバッファオーバーフローにより、「呼出し元」の情報が
どのような値に書き換わってしまうのかを実際に見てみました。
そしてインテルプロセッサは「バッファオーバーフローが発生
した」から怒っているのではなく、「呼出し元のコードを実行
しようとしたけど出来ませんでしたよ。」と言って怒っている
事も説明しました。
前回のプログラム動作イメージをもう1度見てみましょう。
┌──────────────────────────┐
│ OF1.EXE │
│ │
└───┬──────────────────────┘
│ スタック領域
│call命令 格納 ┌──────┐
├─────────────────────→│ 呼出し元 │
│ 制御を移行┌────────┐ └──────┘
├────────→│ main関数 │
│ │ │
│ └───┬────┘
│ │
┌──┴──┐
│コピー処理│
└──┬──┘
│×バッファオーバーフロー発生
│ スタック領域
│ ┌──────┐
├───────→│1F 1E 1D 1C │
│ └──────┘
│処理終了 スタック領域
│どこへ戻るか参照┌──────┐
│←───────┤1F 1E 1D 1C │
│ └──────┘
×××××××
×そんな所に×
×実行可能な×
×プログラム×
×ありません×
×××××××
今回はインテルプロセッサが怒り出さないようにプログラムを
改造してみます。
まず、どうすればインテルプロセッサが怒らないようになるの
か、という事から説明します。
前回説明したように、インテルプロセッサは「バッファオーバ
ーフローが発生した」から怒っているのではなく、「呼出し元
のコードを実行しようとしたけど出来ませんでしたよ。」と言
って怒っているのでした。
それでは「呼出し元」の情報を、「1F 1E 1D 1C」等というわ
けの分からない値でなく、「実行可能なコード」が格納されて
いる場所を示すように変更してあげれば怒らなくなるのではな
いでしょうか。
以下に怒り出さないように変更を加えた後の動作イメージを示
します。
先ほどのイメージとはバッファオーバーフローが発生した後の
動作が異なります。
┌──────────────────────────┐
│ 変更を加えた後のプログラム │
│ │
└───┬──────────────────────┘
│ スタック領域
│call命令 格納 ┌──────┐
├─────────────────────→│ 呼出し元 │
│ 制御を移行┌────────┐ └──────┘
├────────→│ main関数 │
│ │ │
│ └───┬────┘
│ │
┌──┴──┐
│コピー処理│
└──┬──┘
│×バッファオーバーフロー発生
│ スタック領域
│ ┌──────┐
├───────→│実行可能な │
│ │コードの場所│
│ └──────┘
│処理終了 スタック領域
│どこへ戻るか参照┌──────┐
│←───────┤実行可能な │
│ │コードの場所│
│ └──────┘
│ ┌───────┐
└─→│ 実行可能な │
│ コード実行 │
└───────┘
「呼出し元」の情報が格納されていた場所が、バッファオーバ
ーフローにより「実行可能なコードの場所」に書き換えられて
います。
これにより、メイン関数の処理が終了する時に「実行可能なコ
ードの場所」に制御が移行され、指定したコードが実行される
ようになるはずです。
それでは実際に上のイメージの様に動作するか試してみましょ
う。
以前作成したプログラム"of1.c"をコピー&貼り付けして、新
しく出来たファイルを"of2.c"というファイル名に変更してく
ださい。
今回は"of2.c"ファイルに変更を加えていきます。
どのように変更を加えるかというと、新しく「sub」という関
数を作成し、メイン関数の処理が終了した時に「sub」関数が
実行されるようにします。
"of2.c"ファイルを以下のように変更してみて下さい。
"of1.c"との差分がある部分にはコメントを付けておきます。
─↓ここから──────────────────────
#include <stdio.h>
#include <string.h>
#include <stdlib.h> //←●追加
void main(void);
void sub(void); //←●追加
void main()
{
unsigned char* i;
unsigned char b[]= { 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f,
0xff, 0xff, 0xff, 0xff,
};
unsigned char a[]= { 0x0, 0x1, 0x2, 0x3,
0x4, 0x5, 0x6, 0x7,
};
// スタック領域を表示する。
for(i=&b[0];i<=&a[7];i+=4)
{
printf("%08X:%02X %02X %02X %02X\n", i, *i, *(i+1), *(i+2), *(i+3));
}
// バッファbをバッファaにコピーする。
strncpy((char*)a, (char*)b, sizeof(b));
printf("copy end\n");
// スタック領域を表示する。
for(i=&b[0];i<=&a[7];i+=4)
{
printf("%08X:%02X %02X %02X %02X\n", i, *i, *(i+1), *(i+2), *(i+3));
}
printf("&sub:%08X\n", &sub); //←●追加
}
// ●↓ここから追加
void sub(void)
{
printf("Overflow call!!\n");
exit(0);
}
// ●↑ここまで追加
─↑ここまで──────────────────────
変更部分は4箇所だけです。
まず上から3行目の部分に
#include <stdlib.h> //←●追加
というのがあります。
これは後で出てきますが、「exit」というC言語の標準関数を
呼出す為に必要になります。
この記述を追加する事により「exit」関数を呼出す事が出来る
ようになります。
次に、2行下の方に、
void sub(void); //←●追加
というのがあります。
これは、「sub」という関数を呼出しますよ、という宣言です。
そして1番下の方に、
printf("&sub:%08X\n", &sub); //←●追加
というのがあります。
ここでは、「sub」という関数がメモリ上のどこにあるか、と
いうアドレス情報を表示させるようにしています。
最後に、
// ●↓ここから追加
void sub(void)
{
printf("Overflow call!!\n");
exit(0);
}
// ●↑ここまで追加
というのがあります。
ここで「sub」関数を定義しています。
この関数は「Overflow call!!」という文字列を表示して、
「exit」関数を呼出すだけの処理です。
「exit」関数というのは、プログラムを終了させる関数です。
ここまで変更したところで、とりあえず1度実行してみます。
上記の変更を加えただけではまだ「sub」関数が実行される事
はありませんが、これにより「sub」という関数がメモリ上の
どこにあるのか、というアドレス情報を知る事が出来ます。
それでは、とりあえずビルド&実行させてみましょう。
例によって、このプログラムもバグを含んだ物ですので、自己
責任において実行させてください。
実行結果を以下に示します。
─↓ここから──────────────────────
0063FDDC:10 11 12 13
0063FDE0:14 15 16 17
0063FDE4:18 19 1A 1B
0063FDE8:1C 1D 1E 1F
0063FDEC:FF FF FF FF
0063FDF0:00 01 02 03
0063FDF4:04 05 06 07
copy end
0063FDDC:10 11 12 13
0063FDE0:14 15 16 17
0063FDE4:18 19 1A 1B
0063FDE8:1C 1D 1E 1F
0063FDEC:FF FF FF FF
0063FDF0:10 11 12 13
0063FDF4:14 15 16 17
&sub:004020D9
─↑ここまで──────────────────────
「不正な処理を行ったので強制終了します。」と表示されたら、
「閉じる」を押して終了させてください。
ここで1番注目して頂きたいのが、最後の行にある、
&sub:004020D9
です。
これは「sub」という関数が「メモリ上のどこにあるか」を示
す、アドレス情報になります。
この実行結果はWindows98の物ですが、環境によりさまざまな
値になると思います。
「sub」という関数は正常に処理が行える「実行可能なコード」
です。
そして、この「sub」という関数がメモリ上のどこにあるのか
も分かりました。
後はこれを「呼出し元」の部分に設定してあげれば、インテル
プロセッサが怒り出す事は無くなるはずです。
続きは次回に。