😎 クラッキング ハッキング CTF 学習サイト
https://minegishirei.hatenablog.com/entry/2024/08/17/110233
結論
入力値チェックは大事だね。
古事記にもそう書いてある。
バッファオーバーフロー脆弱性
バッファーオーバーフロー脆弱性は、コンピューターの黎明期から今に至るまで見かけるものです。 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
結果、特に何も起きなかった。
root@275f7b1d6b9e:/code/buffer_overflow# ./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
オーバーフローする文字に制限はないことがわかった
./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 著、村上 雅章 訳
https://www.oreilly.co.jp/books/9784873115146/
page:https://minegishirei.hatenablog.com/entry/2024/10/01/214408