バッファーオーバーラン (バッファーオーバーフロー) の脆弱性をついた攻撃に入門する

# 0. 概要
脆弱性の基本的な存在、バッファーオーバーラン。簡単に言うと、メモリにロードされたプログラムを書き換えてしまうといったものである。
本来はバグであるものの、使い方によっては攻撃にも応用可能なため、脆弱性と呼ばれているわけである。今回はポインタの理解も含めて、C言語でバッファーオーバーランを説明する。

# 1. アドレス空間
アドレス空間について理解せずともバッファーオーバーランのプログラムを書くことはできるが、OSが提供するメモリ管理の基本なので概要だけでも掴んでおいた方が良い。
まず大きく分けて、アドレス空間には<b>カーネル空間</b>と<b>ユーザ空間</b>がある。

<b>カーネル空間</b>
カーネル空間は名前の通り、OS起動時にOSの重要な機能が読み込まれるメモリ領域のことである。
なお、Linux等の場合はカーネル空間→ユーザ空間(のプロセス)へ任意のシグナルを送ったりといった特権がある。

<b>ユーザ空間</b>
ユーザ空間は、プロセス(アプリケーション)起動時にそのプロセス情報が展開されるメモリ領域のことである。プロセス毎にページテーブルを持っており、メモリ領域はプロセス間で共有されない。一方、OSの情報は各プロセスで必要であったりするので、それらは共有的に使えるようカーネル空間の仮想アドレスがマッピングされている。

![pt-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95636/f78fbd0a-ae22-1e4f-5ce2-497013589298.png)
https://milestone-of-se.nesuke.com/sv-basic/architecture/user-space-kernel-space/


この説明からプロセスを超えてメモリを操作するのは難しいことや、ましてカーネル空間のメモリを操作することは難しいといったことが分かる。
基本的にメモリ空間に対する攻撃の初手は、攻撃するアドレスの予測を行うことであるが、アドレス空間配置のランダム化（ASLR）というデータ配置を無作為に行う技術も登場しておきり、攻撃は難化していっている。

# 2. バッファーオーバーラン
今回バッファーオーバーランを行う対象は、ユーザ空間に展開されたあるプロセスが入力を受け付けており、
それに対しバッファーオーバーランを起こさせるデータを入力して、データ入力を受け付けていない変数にこちらの意図した値を入力するといったことを試みたい。

## 2.1. 入力受付のプログラム
バッファーオーバーランを行うための素体プログラムを作る。入力を受け付けるプログラムを以下のように作る。

``` c
#include <stdio.h>
int main() {
	int a = 0xFFffFFff;
	char buf[4];
	fgets(buf, 128, stdin);
	return 0;
}
```

`fgets`は入力データを`buf`にコピーする関数で、さらにデータサイズも指定できるセキュアな関数である。
しかし、バッファーオーバーランを起こすため今回は`char buf[4]`で確保したデータサイズより
大きな`128`バイトのデータまでを受け入れられるようにしている。

今回はこの`buf`をオーバーフローさせて`a`の変数を書き換えるということを行う。
仮にゲームであれば、名前入力でオーバーフローを起こさせて、何らかのステータスパラメータ`a`を好きな値にするというストーリーである。

## 2.2. アドレス配置の確認
では、`buf`をどれくらいオーバーフローさせれば`a`を好きな値に書き換えられるのか、アドレスの配置を調べたいと思う。
そこで、以下のようにプログラムを書いて、各変数の先頭アドレスと中身を調べてみる。

``` C
#include <stdio.h>

void dispAddress(char *base) {
	for (int i = 0; i < 20; i++) {
		printf("[%p]: %02X \n", (base+i), *(base + i) & 0x000000FF);
	}
}
int main() {
	int a = 0xFFffFFff;
	char buf[4];

	printf("Buffer Address (hex): %p\n", &buf);
	printf("A Address (hex): %p\n", &a);
	printf("A value (hex): %p\n", a);
	dispAddress(buf);
	printf("\n");
	fgets(buf, 128, stdin);
	printf("%x %s\n", a, buf);
	return 0;
}
```

アドレス番地と実体を1バイト単位で調べたいので、`dispAddress`関数は`char`型を使っている。x86で`int`型を用いると`4byte`刻みになるので、分かりにくい。実行してみると以下のようになるはずである。

なお、相対アドレスは変わらないと思うが、絶対アドレスは実行環境によって変わると思うので、そこは適宜補完してほしい。

```
Buffer Address (hex): 007CFABC
A Address (hex): 007CFAC8
A value (hex): FFFFFFFF
?[007CFABC]: CC
?[007CFABD]: CC
?[007CFABE]: CC
?[007CFABF]: CC
?[007CFAC0]: CC
?[007CFAC1]: CC
?[007CFAC2]: CC
?[007CFAC3]: CC
?[007CFAC4]: CC
?[007CFAC5]: CC
?[007CFAC6]: CC
?[007CFAC7]: CC
?[007CFAC8]: FF
?[007CFAC9]: FF
?[007CFACA]: FF
?[007CFACB]: FF
?[007CFACC]: CC
?[007CFACD]: CC
?[007CFACE]: CC
?[007CFACF]: CC
```

このような結果が出力されたと思う。

`buf`は`007CFABC`アドレスから始まっており、本来なら`4 bytes`分確保されている(`buf[4]`と定義しているから)。

次に`a`は`007CFAC8`アドレスから`4 bytes`分確保されていることが分かる。というのも、`a`の先頭アドレス`007CFAC8`から`007CFACB`アドレスの`4 bytes`までを合わせると初期化した数値と同じになるからである。

`buf`の先頭アドレス`007CFABC`及び`a`の先頭アドレス`007CFAC8`が`12 bytes`離れており、なぜ`5 bytes`以上、無駄にアドレス番地が離れているのかは、専門家ではないため詳しくは分からない。凡そ、プロセッサがデータを取り出しやすい大きさに分割するような<b>アライメント境界</b>だとかの話だと思われる。`5 bytes`以上なのは`char`には終了を示す`00`が最後に自動的に入るためである。

<b>ここで重要なのは`buf`から`a`まで離れている量は`12 bytes`だということである。
すなわち、それくらいの量を書き込めば`a`の領域を侵犯することができる。</b>

イメージでいうと、この様な感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95636/77200e1e-c6a3-c341-0198-de69312f7fab.png)

なお、実際は上下反転して考えるべきである。`a`及び`buf`の変数宣言順序とメモリアドレスの順序が逆転していると思う。
これは変数がスタックされていっているからである。上に積みあがっていると考えればよい。

## 2.3. バッファーオーバーラン
これまでを踏まえて`A`を14個ほど書き込んでみる。
なお、書き込んだ後、メモリ内の値がどのように変化しているか確認するため、再度`dispAddress`関数で表示する。

``` C
#include <stdio.h>

void dispAddress(char *base) {
	for (int i = 0; i < 20; i++) {
		printf("[%p]: %02X \n", (base+i), *(base + i) & 0x000000FF);
	}
}
int main() {
	int a = 0xFFffFFff;
	char buf[4];

	printf("Buffer Address (hex): %p\n", &buf);
	printf("A Address (hex): %p\n", &a);
	printf("A value (hex): %p\n", a);

	dispAddress(buf);

	printf("Character code of a: %p \n", 'A');

	printf("\n");
	fgets(buf, 128, stdin);
	printf("%x %s\n", a, buf);

	dispAddress(buf);
	return 0;
}
```

実行すると以下のような結果が得られたはずである。

```
Buffer Address (hex): 007CFABC
A Address (hex): 007CFAC8
A value (hex): FFFFFFFF
?[007CFABC]: CC
?[007CFABD]: CC
?[007CFABE]: CC
?[007CFABF]: CC
?[007CFAC0]: CC
?[007CFAC1]: CC
?[007CFAC2]: CC
?[007CFAC3]: CC
?[007CFAC4]: CC
?[007CFAC5]: CC
?[007CFAC6]: CC
?[007CFAC7]: CC
?[007CFAC8]: FF
?[007CFAC9]: FF
?[007CFACA]: FF
?[007CFACB]: FF
?[007CFACC]: CC
?[007CFACD]: CC
?[007CFACE]: CC
?[007CFACF]: CC
Character code of a: 00000041

AAAAAAAAAAAAAA
a4141 AAAAAAAAAAAAAA

?[007CFABC]: 41
?[007CFABD]: 41
?[007CFABE]: 41
?[007CFABF]: 41
?[007CFAC0]: 41
?[007CFAC1]: 41
?[007CFAC2]: 41
?[007CFAC3]: 41
?[007CFAC4]: 41
?[007CFAC5]: 41
?[007CFAC6]: 41
?[007CFAC7]: 41
?[007CFAC8]: 41
?[007CFAC9]: 41
?[007CFACA]: 0A
?[007CFACB]: 00
?[007CFACC]: CC
?[007CFACD]: CC
?[007CFACE]: CC
?[007CFACF]: CC
```

まず`A`はASCIIコード(16進数)で`41`である。
14文字打ったため、先頭アドレス`007CFABC`から`14 bytes`分埋まっていることが分かる。
変数`a`のメモリ番地(`007CFAC8`～`007CFACB`)にも侵犯して`41`が書き込まれている。
このため、`FFFFFF`を示していた`a`の整数は`a4141`という値に変化している。

これで変数`a`を直接操作せずとも値を変更できることが確認できた。

しかし1つ疑問が残る。以下の2つである。

```
?[007CFACA]: 0A
?[007CFACB]: 00
```

書き込んでいない余計なものまで書き込まれている。
これは文字の終了コードを示す`0A00`、すなわち`LF (0A) + NULL (00)`がメモリに書き込まれているだけである。