バッファオーバーフローによる任意コードの実行を試したことが無かったので、簡単な例で試してみました。
バッファオーバーフローの実行
下記コードを基に、バッファオーバーフローによってexploitFunc
を実行する方法について考察します。(コードの参考元)
#include <stdio.h>
// 攻撃コード
void exploitFunc(){
printf("exploit!\n");
}
// バッファオーバーフローを起こす関数
void vulFunc(){
void** vul_var = (void **) &vul_var;
}
// メイン関数
int main(void){
vulFunc();
return 0;
}
スタックの状態
上記コードをコンパイルします。
$ gcc -O0 -g bfo.c
その後、GDBを用いてvulFunc()
内でvul_var
を宣言した時点での各種情報を収集します。
なお、vul_var
は自身のアドレスを値として格納しているので、vul_var[]
は自身のアドレスを起点とするvoid *
型の配列と見なせます。
これにより、スタックの読み書きを簡便に実現できます。
Breakpoint 2, vulFunc () at bfo.c:9
9 }
-- スタックの確認 --
(gdb) p vul_var[0]
$1 = (void *) 0x7fffffffde70
(gdb) p vul_var[1]
$2 = (void *) 0x237c98ebd73e7b00
(gdb) p vul_var[2]
$3 = (void *) 0x7fffffffde90
(gdb) p vul_var[3]
$4 = (void *) 0x5555555551cc <main+18>
(gdb) p vul_var[4]
$5 = (void *) 0x0
-- レジスタの確認 --
(gdb) info register
(中略)
rbp 0x7fffffffde80 0x7fffffffde80
rsp 0x7fffffffde70 0x7fffffffde70
(中略)
以上の出力から、vul_var
を宣言した時点でのスタックは下図の状態であると推測できました。
攻撃コードの実行
vul_var[3]に戻りアドレスが格納されていると推測できたので、バッファオーバーフローで戻りアドレスを攻撃コードexploitFunc()
のアドレスに書き換え、実行を試みます。
#include <stdio.h>
// 攻撃コード
void exploitFunc(){
printf("exploit!\n");
}
// バッファオーバーフローを起こす関数
void vulFunc(){
void** vul_var = (void **) &vul_var;
vul_var[3] = exploitFunc; // 追加した式
}
// メイン関数
int main(void){
vulFunc();
return 0;
}
上記コードをコンパイルし、実行します。
$ gcc -g -O0 bof.c
$ ./a.out
exploit!
Segmentation fault (core dumped)
Segmentation faultが起きたものの、exploitFunc()
が実行されているのを確認できました。
なお、vul_var[1]
に相当するアドレスには、カナリアが格納されていると推測されます。
カナリアは、ローカル変数領域のあふれによるスタック破壊を検知するためのものである。(中略)、関数からのリターン直前にカナリアの値が書き変わっていないかがチェックされ、スタック破壊が検出される。
実際にvul_var[1]
の値を0に書き換えて実行したところ、以下のように実行がabortされました。
$ ./a.out
*** stack smashing detected ***: terminated
Aborted (core dumped)
感想
今回は非常に簡単な例でしたが、バッファオーバーフローはサービス妨害攻撃(DoS)の手段となるだけではなく、様々な攻撃の足掛かりとなり得るのを実感できました。