■ プログラムを作る ■
前回はバッファオーバーフローのバグと、他のバグとの違いに
ついて説明しました。
バッファオーバーフローは「同じ領域内」で発生するバグの為、
プロセッサの保護機能が動作してくれない事を説明し、そして
これにより、「エラーが発生しても処理を続行してしまう。」
という問題を含んでいる事も説明しました。
今回は、バッファオーバーフローのプログラムを実際に作って
みようと思います。
まず最初は正常なプログラムから作ってみましょう。
新しく"of1.c"というファイルを作成し、中身は以下の様にし
てみてください。
─↓ここから──────────────────────
#include <stdio.h>
#include <string.h>
void main(void);
void main()
{
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,
};
// バッファbをバッファaにコピーする。
strncpy((char*)a, (char*)b, sizeof(a));
printf("copy end\n");
}
─↑ここまで──────────────────────
ここでお願いがあるのですが、この講座はDigital Mars社製の
Cコンパイラを使用する事を前提として説明します。
マイクロソフト社製のVisual C++を御使用の方もいらっしゃる
かもしれませんが、この講座を理解する為には、Visual C++を
使用する事はあまりお勧めできません。
何故かと言うと、Visual C++を使用した場合、データの並び順
をプログラマの意図した通りにする事が難しく、混乱を招く恐
れがあるからです。
Visual C++コンパイラはデータを勝手に並び替えてしまうよう
に機械語コードを出力してしまうのです。
更に、Visual C++にはデバッグモードとリリースモードという
2つのモードがあり、これによってもデータの並び順や、デー
タを格納する領域自体の大きさ、機械語コード等も変わってし
まうようです。
何故マイクロソフトはデータを並べ替えてしまうという、わけ
の分からない機能を入れたのか分かりませんが、マイクロソフ
ト製品にはこのようなわけの分からない機能を含んでいる事は
よくある事なので我慢しましょう。(^^;
きっと何か深い理由があるのかもしれません。
また、この様なコンパイラの違いというのは、通常のプログラ
ムであればあまり気にする必要はありません。
これから作成するプログラムはバッファオーバーフローを発生
させる為に作成する物なので、データの並び順や大きさが非常
に重要になってくるのです。
という事で、先に示したソースコードはDigital Mars社製コン
パイラ用ですので、よろしくお願いします。
ただし、マイクロソフト社製のVisual C++を使用している場合
でも、デバッグモードに限っては若干の修正で大丈夫なようで
す。
以下にVisual C++を使用した場合のソースコードを示します。
Visual C++ Debug modeの場合
─↓ここから──────────────────────
#include <stdio.h>
#include <string.h>
void main(void);
void main()
{
unsigned char a[]= { 0x0, 0x1, 0x2, 0x3,
0x4, 0x5, 0x6, 0x7,
};
unsigned char b[]= { 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f,
0xff, 0xff, 0xff, 0xff,
};
// バッファbをバッファaにコピーする。
strncpy((char*)a, (char*)b, sizeof(a));
printf("copy end\n");
}
─↑ここまで──────────────────────
先ほど示したソースコードとは、データを定義する順序が変わ
っただけです。
リリースモードの場合は変更点が多くなりそうなので今回は省
略します。
また、今後示すソースコードは全てDigital Mars社製コンパイ
ラを使用する事を前提としますので、Visual C++を使用しても
混乱しない自信がある方だけVisual C++を使用してください。
混乱しそうだという方は素直にDigital Mars社製コンパイラを
インストールしましょう。(無料です。)
インストールのやり方はこちらに書いてあるので見てみてくだ
さい。
>>>
academy002-039.htm
さて、それではソースコードの説明を行います。
C言語が分かる方であれば、このプログラムが何をしているか
すぐに分かると思います。
バッファ「a」とバッファ「b」を確保して、コピーしている
だけの処理です。
このプログラムの最初でバッファを確保しています。
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,
};
これは「main」という関数の中だけで「一時的に使用出来るデ
ータ」です。
このバッファは「スタック領域」という場所に確保されます。
以下に、スタック領域にバッファ「a」とバッファ「b」が確
保されているイメージを示します。
スタック領域
┌┌────┐
││ 10 │
││ 11 │
││ 12 │
バッファb││ 13 │
││ 14 │
││ 15 │
││ 16 │
││ 17 │
││ 18 │
││ 19 │
││ 1A │
││ 1B │
││ 1C │
││ 1D │
││ 1E │
││ 1F │
││ FF │
││ FF │
││ FF │
││ FF │
├├────┤
││ 0 │
││ 1 │
││ 2 │
││ 3 │
バッファa││ 4 │
││ 5 │
││ 6 │
││ 7 │
└└────┘
ここまでは良いでしょうか?
普通にスタック領域にデータが格納されていますね。
次に、バッファ「b」をバッファ「a」にコピーしています。
ここで、バッファ「a」のサイズ分だけコピーしているという
事に注意しておいてください。
strncpy((char*)a, (char*)b, sizeof(a));
~~~~~~~~~
↑
バッファ「a」のサイズ分だけコピーする指示。
つまり、サイズチェックが行われているという事。
以下にコピー処理が行なわれているイメージを示します。
スタック領域
┌┌────┐┐
││ 10 ││
││ 11 ││
││ 12 ││バッファ「a」のサイズ分
バッファb││ 13 │├─┐
││ 14 ││ │
││ 15 ││ │
││ 16 ││ │
││ 17 │┘ │
││ 18 │ │
││ 19 │ │
││ 1A │ │コピー
││ 1B │ │
││ 1C │ │
││ 1D │ │
││ 1E │ │
││ 1F │ │
││ FF │ │
││ FF │ │
││ FF │ │
││ FF │ │
├├────┤┐ │
││ 10 ││ │
││ 11 ││ │
││ 12 ││ │
││ 13 ││ │
バッファa││ 14 ││←┘
││ 15 ││
││ 16 ││
││ 17 ││
└└────┘┘
バッファ「b」がバッファ「a」にコピーされました。
このプログラムでは、きちんとサイズチェックを行ってからコ
ピー処理が行われたので、バッファを溢れる事無く、正常にコ
ピー処理が行われています。
最後に「copy end」という文字を表示して、このプログラムは
終了です。
printf("copy end\n");
このプログラムをビルド&実行しても、見た目上、「copy end」
という文字が表示されるだけです。
これではつまらないので、コピーする前とコピーした後のスタ
ック領域を画面に表示させるように改造してみましょう。
先ほどのプログラムを以下の様に修正してください。
変更部分にはコメントを付けておきます。
─↓"of1.c"ここから───────────────────
#include <stdio.h>
#include <string.h>
void main(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(a));
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));
}
// ●ここまで追加
}
─↑ここまで──────────────────────
コピー処理を行う前と後でスタック領域の中身を表示させるよ
うに処理を追加しました。
これでとりあえずプログラムは完成です。
ビルド&実行してみてください。
実行結果は以下の様になります。
─↓実行結果──────────────────────
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┘
─↑ここまで──────────────────────
実行結果について説明します。
まず、コピー処理を行なう前にスタック領域が表示されます。
●コピー処理実行前のスタック領域
0063FDDC:10 11 12 13┐
0063FDE0:14 15 16 17│
0063FDE4:18 19 1A 1B│バッファ「b」
0063FDE8:1C 1D 1E 1F│
0063FDEC:FF FF FF FF┘
0063FDF0:00 01 02 03┐バッファ「a」
0063FDF4:04 05 06 07┘
左端の「0063FDxx:」という部分はメモリ上の何番目かを現す
アドレス情報です。
このアドレス情報の値は、環境によって変わるかもしれません。
次に、バッファのコピー処理が行なわれ、「copy end」という
文字が表示されます。
●コピー処理実行
copy end
最後にもう1度、スタック領域が表示されます。
●コピー処理実行後のスタック領域
0063FDDC:10 11 12 13┐
0063FDE0:14 15 16 17│
0063FDE4:18 19 1A 1B│バッファ「b」
0063FDE8:1C 1D 1E 1F│
0063FDEC:FF FF FF FF┘
0063FDF0:10 11 12 13┐バッファ「a」
0063FDF4:14 15 16 17┘
 ̄ ̄ ̄ ̄ ̄ ̄
↑
書き換わっている
コピー処理が行なわれたので、バッファ「a」の中身が書き換
わっています。
さて、ここまでは特にバグの無いプログラムを作成してきまし
た。
次回はこのプログラムにバッファオーバーフローのバグを仕込
み、どのような挙動をするか見てみる予定です。