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

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.




■デジタル用語辞典:
■ 任意のコードを実行する ■

前回はバッファオーバーフローが発生した後に、インテルプロ
セッサにより強制終了されてしまわないように、プログラムを
改造してみました。
前回のプログラムをもう1度見てみましょう。


─↓"of2.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);
}
// ●↑ここまで追加
─↑ここまで──────────────────────

このプログラムを実行した結果は以下のようになります。

─↓ここから──────────────────────

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」という関数がメモリ上のどこにあるのか
も分かりました。

後はこれを「呼出し元」の部分に設定してあげれば、インテル
プロセッサが怒り出す事は無くなるはずです。


それでは、最後にもう1箇所だけ変更してもらいます。
先ほどのプログラムのバッファ「b」の部分を変更します。
以下に変更後の物を示します。


  unsigned char b[]= { 0x10, 0x11, 0x12, 0x13,
             0x14, 0x15, 0x16, 0x17,
             0x18, 0x19, 0x1a, 0x1b,
             0xd9, 0x20, 0x40, 0x00,// ←●変更した
             0xff, 0xff, 0xff, 0xff,
          };


変更した部分をよく見て頂きたいのですが、先ほどの「sub」
関数のアドレス情報と同じ値に書き換えてあります。

●「sub」関数のアドレス情報

&sub:004020D9


●バッファ「b」変更部分抜粋

0xd9, 0x20, 0x40, 0x00,// ←●変更した


なんだか微妙に違うんじゃない?と思われる方もいらっしゃる
かもしれません。
しかしこれは、少し「表現方法」が異なるだけです。

以下に、バッファ「b」がメモリに格納されているイメージを
示します。


●バッファ「b」変更部分抜粋

0xd9, 0x20, 0x40, 0x00,// ←●変更した

     メモリ
    ┌────┐
    │ d9 │
    │ 20 │
    │ 40 │
    │ 00 │
    └────┘
    7     0


このメモリの幅は8ビットしかありません。

これを32ビットの幅で表現し直してみます。
以下をご覧ください。

     メモリ
    ┌────┐
    │ d9 ├─────────────────┐
    │ 20 ├────────────┐    │
    │ 40 ├───────┐    │    │
    │ 00 ├──┐    │    │    │
    └────┘  │    │    │    │
    7     0  ↓    ↓    ↓    ↓
          ┌────┬────┬────┬────┐
          │ 00 │ 40 │ 20 │ d9 │
          └────┴────┴────┴────┘
          31                    0


32ビットの幅で表現し直すと「004020d9」となりました。
ただ単に、メモリの上の方にあるデータが右端(ビット数の少
ない方)に来ただけです。                ̄
 ̄ ̄ ̄
この様な表現方法の事を「リトルエンディアン」といいます。
インテル互換プロセッサは全て「リトルエンディアン」の表現
方法となります。

尚、ここでは「表現方法」という言い方をしていますが、例え
ばこれがメモリからレジスタに移動する命令の場合、同じよう
な動作をする、という事も覚えておくようにしましょう。


●メモリからレジスタへ移動する時

     メモリ
    ┌────┐
    │ d9 ├─────────────────┐
    │ 20 ├────────────┐    │
    │ 40 ├───────┐    │    │
    │ 00 ├──┐    │    │    │
    └────┘  │    │    │    │
    7     0  ↓    ↓    ↓    ↓
          ┌────┬────┬────┬────┐
          │ 00 │ 40 │ 20 │ d9 │
          └────┴────┴────┴────┘
          31      レジスタ          0


●レジスタからメモリへ移動する時

     メモリ
    ┌────┐
    │ d9 │←────────────────┐
    │ 20 │←───────────┐    │
    │ 40 │←──────┐    │    │
    │ 00 │←─┐    │    │    │
    └────┘  │    │    │    │
    7     0  │    │    │    │
          ┌─┴──┬─┴──┬─┴──┬─┴──┐
          │ 00 │ 40 │ 20 │ d9 │
          └────┴────┴────┴────┘
          31      レジスタ          0


何故このような表現方法や動作を細かく説明するのかと言うと、
これとは全く異なる動作をするプロセッサも存在するからです。

インテル互換「以外」のプロセッサの場合、これとは逆の動作
をするのです。

以下にインテル互換「以外」のプロセッサの場合の動作イメー
ジを示します。


●メモリからレジスタへ移動する時

     メモリ
    ┌────┐
    │ d9 ├──┐
    │ 20 ├──┼────┐
    │ 40 ├──┼────┼────┐
    │ 00 ├──┼────┼────┼────┐
    └────┘  │    │    │    │
    7     0  ↓    ↓    ↓    ↓
          ┌────┬────┬────┬────┐
          │ d9 │ 20 │ 40 │ 00 │
          └────┴────┴────┴────┘
          31      レジスタ          0


●レジスタからメモリへ移動する時

     メモリ
    ┌────┐
    │ d9 │←─┐
    │ 20 │←─┼────┐
    │ 40 │←─┼────┼────┐
    │ 00 │←─┼────┼────┼────┐
    └────┘  │    │    │    │
    7     0  │    │    │    │
          ┌─┴──┬─┴──┬─┴──┬─┴──┐
          │ d9 │ 20 │ 40 │ 00 │
          └────┴────┴────┴────┘
          31      レジスタ          0


先ほどのイメージと比べてみるとすぐに分かりますが、8ビッ
トの幅のデータを32ビットで表現しなおした時に、インテル
互換とそうでないプロセッサではデータの並び順が逆になって
います。

インテル互換以外のプロセッサでは、メモリの上の方にあるデ
ータが左端(ビット数の大きい方)に来ています。
            ̄ ̄ ̄ ̄
この様な表現方法の事を「ビッグエンディアン」といいます。
インテル互換以外のプロセッサは全て「ビッグエンディアン」
となります。(ただし、「ビッグエンディアン」のプロセッサ
の中には、動作モードを変更する事により、「リトルエンディ
アン」で動作する事が可能な物もあります。)


さて、ソースの変更が終わったらビルド&実行してみましょう。
最初に示したCソースコードでは、明示的に「sub」という関
数を「呼出していない」事に注意しておいてください。

それでは実行させてみてください。


以下に実行結果を示します。

─↓ここから──────────────────────

0063FDDC:10 11 12 13
0063FDE0:14 15 16 17
0063FDE4:18 19 1A 1B
0063FDE8:d9 20 40 00
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:d9 20 40 00
0063FDEC:FF FF FF FF
0063FDF0:10 11 12 13
0063FDF4:14 15 16 17
&sub:004020D9
Overflow call!!

─↑ここまで──────────────────────


今度は強制終了されませんでしたよね?

そして「sub」関数が実行され、「Overflow call!!」という文
字が表示されました。

Cソースの中から明示的に呼出す事は行っていませんが、バッ
ファオーバーフローにより「呼出し元」の情報を意図した値に
書き換えた為、「sub」関数が呼出されているのです。

以下にこのプログラムの動作イメージを示します。


┌──────────────────────────┐
│          OF2. EXE         │
│                          │
└───┬──────────────────────┘
    │                       スタック領域
    │call命令               格納 ┌──────┐
    ├─────────────────────→│ 呼出し元 │
    │    制御を移行┌────────┐   └──────┘
    ├────────→│ main関数 │
    │         │        │
    │         └───┬────┘
    │             │
               ┌──┴──┐
               │コピー処理│
               └──┬──┘
                  │×バッファオーバーフロー発生
                  │         スタック領域
                  │        ┌──────┐
                  ├───────→│sub 関数の │
                  │        │    場所│
                  │        └──────┘
                  │処理終了     スタック領域
                  │どこへ戻るか参照┌──────┐
                  │←───────┤sub 関数の │
                  │        │    場所│
                  │        └──────┘
                  │  ┌───────┐
                  └─→│ sub 関数  │
                     │ 実行開始  │
                     └───────┘


バッファオーバーフローが発生した事により、「呼出し元」の
情報に「sub関数の場所」が書き込まれました。

そして、インテルプロセッサはメイン関数の処理が終了する時
に、どこに制御を移行させるのかを参照します。

「呼出し元」の情報はこの時には「sub関数の場所」に書き換
えられてしまっているので、結果としてsub関数に制御が移行
される事になります。


以上、今まで見てきたように、バッファオーバーフローのバグ
を含んだプログラムを利用すると、制御を「意図した場所へ」
移行させる事が出来るようになります。

よくセキュリティ情報などを見ていると、「バッファオーバー
フローにより任意のコードが実行される。」という表現を見か
ける事があります。

つまりバッファオーバーフローのバグがある=任意のコードが
実行されてしまう、という事です。


特にプログラマーの方は、常にバッファが溢れてしまうことは
ないか、また、予想外に大きなデータに対して正しく対処でき
るようにプログラムが作られているか、などを慎重に検討しな
がらプログラムを作成する必要があるのです。

 バッファオーバーフローのバグがある

 =任意のコードが実行される

 =マシンが乗っ取られる

 =そのマシンが会社の基幹システムだったら?

・・・その先はどうなってしまうのでしょうか?



と、言うような感じで、一般に売られている書籍や雑誌などで
は終了してしまう事が多いです。

ですので、今回までの情報であれば、実は知っている方は結構
いらっしゃったのではないでしょうか?

先ほど、「バッファオーバーフローのバグがある=任意のコー
ドが実行されてしまう」と書きました。

この言葉自体に間違いはありません。

しかし、今までの説明を見ただけで本当にこの事を理解出来た
事になるのでしょうか?

例えば、今まで「sub」という関数(任意のコード)を実行さ
せる為にプログラムにさまざまな変更を加えてきました。

プログラムを変更する事により、「sub」という関数がどこに
あるのかを調べ、その情報を基にまたプログラムに変更を加え
て、やっと"Overflow call!!"の文字を表示させる事に成功し
ました。

つまり、任意のコードを実行させる為にプログラムを何回か変
更したり、実行したりして、やっと目的を達成する事が出来た
というわけです。

世の中にはバッファオーバーフローの脆弱性を突いたコンピュ
ータウィルスが多数存在します。

これらのコンピュータウィルスはソースコードを変更したり、
任意のコードがどこにあるのか調べたりという事を行っている
のでしょうか?

そんな事はありません。

いきなりコンピュータをのっとってしまうのです。
(第一、ソースコードがどこにあるというのでしょうか。)

実はこの講座には、もう少し続きがあります。

今までの説明で、バッファオーバーフローの説明は大体終わっ
た事になります。

しかし、コンピュータウィルス等はバッファオーバーフローの
脆弱性をどのように突いて任意のコードを実行してしまうのか、
という点については全く説明不足です。

次回からは、コンピュータウィルスがどのようにコンピュータ
をのっとってしまうのか、Windowsに感染するコンピュ
ータウィルスを例に説明を行います。


▲このページの上へ

▲このページの上へ

▲このページの上へ

▲このページの上へ

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

▲このページの上へ