1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-10-01

元ネタ

結論

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

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

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

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

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

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

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

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

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

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

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

環境構築

Note

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

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

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

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

サンプルコード

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

# 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

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

# ./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に格納されてしまった。オーバーフロー発生!

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

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

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 などの安全な関数を使ってバッファオーバーフローを防ぐべきです。

#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;
}

まとめ

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

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

参考

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

Jon Erickson 著、村上 雅章 訳

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?