1. HOME
  2. ブログ
  3. CPUのアーキテクチャから徹底解説 バッファオーバーフローで何が起こるか第5回~対策をすりぬける攻撃(前編)

CPUのアーキテクチャから徹底解説 バッファオーバーフローで何が起こるか
第5回~対策をすりぬける攻撃(前編)

4回にわたってバッファオーバーフローについて解説した本連載も、あと2回で終了となります。前回記事では、バッファオーバーフロー脆弱性対策の歴史について解説しました。前回の記事をまだご覧になっていない方は、ぜひこの機会に当連載記事に目を通していただければと思います。

今回はこれらの対策が必ずしも完全ではなく、すり抜けの方法が存在することを、実例を示しながら前編・後編にわけて解説します。前編では実例として用意した脆弱なコードについて解説します。後編では脆弱なコードを攻撃するコードを用意し、実際に攻撃を行います。

なお、これらのコードの実装については「ももいろテクノロジー」様のプログラムコードを参考に致しました。この場を借りて、ブログの執筆者様に厚く御礼申し上げます。

脆弱なコードの実例

すり抜けの実例を示すために、バッファオーバーフロー脆弱性を持つC言語コード「weak.c」を用意します。これ以降、本記事ではこのコードを「脆弱コード」と呼ぶことにします。

1	#include <stdio.h>
2	#include <stdlib.h>
3	#include <string.h>
4	
5	int vuln() {
6	    int buf_size;
7	    char buffer[10];
8	    char line[20];
9	
10	    setlinebuf(stdout);
11	
12	    fgets(buffer, sizeof(buffer), stdin);
13	    buf_size = atoi(buffer);
14	
15	    gets(buffer);
16	    strncpy(line, buffer, buf_size);
17	    printf(line);
18	    puts("\n");
19	
20	    gets(line);
21	    puts(line);
22	
23	    return 0;
24	}
25	
26	int main(int argc, char** argv) {
27	    vuln();
28	}

このコードは図1のように、ユーザが入力した文字を画面に返すだけの、シンプルなコンソールアプリケーションです。このアプリケーションの動きを示すため、図1に示した番号と脆弱コードの対応を以下に示します。

①12行目、13行目のfgets()でユーザ入力をbufferに記憶。またそれを数値として buf_size に記憶する。
②15行目~18行目でユーザ入力をbufferに記憶。それをlineにコピーした上でコンソールに出力。
③20行目、21行目でユーザ入力をlineに記憶。それをコンソールに出力。

以下に実行例を示します。上記の1~3について、それぞれ「21」「ABCDE」「HIJKL」を入力しています。また2と3については入力内容がそのまま出力されています。

図 1 脆弱コードの実行結果

このコードはバッファオーバーフローや、その他の脆弱性を持っています。以下の表に脆弱箇所と内容の一覧を示します。

No.脆弱箇所(行番号)内容
115入力サイズを考慮しない標準Cライブラリ(これ以降はlibcと言います)の関数gets()を使っている。そのためlineのサイズを超えた入力を受け、スタック上の制御情報まで上書きできてしまう。すなわちバッファオーバーフロー脆弱性を持つ。
216libcの関数strncpy()はbuf_sizeの値で示す文字数コピーするが、ユーザが入力した値であるbuf_sizeを、そのままコピー文字数として使用している。そのため次のNo.3の脆弱性を引き起こす可能性がある。
316このlibcの関数strncpy()呼び出しはnot null terminate脆弱性を持っている。ここではstrncpy()にbufferからlineに文字列をコピーさせている。しかしこのstrncpy()関数は、buf_size数以上の文字をコピーするとき、コピー先に文字列の終端記号を付けない。このときコピー先文字列をlibcの関数のputs()やprintf()などで表示させた場合は、コピー先文字列の先にあるデータまで文字として表示してしまう。
417このlibcの関数printf()呼び出しはformat string attack脆弱性を持っている。printf関数はすべての引数がスタックに置かれているものとして動作する。したがってprintf関数は書式文字列の後の引数がない場合であっても、スタックの内容を1ワード単位で引数として使用する。脆弱コードの実装では第一引数にユーザ入力を渡している。そのため、たとえばユーザが”%s”や”%21$08x”などを入力した場合、printf関数はスタックの内容を出力してしまう。
520No.1(15行目) と同様である。バッファオーバーフロー脆弱性を持つ。
表 1 脆弱コードが持つ脆弱性の一覧

コードに対する攻撃

脆弱コードを以下のようにビルドすると、前回記事「バッファオーバーフロー脆弱性対策の歴史」で解説したバッファオーバーフロー対策が施された実行ファイル「a.out」が生成されます。内容の詳細は前回記事「バッファオーバーフロー脆弱性対策の歴史」を参照ください。

gcc -fPIE -fstack-protector weak.c

しかし、ここで施された対策も完全ではありません。以下の表に、施されたバッファオーバーフロー対策を一覧します。また、これらのすり抜け方法をサブセクションで説明します。

No.対策内容
1NXスタック上にプログラムを配置されることへの対策。NX  bit によってスタック上のプログラム実行を抑止する。
2ASLR (+PIE)libcや実行ファイルのプログラムを不正に利用されることを抑止。プログラムのアドレスを実行のたびにランダムにすることで攻撃者はどのアドレスで上書きすればよいかがわからなくなる。このことによりプログラムの不正利用が難しくなる。
3SSPcanaryにより制御情報の書き換えを検知。プログラムは「canaryの値が変われば制御情報も書き換えられた」とし、即時終了する。このことにより制御情報の書き換えによる攻撃が難しくなる。
表 2 脆弱コードに施されたバッファオーバーフロー対策の一覧

 【対策1「NX」のすり抜け方法】

NXはスタックに置かれたコードの実行を禁止しますが、libcや実行ファイルのプログラムの実行は禁止しません。したがって、これらのプログラムを不正に利用すればNXをすり抜けられます。

【対策2「ASLR(+PIE)」のすり抜け方法】

ASLRは図2:①のとおり、libcをメモリのランダムな位置に配置します。PIEも同様に図2:②のとおり、プログラムをメモリのランダムな位置に配置します。しかし図2:③に示すとおり、どの関数がプログラムの先頭からどれだけ離れているかは変わりません。これ以降、libcなどプログラムの先頭アドレスからどれだけ離れているか、ということを「オフセット」といいます。

図 2 ASLR(+PIE)のバッファオーバーフロー対策

オフセットが変わらないことを利用してASLR(+PIE)をすり抜けることができます。たとえばlibcのsystem()関数のアドレスを不正に取得する場合を考えます。以下のようにすることでASLRをすり抜け、目的のアドレスを得られます(注釈:PIEも同じような手順ですり抜けられるため、本記事では割愛します)。

  • 事前にlibcのアセンブリコードを見て、main()関数を呼ぶ箇所と不正に利用したい関数の、両方のオフセットを見る。
  • 図3:④のように、呼び出し元に戻るためのアドレス(リターンアドレス)を特定する。(たとえばformat stringの脆弱性を利用してリターンアドレスを特定する)
  • 図3:⑤のようにリターンアドレスがわかればlibcの先頭アドレスがわかる。
  • 図3:⑥のようにlibcの先頭アドレスがわかればsystem()関数のアドレスがわかる。
図 3 ASLR(+PIE)のすり抜け

【対策3「SSP」のすり抜け方法】

SSPは図4:⑦のようにcanaryの値の書き換えを検知します。しかし実行時のcanaryの値が漏れてしまうと、SSPは無力になります。たとえばnot null terminateなど他の脆弱性の利用よって実行時のcanaryを特定します。そしてcanaryをバッファオーバーフロー攻撃の入力データに含めます。このとき図4:⑧のようにオーバーフローによってスタックのcanaryの値を、先ほど取得したcanaryの値で上書きするように入力データを調整します。こうすることでSSPはバッファオーバーフローによるcanary書き換えを検知できなくなります。

後編について

これまで脆弱コードとその攻撃方法について解説しました。後編では、脆弱コードに対して、バッファオーバーフローの対策をすべてすり抜ける攻撃を行いたいと思います。また、すり抜けの結果として外部コマンド「/bin/sh」を実行させます。プログラムを見る限り、脆弱コードは「/bin/sh」コマンドを実行できません。しかし攻撃が成功すれば、脆弱コードは「/bin/sh」コマンドを実行してしまいます。


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

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

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

関連記事

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