分かりやす〜い
コンピュータ技術情報

TOPに戻る
▼Processor
バッファオーバーフロー
┣ 異常な動作
┣ 本当に怖い事
┣ プログラムを作る
┣ バッファオーバーフロ
┃ ーを発生させる

┣ スタック領域の構成
┣ 何故強制終了されるの
┃ か?

┣ 強制終了されないよう
┃ に作り変える

┣ 任意のコードを実行す
┃ る

┣ of1.cとof2.cとの違い
┣ 例外
┣ 例外ハンドラの行う事
┣ of3.cを作る
┣ of1.cとof3.cとの違い
┣ of3.exeの例外ハンド
┃ ラ

┣ of3.exeを強制終了す
┃ るのは?

┣ 例外ハンドラの特徴
┣ Blue Screen
┣ BufferOverFlow応用
┃ プログラム

┗ 機械語の説明

Copyright(C) 2001-2002.ugpop. All rights reserved.




■デジタル用語辞典:
■ プログラムを作る ■

前回はバッファオーバーフローのバグと、他のバグとの違いに
ついて説明しました。

バッファオーバーフローは「同じ領域内」で発生するバグの為、
プロセッサの保護機能が動作してくれない事を説明し、そして
これにより、「エラーが発生しても処理を続行してしまう。」
という問題を含んでいる事も説明しました。

今回は、バッファオーバーフローのプログラムを実際に作って
みようと思います。


まず最初は正常なプログラムから作ってみましょう。

新しく"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」の中身が書き換
わっています。

さて、ここまでは特にバグの無いプログラムを作成してきまし
た。

次回はこのプログラムにバッファオーバーフローのバグを仕込
み、どのような挙動をするか見てみる予定です。


▲このページの上へ

▲このページの上へ

▲このページの上へ

▲このページの上へ

▲このページの上へ

←前に戻る    ▲このページの上へ    続きを読む→