sai’s diary

プログラミング関連の備忘録とつぶやきを書いています。

スタックの保護・破壊検出について

試験中にある関数からreturnするとプロセスがSEGVで落ちるということがありますがコンパイラGCCであれば "-fstack-protector"というオプションを利用するとSEGV発生時にバックトレースを表示し、発生個所を特定することが可能です。

リリース版にも適用するとリターンアドレスの書き換えを防止出来ますがパフォーマンスに影響が出るため 処理速度を測定してから適用するのが良いと思います。

以下はスタック破壊をするサンプルソースです。 foobar()で1byteの配列に32byteの書き込みを行っているため、リターンアドレスの領域を破壊しています。

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

int foobar( void ) {
  char buff[1];
  memset( buff, 0, 32 ); // メモリ破壊をしている箇所
  printf("memset(),end\n");
  return 0;
}

int main( int argc, char **argv ) {
  foobar();
  return 0;
}

"-fstack-protector"はローカル変数に文字配列が無いか、ローカル変数があっても 8バイト未満の場合にはスタック破壊検出コードを生成しないという動作になります。

そのため、今回のサンプルソースは"-fstack-protector"と 必ずスタック破壊検出コードを生成する"-fstack-protector-all"の2つを試します。

"-fstack-protector"でビルドした場合(foobar()のローカル変数が1byteのためスタック破壊検出コードが生成されない)

[user@localhost sample]$ gcc StackOverflow.c -Wall -g -fstack-protector
[user@localhost sample]$ ./a.out
memset(),end
セグメンテーション違反です (コアダンプ)

"-fstack-protector-all"でビルドした場合(ローカル変数に関係なくスタック破壊検出コードが生成される)

[user@localhost sample]$ gcc StackOverflow.c -Wall -g -fstack-protector-all
[user@localhost sample]$ ./a.out
memset(),end
*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib/libc.so.6(__fortify_fail+0x4d)[0xc92f4d]
/lib/libc.so.6[0xc92efa]
./a.out[0x8048492]
[0x0]
======= Memory map: ========
001ea000-001eb000 r-xp 00000000 00:00 0          [vdso]
00b70000-00b8e000 r-xp 00000000 fd:00 1445936    /lib/ld-2.12.so
00b8e000-00b8f000 r--p 0001d000 fd:00 1445936    /lib/ld-2.12.so
00b8f000-00b90000 rw-p 0001e000 fd:00 1445936    /lib/ld-2.12.so
00b96000-00d27000 r-xp 00000000 fd:00 1445937    /lib/libc-2.12.so
00d27000-00d29000 r--p 00191000 fd:00 1445937    /lib/libc-2.12.so
00d29000-00d2a000 rw-p 00193000 fd:00 1445937    /lib/libc-2.12.so
00d2a000-00d2d000 rw-p 00000000 00:00 0
04a09000-04a26000 r-xp 00000000 fd:00 1445967    /lib/libgcc_s-4.4.7-20120601.so.1
04a26000-04a27000 rw-p 0001d000 fd:00 1445967    /lib/libgcc_s-4.4.7-20120601.so.1
08048000-08049000 r-xp 00000000 fd:00 786600     /home/user/sample/a.out
08049000-0804a000 rw-p 00000000 fd:00 786600     /home/user/sample/a.out
08ed6000-08ef7000 rw-p 00000000 00:00 0          [heap]
b7793000-b7794000 rw-p 00000000 00:00 0
b77a1000-b77a4000 rw-p 00000000 00:00 0
bfa52000-bfa67000 rw-p 00000000 00:00 0          [stack]
アボートしました (コアダンプ)

バックトレースで表示されたアドレスをaddr2lineコマンドで参照すると発生個所がfoobar()であることも分かります。

[user@localhost sample]$ addr2line -e ./a.out 0x8048492
/home/user/sample/StackOverflow.c:9

スタック破壊を検出する仕組みとしては「ローカル変数のスタックを格納する領域」と「ebp退避領域、関数リターンアドレスを 格納する領域」の間に「カナリア」と呼ばれる4バイトのデータを配置し、カナリアの値が変化していないかを確認することで スタック破壊が起きているかどうかを検出している様です。

IPAのサイトにより詳細な説明がありますので興味を持たれた方は以下を参照ください。

第10章 著名な脆弱性対策 バッファオーバーフロー: #5 運用環境における防御 http://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c905.html

環境

Linux 2.6.32-431.el6.i686

gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)