CPUのアーキテクチャから徹底解説
バッファオーバーフローで何が起こるか
第1回プログラムが動くしくみ
セキュリティーニュースなどで「バッファオーバーフロー」というソフトウェアの脆弱性を聞いたことはないでしょうか。このバッファオーバーフローは、境界外への書き込みという脆弱性を引き起こします。この境界外への書き込みは Common Weakness Enumeration (CWE™) の「2022年、最も恐ろしいソフトウェアの弱点トップ25」によれば、ナンバー1の脆弱性として挙げられています。
この脆弱性を悪用されると、情報漏えいやランサムウェアによる脅迫など、さまざまな被害を受ける恐れがあります。
一方で、この脆弱性がどのようにして悪用されるのか、なぜ深刻な被害につながるのかは、あまり知られていないのではないでしょうか。そこで、これから複数回にわけてバッファオーバーフロー脆弱性が悪用されるしくみと影響について解説します。
このしくみを理解するためにはプログラムの動くしくみを知ることが重要です(注釈:プログラムとは、ソースコードを指す場合と実行可能ファイルを指す場合があります。本記事では後者を説明します)。 まず1回目となる本記事では、プログラムが動くしくみについて解説します。なお、これから示す図やプログラミング言語、コンパイラ、CPUは C言語、GNU Compiler、ARM Cortex-A シリーズ CPU(32bitモード) を想定します。
1.プログラムとは?
プログラムはCPUが実行するものです。その実体はCPUへの命令と実行に必要なデータの集まりであり、ソースコードをコンパイルとリンクによって変換して作ります(注釈:本来はコンパイラやリンカーを使ってソースコードをプログラムに変換します。これらはビルドツールの機能であるため、本書では詳細の説明を割愛します)。
図1に、ソースコードをプログラムに変換するイメージを示します。なお、本来プログラムはCPUが読める2進数で表した機械語ですが、これでは人間には分かりませんので、アセンブリコードで示します。
プログラムの命令にはアドレスが割り振られます。アドレスとはプログラム実行時のメモリ空間での位置を表すものです(注釈:本来はリンカー、ローダーがプログラムのアドレス決定やメモリ空間への配置を行います。これらはビルドツールやOSの機能であるため、本書では詳細の説明を割愛します)(注釈:アドレスは「番地」という言い方もありますが、本記事ではアドレスで統一します)。
2.プログラムの制御情報
プログラムを実行するためには、実行する命令のアドレスや分岐元のアドレスなどの制御情報が必要となります。これらの制御情報は以下の2種類の場所に記憶されます。
2-1.レジスタ
レジスタとはCPU内にある記憶場所です。実行する命令のアドレスや後述するスタックの位置などの情報を記憶します。 代表的なものを表1に挙げます。
名 称 | 略称 | 用 途 |
プログラムカウンタ (Program Counter) | pc | 実行する命令のアドレスを記憶する。 |
リンクレジスタ (Link Register) | lr | (後述の)分岐命令を実行する際に、分岐元のアドレスを記憶する。 |
スタックポインタ (Stack Pointer) | sp | スタックの先頭のアドレスを記憶する。 |
フレームポインタ (Frame Pointer) | fp | 関数が確保したスタックフレームの底のアドレスを記憶する。スタックフレームとはスタック内の領域であり、関数が使用する引数やローカル変数を記憶する領域のこと。 |
また以降は個別のレジスタを「略称+レジスタ」(例:pcレジスタ)と述べます。
2.2スタック
スタックとはプログラムが一時的に使う情報を記憶するメモリ空間にある記憶場所です。関数内のローカル変数の内容や、(サブルーチンにおける)リターンアドレス、レジスタの値などを記憶します。スタックは Last In, First Out (LIFO)という、後から記憶した情報を先に取り出すという特徴があります。従って、スタックは先頭の位置が重要になるので、これを専用のspレジスタに記憶します。
図2はCPUがスタックへ情報の追加と取り出しを行うイメージです。情報を追加する場合、CPUはspレジスタの値(0xBEC3CFF4)を1つ上のアドレス(0xBEC3CFF0)に変えます(図2①)。次にspレジスタが示すアドレスに情報データ(0xFFFFFF90)を追加します(図2②)。情報を取り出すときは、CPUはspレジスタが示すアドレス(0xBEC3CFF0)から情報(0xBEC3CFF0)を取り出します(図2③)。情報を取り出した後、spレジスタの値を1つ下のアドレス(0xBEC3CFF4)に変えます(図2④)。
3.プログラムの実行
プログラム開始時のpcレジスタの内容はプログラムのエントリーポイントを示しています(エントリーポイントはプログラムの先頭であるとイメージしてください。エントリーポイントについての説明は、実行(可能)ファイルやELFフォーマットの説明になるため、本記事では省略します)。
図3はCPUのプログラム実行イメージです。CPUはpcレジスタの示すアドレス(0x00100004)の命令2を実行します(図3①)。次にpcレジスタを次のアドレス(0x00100008)に進めます(図3②)。そして次のアドレスの命令3を実行します(図3③)。CPUはこれらの繰り返しによってプログラムを上から順に実行します。
上記のとおりCPUは命令を上から順に実行します。一方で、命令には実行するアドレスを変える「分岐命令」というものがあります。分岐命令は、たとえばC言語の関数呼び出しに利用されます(注釈:関数については後述します)。
3-1.分岐命令を実行
CPUは分岐命令の実行において、分岐先のアドレスをpcレジスタにセットします。図4は分岐命令を実行する直前と直後のイメージです。まずCPUは分岐命令を実行します(図4①)。CPUは分岐命令を実行するとpcレジスタの内容を分岐命令で示されたアドレスに変えます(図4②)。結果としてCPUが次に実行する命令は分岐先のアドレスになります(図4③)。
著者
株式会社ATTC(エーティーティーシー) 尾關晃充
元々はWebアプリケーションのサーバサイドエンジニアだった。
ATTC入社後はセキュリティ製品開発に携わっている。
現在は「ATTC Control Flow Integrity」の開発担当を務めセキュリティや
脆弱性対策に取り組んでいる。
「CPUのアーキテクチャから徹底解説 バッファオーバーフローで何が起こる」かの最新話は、メールマガジンにてもご案内致しています。是非JAPANSecuritySummit Updateのメールマガジンにご登録ください。
メールマガジンの登録はこちらからお願いします。