2022年8月、ホームページを全面リニューアルしました! 情報を分かりやすくお伝えできるサイト作りを目指してまいります。

バッファオーバーフロー攻撃って?!

セキュリティ関係のことを調べるのが好きで、従来より書き記してきましたが、今回はバッファオーバーフロー攻撃ってどういうことか?こんな疑問にスポットを当ててみました。
多分、解らない方々も多いかと思います。早い話が、セキュリティホールを突いて目的のサーバを不能にさせてしまう、とても恐ろしいクラッキングの一種です。ここでは、バッファオーバフローをもう少し掘り下げてどのような攻撃なのかを説明したいと思います。


1.バッファオーバーフロー攻撃とは?

ネットワークサービスデーモンがバッファオーバーフローになったことにより、リモートから勝手にコマンドを実行することができてしまう。というとても最悪な事態に陥ります。

バッファオーバフロー攻撃とは、バグが存在するプログラムに、大量のデータを送信し、そのプログラムを暴走させ、本来そのプログラムに無い”コマンドの実行”と言う機能を無理やり行わせる攻撃です。


2.バッファオーバーフローの仕組み

何故、バッファオーバーフロー攻撃が出来てしまうのか?それを考えてみます。さて、バッファオーバーフロー攻撃の仕組みを理解するためには、C言語について知るがあります。
図1は、C言語で書かれた簡単なプログラムです。C言語は、main()で処理が開始され丸の番号順にプログラムが実行される。ここで注意してほしいのは、sub()と言うサブルーチンに処理がわたっているところです。main()からは、5行目と8行目にある”sub();”と記述されている部分で、処理がsub()関数へ渡っています。sub()関数の中では、一連の処理を行い、109行目にある”return;”命令でmain()のもとの位置(sub()を呼び出した位置)へ処理を戻しています。

1: main()        ① ここから始まる。
2: {
3:    ・
4:    ・
5:   sub();      ② ③へ飛ぶ
6:    ・
7:    ・
8:   sub();      ④ ③へ飛ぶ
9:    ・
10:    ・
11: }



100: sub();       ③ ここに来る。
101: {
102:   int a;
103:   char buf[128];
104:   ・
105:   ・
106:   strcpy(buf,DATA);
107:   ・
108:   ・
109:   return;     ④ ②から来たら②へ戻る。④から来たら④へ戻る
110: }

図1 C言語で書かれた簡単なプログラム

C言語で記述されたプログラムと言えども、実際に実行されるのは、コンパイル及びリンクされたマシン語プログラムです。CPUはメモリに読み込まれたマシン語プログラムを、順次実行していきます。しかし、CPUは単にIP(Instruction Pointer)レジスタ(PC : Program Counterとも呼ばれる)が指すアドレスに格納されている各命令の実行を繰り返しているだけです。CPUが一つのマシン語命令を実行するステップは、以下のサイクルで行われます。

● フェッチ  IPレジスタに格納されているアドレスから、1命令を取り出します。

   ↓

● IP調整   次のサイクルのために、IPレジスタのアドレス値を加算しておきます。

   ↓

● 解析    フェッチで取り出した命令を解析します。

    ↓

● 実行    上で解析した命令を実行します。

サブルーチンへジャンプしたり、サブルーチンから呼び元へ戻る場合は、上記の”実行”のステップで、IPレジスタを書き換える処理が行われるだけで、次に実行すべき個所を強制的に変更しているだけです。
さて、図1の説明に戻りますが、main()関数の中にある2つの”sub()”命令は、いずれもsub()関数の先頭である100行目にジャンプするものです。sub()関数の先頭の命令が格納されているアドレスは、コンパイル及びリンク時に関わるため、固定のアドレスをIPレジスタに書き込む命令が生成されます。しかし、sub()関数内の”return;”命令は、あるときは6行目にジャンプし、またあるときは9行目にジャンプします。同じ命令でプログラム上の別な位置にジャンプすることが可能なのです。
これを実現するために、C言語で作られたプログラムはスタックを利用しています。
スタックとは?
後入れ先出し(FIFO)のキューを実現するための動的メモリ領域のことです。スタックにデータをためることをPUSHと言い、データを本にたとえると本の天辺に積まれる。これは、スタックにデータを格納することを表しています。変わって、山積みの本の中から取り出すには、山の天辺から取り出します。つまり、最後にPUSHしたデータが取り出されるわけで、これをPOPと言います。データ格納をPUSH、データ取出しをPOPと言うわけです。
スタックは、主にデータの一時的な格納に使われています。
C言語では、サブルーチンをコールする場合に、そのサブルーチンからの戻り位置をあらかじめスタックに退避(PUSH)しています。サブルーチン内の処理が終了するかもしくは、return命令が実行された場合は、あらかじめスタックに保存しておいた戻り値を取り出し(POP)、取り出されたアドレスにジャンプすることで元の位置に戻ることができるわけです。
これが実は、スタックはサブルーチンからの戻り値を保存するだけの物ではないのです。関数内で定義された変数(内部変数)も、特に(static等)指定しない限り(つまりautoであるもの)、通常スタック上に確保されるのです。
図1の102行目と103行目は内部変数(auto)の宣言を行っているが、これらの変数はスタック上に確保されます。
スタックの使われ方も簡単に説明しておきましょう。スタックは通常、高い値のアドレスから消費していきます。
つまり、先に確保した領域のアドレスは、次に確保した領域のアドレスよりも高い値のアドレスになります。図1を例にすると、まずmain()関数内で内部変数を確保するためにスタックが消費されます。sub()関数内の109行目の”return;”を実行する時点では、スタックは図2の位置になっている。

00000000~FFFFFFFF迄→スタックポインタ
====================== 00000000番地

----------------------

   sub()の内部変数bufの格納領域(128バイト)

----------------------
----------------------

   sub()の内部変数aの格納領域

----------------------

----------------------

   main()への戻り値アドレス格納

----------------------

====================== FFFFFFFF番地

図2   スタックの内部メモリ配置図

さて、図1の106行目を見てください。ここで行っているのは、変数DATAに格納されているデータを、内部変数bufにストリングコピー関数でコピーしています。このプログラムで、DATAに格納されているデータは、bufサイズ(128バイト)よりも少ないことを前提として作られています。もしここで、DATAに256バイトのデータが格納されていたとしたら、どうなるでしょうか?(考えただけでも恐ろしい!!!)
他の言語の場合、DATAの先頭128バイトまでがコピーされ、それ以降は切り捨てられるでしょうがC言語の場合、その言語の仕様上、256バイト躊躇しないでコピーしてしまいます。つまり、buf領域を超え関係の無い領域までを侵してしまいます。そう!もう解りましたか!?これがバッファオーバーフローです!ですから、C言語を経験した人なら一度ぐらいバッファオーバーフローを作ったことがあるはずです。
(小生も作ったことはあります)
図2のメモリ配置図を見てみると、変数bufの次には変数aが有り、その次には戻り値を格納した領域が有ります。もし、このコピー処理で、戻り値が格納された領域を上書きしてしまった場合、図1の109行目の”return;”実行時に本来戻るべき位置に戻れなくなり、全く違う番地に処理渡り、結果としてハングアップ状態となります。
このような例で、もしプログラムがネットワークをサービスするデーモンプログラムだったとしてDATAがクライアントから送信されたデータを格納している変数だったとしたらどうなるでしょう?このデーモンプログラムは簡単にハングアップさせることができることになります。
このようなプログラムをDoS(Denial of Service)攻撃に対する脆弱性が有るという事が言えます。


3.実際のバッファオーバーフロー攻撃

バッファオーバーフロー攻撃では、さらに前項の説明を応用しています。前項の説明で戻り位置を上書きしてしまう部分の内容は、クライアント(攻撃するほう)からのデータの一部になります。つまり、この部分を巧みに操ることにより、ターゲットになるネットワークデーモンの制御を自由に操作することができるのです。通常、バッファオーバーフロー攻撃をする場合、攻撃側が行いたい処理が記述されたマシン語と、書き換える戻りアドレスが含まれたデータをターゲットに送りつけます。書き換える戻りアドレスは、スタックのアドレスになるようにします。この場合、”return;”実行時にはスタック内のある位置に処理が移り、結果的に、攻撃側が送りつけたマシン語が実行されます。つまり、攻撃側からは好き勝手なことが出来ちゃいます。通常、ネットワークデーモンは管理者権限(rootもしくはadministrator)で動いています。と言うことは、バッファオーバーフロー攻撃で実行されてしまうマシン語の処理も管理者権限で動いてしまうのです。
このように、単にバッファオーバーフローを起こしハングアップさせるだけでなく、任意の処理の実行まで出来てしまうことをバッファオーバーランになるとか言います。
バッファオーバーフローのセキュリティホールが存在する可能性があるのは、C言語で書かれたOS及びアプリの全プログラムです。ですから、かなり深刻な問題なんです。言うまでも無くUnix上で動くプロセスは、ほとんどC言語で書かれています。
まあ、Windowsにも言える事ですが・・・・・。


4.バッファオーバーフロー攻撃を受けたかどうか確かめる

必ずしもバッファオーバーフロー攻撃が行われたかは定かではないが、coreファイルが作成されていたら注意し
たほうがいいと思います。coreファイルの持ち主が誰かは、以下のfileコマンドで確かめられます。また、strings
コマンドで内容を確認したり、デバッガも使用することが出来ます。

# file /core
/core: ************************************


5.バッファオーバーフロー攻撃の対策

バッファオーバーフローが問題なのは、ユーザから与えられたデータの長さをチェックせずに、バッファにコピーしてしまうことです(特に、文字列型データがそれに該当する)。
C言語には、文字列という概念は有りません。文字列を、そのデータが格納されたアドレス(ポインタ)で扱います。そのアドレスから、Nullが出現するまで1つの文字列としています。したがって、strcpy()などはコピー元のアドレスからNull文字が出現するまでをサイズの制限なくコピーできてしまうのです。
このようなことから、

● strcpy()、strcat()、gets()、sprintf()等の関数はできるだけ使わない。
● ユーザから何らかの形で入力されるデータは、入力されたときにサイズチェックを必ず入れるようにする。
● モジュール別のパッチ情報を確認して、パッチを適用する。
● 不要なデーモンは立ち上げない。

このようなことに、気をつけるべきかと考えます。

また、Solaris2.6以降のバージョンには素晴らしいことに、バッファオーバーフロー攻撃の対策ができるように
なっています。この機能は、カーネルのオプションの変更で有効になります。/etc/systemファイルに以下のマ
ラメータを設定して、再起動するだけです。

set noexec_user_stack=1
set noexec_user_stack_log=1

一行目は、Stackセグメントの実行権限をはずす指定で、2行目はsyslogへ出力する設定です。

以上

コメント