1. HOME
  2. ブログ
  3. CPUのアーキテクチャから徹底解説 バッファオーバーフローで何が起こるか 第3回 バッファオーバーフローでなにが起こるか

CPUのアーキテクチャから徹底解説 バッファオーバーフローで何が起こるか
第3回 バッファオーバーフローでなにが起こるか

バッファオーバーフローのしくみ

これまでの記事「プログラムが動くしくみ」や「関数利用時のスタック(アドレス)の動きを詳しく追う」では、バッファオーバーフローの前提知識として、プログラムがどう動くのかを解説しました。記事をまだ読んでいない場合は、ぜひ読んでいただければと思います。今回はバッファオーバーフローのしくみについて解説します。なお今回の記事においてもプログラミング言語とCPUは、 C言語とARM Cortex-A シリーズ CPU(32bitモード) を想定します。

そもそもバッファとは何か?

コンピュータ上で動くプログラムにおいて、一時的な記憶場所のことを「バッファ」と言います。プログラムはスタックやその他のメモリ領域をバッファとして使用します(注釈:その他とは「ヒープ」やプログラムの「データセグメント」などが該当しますが、これらのオーバーフローはスタックのオーバーフローとしくみが似ているため、本記事では説明を割愛します)。以下に、スタックがバッファとして利用される場合のイメージ図を示します。

前回の記事でも触れたとおり、スタックには関数の呼び出し元アドレスやスタックフレームの底といった、プログラムの制御情報が記憶されます(図1:①)。またプログラムは関数のローカル変数の値を、バッファとして利用するスタックに記憶します(図1:②)。(注釈:バッファには引数の値が記憶されることもありますが、説明を簡易にするため本記事ではローカル変数についてのみ述べます)。

図 1 スタックをバッファとして利用する

バッファオーバーフローをコップと水でたとえると

バッファオーバーフローはコップと水の関係を想像すると理解しやすくなります。バッファをコップ、データを水と考えると、バッファオーバーフローとはコップから水が溢れるようなものです。たとえば、8バイトの大きさのローカル変数「characters」に6文字「buffer」をセットした場合とは(図2:②および図2:③)、100mlのコップに80mlの水を入れたようなものです(図2:①)(注釈:図のソースコードはNULL終端を含む、ASCIIコードの文字列情報を想定しています)。またコップには100mlまでの水を入れることができますし、バッファには制御文字を含めて8文字までの情報を記憶することができます。

図 2 コップの大きさとバッファの大きさ

では、100mlを超える水をコップに入れるとどうなるでしょうか。当然の話ですが、水はコップから溢れ出します(注釈:表面張力などの物理現象は省略します)。これと同じように、8文字を超える情報をバッファに記憶した場合はどうなるでしょうか。コップの水と同じように、情報がバッファから溢れます。このことを「バッファオーバーフロー」と言います。

バッファオーバーフローが起こるしくみ

バッファオーバーフローとはコップから水が溢れるように、バッファからデータが溢れることを言います。では実際のところ、プログラムはどのようにしてバッファオーバーフローを起こすのでしょうか。これからバッファオーバーフローが起こるしくみを、C言語のサンプルコードを元に説明します。なおサンプルコードを実行するためにはプログラムへの変換が必要ですが、説明を簡潔にするため変換については省略します。

図 3 サンプルコード

このサンプルコードは、コンソールアプリケーションとしての動作を想定しています。ユーザーのキーボード入力(注釈:より正確には標準入力です)の内容をそのままコンソールに表示するというシンプルな仕様です。

図 4 サンプルコード実行イメージ

サンプルコードはキーボード入力のバッファとして8文字分の characters 変数を宣言しています(図4:①)(注釈:character変数には「制御文字を含む」8文字をセットできますが、本記事では説明を簡潔にするため、制御文字の存在を省略します)。ユーザーのキーボード入力はfgets()関数で受けます(図4:②)

実はサンプルコードはfgets()の使い方に問題があり、80文字の入力を受けつけてしまいます(図4:③)。そのためこのコードを実行すると、ユーザーが8文字を超える文字を入力した時に、最大80文字までバッファの大きさを超える文字情報をバッファにセットしようとします。このときにバッファオーバーフローが発生します。 このサンプルコードをプログラムに変換したときの、プログラムの実行イメージは下図になります。まずプログラムは実行時にcharacters変数の値を記憶するための、8文字分(8バイト)のバッファをスタックに確保します(図5:①)。そののちプログラムは(fgets()関数の仕様により)ユーザー入力を待ちます。このときユーザーが “buffer” という文字を入力すると(図5:②)、fgets()関数は先ほど確保したバッファに “buffer” という文字情報をセットします(図5:③)。

図 5 サンプルコード(プログラム)の通常実行イメージ

バッファオーバーフローが起こる様子

次は実際にバッファオーバーフローを起こしてみます。図5のときと同じですが、まずプログラムは実行時にcharacters変数の値を記憶するための、8文字分(8バイト)のバッファをスタックに確保します(図6:①)。このあとプログラムはユーザー入力を待ちますので、このとき16文字 ”buffer over flow” をプログラムに入力してみます(図6:②)。するとfgets()関数は入力された文字情報 “buffer over flow” をバッファに書き込みます(図6:③)。この文字情報の大きさはバッファの大きさを超えています。そのためバッファの範囲から文字情報が溢れます(図6:④)。まさにこのときバッファオーバーフローが発生しました。溢れた文字情報は、そのままバッファの範囲を超え、既にセットされていた値を上書きします。

図 6 サンプルコードのバッファオーバーフロー発生メージ

なお今回の例では、バッファオーバーフローによってバッファに隣接している制御情報まで文字情報に書き換わっています(図6:④)。今回のプログラム実行はバッファオーバーフローによって関数の呼び出し元の値が、プログラムのアドレスではない値に書き換わりました。そのためプログラムはサンプルコードの最後において、関数から戻る時に存在しないアドレスに戻ろうとして異常終了します。

図 7 バッファオーバーフローによるサンプルコードの異常終了

この異常終了の動きは、意図的にバッファオーバーフローを発生させて、制御情報を書き換えることにより、不正なコードを実行させることが論理的に可能であることを示しています。次回以降の記事で、このような場合の詳細を解説します。

おわりに

今回の記事では、バッファオーバーフローが起こるしくみについて以下3点を説明しました。

  • バッファオーバーフローはコップの水が溢れるようなもの
  • 具体的にはバッファからデータが溢れること
  • バッファオーバーフローによってプログラムの制御情報まで書き換わることがある

特に制御情報が書き換わると、プログラムが異常終了したり、予期しない動作をしたりします。このことはセキュリティ上の脆弱性につながるため、いままでさまざまな対策が施されてきました。次回はこの脆弱性への対策の歴史について説明いたします。


著者
株式会社ATTC(エーティーティーシー) 尾關晃充

元々はWebアプリケーションのサーバサイドエンジニアだった。
ATTC入社後はセキュリティ製品開発に携わっている。
現在は「ATTC Control Flow Integrity」の開発担当を務めセキュリティや
脆弱性対策に取り組んでいる。


CPUのアーキテクチャから徹底解説 バッファオーバーフローで何が起こるか」の最新話は、メールマガジンにてもご案内致しています。是非JAPANSecuritySummit Updateのメールマガジンにご登録ください。
メールマガジンの登録はこちらからお願いします。

関連記事

サイバーセキュリティの課題をテーマ別に紹介中!!