バッファオーバーフロー脆弱性は思ったより簡単に発生する


元ネタ

https://minegishirei.hatenablog.com/entry/2024/10/01/214408



## 結論

入力値チェックは大事だね。

古事記にもそう書いてある。


## バッファオーバーフロー脆弱性

バッファーオーバーフロー脆弱性は、コンピューターの黎明期から今に至るまで見かけるものです。
InternetExploreのVML脆弱性についても、バッファオーバーフローが用いられていました。


## C言語のバッファオーバーフロー

C言語は高水準プログラミング(?)と呼ばれますが、データの整合性確保についてはプログラマが責任を持ちます。
この責任をコンパイラに任せてしまうと、全ての変数に対して整合性チェックが行われ、実行プログラムの処理速度が低下してしまうのです。

**C言語は簡潔さと速度を信条としますが、データの整合性は捨てています。**

プログラマがコントロールできる部分を増やし、実行効率を高めてはいますがその代償として、プログラマのうっかりミスによってバッファオーバーフローやメモリりーくが発生してしまうのです。

> 8バイトしか割り当てられない領域に、10バイトのデータを割り当てようとすると、プログラムがクラッシュしてしまうこともあるのです。

具体的にコードを見てみます。


## バッファオーバーフロー サンプルコード

### 環境構築

> **Note**
> 
> この章ではバッファオーバーフローを実演します。
> Dockerおよびgitをインストールしている方は、次のコードでC言語の実行環境を整えることができ、ソースコードも同封されています。

```sh
git clone https://github.com/minegishirei/hacking_lab
cd hacking_lab
cd c_language
./run.sh
```

bashが立ち上がった後は、次のコマンドでビルドと実行をしてみてください。

```sh
cd buffer_overflow
gcc -g -O0 main.c
./a.out aaa
```

### サンプルコード

例えば、次のようなコードを実行してみる。

```c
# include <stdio.h>
# include <string.h>

int main(int argc, char *argv[]){
    int value = 5;
    char buffer_one[8], buffer_two[8];

    strcpy(buffer_one, "one");
    strcpy(buffer_two, "one");
    
    printf("buffer_two ポインター : %p \n", buffer_two);
    printf("buffer_two 値 : %s \n", buffer_two);
    printf("buffer_one ポインター : %p \n", buffer_one);
    printf("buffer_one 値 : %s \n", buffer_one);
    printf("value ポインター : %p \n", value);
    printf("value 値 : %d \n", value);
    /*最初の引数をbuffer_twoにコピーする*/
    strcpy(buffer_two, argv[1]);
    
    printf("buffer_two ポインター : %p \n", buffer_two);
    printf("buffer_two 値 : %s \n", buffer_two);
    printf("buffer_one ポインター : %p \n", buffer_one);
    printf("buffer_one 値 : %s \n", buffer_one);
    printf("value ポインター : %p \n", value);
    printf("value 値 : %d \n", value);
}
```

後はこのコードを保存し、 `gcc -g -O0 main.c`を実行してみてください。
（Gitリポジトリの中にはすでに同封済み。）

`a.out`ファイルが作成されたら完了です。


### 実験

#### メモリ内部に収める

まずは通常通り、 `aaaa`と入力してみる

```
./a.out aaaa
```

結果、特に何も起きなかった。

```console
# ./a.out aaaa
buffer_two ポインター : 0xffffca961a38 
buffer_two 値 : one 
buffer_one ポインター : 0xffffca961a40 
buffer_one 値 : one 
value ポインター : 0x5 
value 値 : 5 
buffer_two ポインター : 0xffffca961a38 
buffer_two 値 : aaaa 
buffer_one ポインター : 0xffffca961a40 
buffer_one 値 : one 
value ポインター : 0x5 
value 値 : 5 
```

#### オーバーフローさせる

次に、配列の確保分を超えた `aaaaaaaaa`と入力してみる

```
./a.out aaaaaaaaa
```

**結果、`buffer_two`から溢れた一文字の`a`が、`buffer_one`に格納されてしまった。オーバーフロー発生！**

```console
root@275f7b1d6b9e:/code/buffer_overflow# ./a.out aaaaaaaaa
buffer_two ポインター : 0xffffdc6761e8 
buffer_two 値 : one 
buffer_one ポインター : 0xffffdc6761f0 
buffer_one 値 : one 
value ポインター : 0x5 
value 値 : 5 
buffer_two ポインター : 0xffffdc6761e8 
buffer_two 値 : aaaaaaaaa 
buffer_one ポインター : 0xffffdc6761f0 
buffer_one 値 : a 
value ポインター : 0x5 
value 値 : 5 
```

#### さらにオーバーフローさせる

`buffer_two`をこえ、`buffer_one`をこえ、16文字以上の引数を入れた場合... `aaaaaaaaaaaaaaaaaaaaa`

**オーバーフローする文字に制限はないことがわかった**

```console
root@275f7b1d6b9e:/code/buffer_overflow# ./a.out aaaaaaaaaaaaaaaaaaaaa
buffer_two ポインター : 0xffffd238f2e8 
buffer_two 値 : one 
buffer_one ポインター : 0xffffd238f2f0 
buffer_one 値 : one 
value ポインター : 0x5 
value 値 : 5 
buffer_two ポインター : 0xffffd238f2e8 
buffer_two 値 : aaaaaaaaaaaaaaaaaaaaa 
buffer_one ポインター : 0xffffd238f2f0 
buffer_one 値 : aaaaaaaaaaaaa 
value ポインター : 0x61 
value 値 : 97
```


## 予防方法

buffer_two のサイズは8バイトですが、argv[1] の長さがこれを超える場合、メモリが破壊される可能性があります。
コピーする際には、**strncpy** などの安全な関数を使ってバッファオーバーフローを防ぐべきです。

```c
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]){
    int value = 5;
    char buffer_one[8], buffer_two[8];

    strcpy(buffer_one, "one");
    strcpy(buffer_two, "one");

    printf("buffer_two ポインター : %p , 値 %s\n", buffer_two, buffer_two);
    printf("buffer_one ポインター : %p , 値 %s\n", buffer_one, buffer_one);
    printf("value      ポインター : %p , 値 %d\n", &value, value);
    
    if (argc > 1) {
        strncpy(buffer_two, argv[1], sizeof(buffer_two) - 1);
        buffer_two[sizeof(buffer_two) - 1] = '\0'; // ヌル終端
    }

    printf("buffer_two ポインター : %p , 値 %s\n", buffer_two, buffer_two);
    printf("buffer_one ポインター : %p , 値 %s\n", buffer_one, buffer_one);
    printf("value      ポインター : %p , 値 %d\n", &value, value);

    return 0;
}
```

## まとめ

入力値チェックは大事だね。

古事記にもそう書いてある。


## 参考

<img src="https://www.oreilly.co.jp/books/images/picture_large978-4-87311-514-6.jpeg">

Hacking：美しき策謀 第2版
―脆弱性攻撃の理論と実際

Jon Erickson　著、村上 雅章　訳

https://www.oreilly.co.jp/books/9784873115146/