300 likes | 450 Views
YLUG 読書会 バッファオーバフロー防止技術. YASUDA Yasunori yasunori@furuta.com 11/Sep/2003. 目次. バッファオーバフロー概要 スタックオーバフロー ヒープオーバフロー 検知・防御技術 canary 系 Stack Guard, Stack Smashing Protector, Stack Shield Address Obfuscation 系 ASLR (Address Space Layout Randomization) Address Encryption 系 PointGuard
E N D
YLUG読書会バッファオーバフロー防止技術 YASUDA Yasunori yasunori@furuta.com 11/Sep/2003
目次 • バッファオーバフロー概要 • スタックオーバフロー • ヒープオーバフロー • 検知・防御技術 • canary系 • Stack Guard, Stack Smashing Protector, Stack Shield • Address Obfuscation 系 • ASLR (Address Space Layout Randomization) • Address Encryption 系 • PointGuard • Non executable memory系 • non-executable stack , PAGEEXEC, SEGMEXEC, exec-shield, • Library 系 • Libsafe, Libverify
バッファオーバフロー概要stack overflow • スタックをオーバフローさせ、スタック内にある return address ないしは saved frame pointer を改竄 • スタックの改竄内容 • return address 時: exploit code • saved frame pointer 時: exploit code & stack frame • return address の改竄の場合には、攻撃された関数の return 時、saved frame pointer の改竄時には、攻撃された関数を呼び出した関数が return するときに任意のアドレスにジャンプ • ジャンプ先 • スタック上の実行コード • libc (return into libc) • スタックに引数を書き、libc 内の関数にジャンプ
バッファオーバフロー概要stack overflow overflow low [sample.c] char *func(char *msg) { int var1; char buf[80]; int var2; strcpy(buf,msg); return msg; } int main(int argc, char **argv) { char *p; p = func(argv[1]); exit(0); } var2 func() code/ stack frame buf func() var1 .. func() address of code/ stack frame saved %ebp func() return address func() func()'s arguments main()/func() main() p main() saved %ebp main() return address start()/main() main()'s arguments high
バッファオーバフロー概要heap overflow • ヒープをオーバフローさせ、ヒープの管理データ(malloc_chunk)を改竄 • free() 時に行うリンクのつなぎ変え処理時に、改竄された値で指定したアドレスの内容を書き換える • 基本的に任意のアドレスの書き換えをピンポイントで可能 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk* fd; struct malloc_chunk* bk; } #define unlink(P, BK, FD) \ { \ BK = P->bk; \ FD = P->fd; \ FD->bk = BK; \ BK->fd = FD; \ }
バッファオーバフロー概要heap overflow free() 対象 malloc chunk:O malloc chunk:P malloc chunk:Q data data data overflow malloc chunk: Q malloc chunk: O malloc chunk: P P->fd P->bk prev size prev size prev size size size size P->bk+8 fd fd = X fd P->fd+12 bk bk = Y bk O->bk = P->fd->bk = (P->fd +12) ← Y Q->fd =P->bk->fd =(P->bk+8) ← X
検知・防御技術stack guard • 方式 • canary word による stack overflow 検知 • return addressの直前に canary word を入れておき、関数のリターン時に canary word が破壊されているかどうかで、スタックオーバフローを検知 • 検知したらabortを呼ぶ • 実装 • コンパイラで実装 • 関数の呼び出し時に canary word を埋め込むコード、関数のリターン時にcanary wordの破壊をチェックするコード(C言語レベル)を挿入 • Bypass 方法 • saved frame pointer のみ書き換えることで可能 • libc の GOT (Global Offset Table), .dtors などを書き換えることで可能 • e.g.) exit のアドレスを書き換え • data/bss領域の間にある
検知・防御技術stack guard low canary word の種類 1: NULL canary 値: 0x00 2: terminate canary 値: 0x000aff0d 文字列系関数の terminatorを含む NUL(0x0), \n(0x0a) 3: random canary 値: exec 時に乱数を作り、 それとの XOR をとる (MS の /GS もほぼ同じだが 挿入位置が変数と saved %ebp の間) var2 func() buf func() var1 func() saved %ebp func() canary word func() func() return address func()'s arguments main()/func() high
検知・防御技術stack guard function_prologue: pushl $0x000aff0d // push canary into the stack pushl %ebp // save stack frame pointer mov %esp,%ebp // saves a copy of current %esp ... function_epologue: leave // restore stack frame cmpl $0x000aff0d,(%esp) // check canary jne canary_changed addl $4,%esp // remove canary from stack ret canary_changed: ... // abort the program with error call __canary_death_handler jmp // just in case I guess
検知・防御技術Stack Smashing Protector • 方式 • canary word によるstack overflow検知 • saved frame pointer の前に canary word (random)を入れておき、関数のリターン時に canary word が破壊されているかどうかで、スタックオーバフローを検知 • ローカル変数の再配置 • 非バッファ変数をバッファ変数の低位アドレスに配置。非バッファ変数を守る • 関数の引数のコピー • 引数をローカル変数の領域にコピー。オリジナルを守る • 実装 • コンパイラで実装 • 関数の呼び出し時に canary word を埋め込むコード、関数のリターン時にcanary wordの破壊をチェックするコードを挿入 • スタックフレームの構造の変更 • By pass 方法 • libc の GOT (Global Offset Table), .dtors などを書き換えることで可能
検知・防御技術Stack Smashing Protector low [sample2.c] typedef struct {char str[32];} string_t; char *func(char *arg1, string_t arg2) { int var1=1; char buf2[80]; int var3=3; char buf4[80];; strcpy(buf2,arg1); strcpy(buf4,arg1); strcpy(arg2.str,arg1); return msg; } copy of arg1 func() var3 func() var1 func() buf4 func() buf2 func() copy of arg2 func() canary word func() saved %ebp func() func() return address func()'s arguments main()/func() high
検知・防御技術Stack Smashing Protector standard_prologue: pushl %ebp // save frame pointer mov %esp,%ebp // saves a copy of current %esp subl $272,%esp // space for local variables and canary protection_prologue: movl __guard, %eax // read global canary movl %eas,-24($ebp) // save copy of canary in stack ... (function body)
検知・防御技術Stack Smashing Protector protection_epologue: movl -24(%ebp),%edx cmpl __guard, %edx // is canary in stack changed? je standard_epilogue movl -24(%ebp), %eax pushl %eax // push altered canary value pushl $function_name // push function name call __stack_smash_handler addl $8, %esp standard_epilogue: movl %ebp,%esp // standard epilogue popl %ebp ret
検知・防御技術Stack Shield • 方式 • return address を別のメモリ領域(retarray)にコピーしておき、関数のリターン時にチェックすることによりstack overflow を検知 • return address の改竄のチェック • 関数のリターン先がスタックやヒープにジャンプしていないかのチェック • 関数のポインタがスタックやヒープにジャンプしていないかチェック • 実装 • アセンブラで実装(入力・出力ともアセンブラソース) • retarray:保存用メモリ領域 • retptr: 何個 return address を保持しているかのポインタ(=array tail) • Bypass 方法 • saved frame pointer のみの改竄で可能 • libc の GOT (Global Offset Table), .dtors などを書き換えることで可能
検知・防御技術Address Space Location Randomize • 方式 • スタック、ヒープ、テキスト、ライブラリをマップする仮想アドレスを exec 時にランダマイズし、ジャンプするアドレスの指定を困難にする • stack : 4~27 bit • mmap (library, heap, thread stacks, shared memory) : 12~27bit • exec (text/data/bss), brk(heap) : 12~27bit • 実装 • カーネル内で実装 • exec 時にランダマイズしてマップ • 絶対アドレスの場合には fault handlerがアドレスを再セット • Bypass 方法 • ランダマイズされていない範囲(0~11bit = 4kbytes = 1page)はアドレスの指定が簡単 • 同じページに飛び先のコードがある場合、return address を下位1byteのみ書き換えでOK • リーク関数(printf系)のあるプログラムを利用し、1byte overflow でmain()のreturn address を読み出し、libc内の関数のアドレスを計算
検知・防御技術PointGuard • 方式 • ポインタの値を暗号化してメモリに保持、レジスタにロードするときに復号することによりジャンプするアドレスの指定を困難にする • 暗号は 32bit 乱数との XOR(乱数は exec 時に取得) • ポインタの値を改竄されても、illegal な領域を指すだけですむ • 乱数は readonly page において、書き換えられなくする • 乱数はプログラム実行時に生成 • すべてのポインタを対象 • Return address, Frame pointer, Function pointer (stack, heap), Data pointer (stack, heap) • 実装 • コンパイラで実装 • GCC の AST (Abstract Syntax Tree) ステージで実装
CPU 1. fetch pointer value 0x1234 2. access data referenced by pointer pointer decryption 0x7239 encrypted pointer Memory data 0x7239 0x1234 検知・防御技術PointGuard
検知・防御技術non-executable stack • 方式 • x86 のセグメント機能を利用した non-executable stack • コードセグメントのリミット(CS limit)をスタックのアドレス境界以下にし、スタック上のコードを実行できなくする • x86 ではページプロテクションは READ, WRITE しかないため、ハードウェアレベルで non-executable の制御はできない • 実装 • カーネル内で実装 • exec 時にメモリレイアウトを調整 • Code segment descriptor を設定 • mprotect(2)でPROT_EXEC設定可 • Bypass 方法 • return into libc など 0x0 CS stack growth 0xbfffffff
検知・防御技術PAGEEXEC • 方式 • x86 のメモリ保護機能を利用した non-executable memory • PTEのUser/Supervisor bit を exec/non-exec bit として転用。 • data 領域は non-exec (supervisor)に設定すると、data を読み込む際、必ず trap が発生する。 • page fault handler は instruction fetch の場合、プログラムを abort, data fetch の場合、supervisor bit を user bit にして、TLB にロード。 • データキャッシュにキャッシュされるものはすべて User bit を立てる。 • キャッシュフラッシュの際に supervisor bit を立てて書き戻す • 実装 • カーネル内で実装 • page fault handler など • Bypass 方法 • return into libc など
検知・防御技術SEGMEXEC • 方式 • x86 のセグメント保護機能を利用した non-executable memory • ユーザの仮想アドレス空間をデータセグメント(0~1.5GB)とコードセグメント(1~3GB)に分ける。 • データセグメントにはデータとテキスト、コードセグメントにはテキストのみ配置 • テキストにはデータ(string, function pointer table)も含まれるため、ミラーを data 領域におく • 実装 • カーネル内で実装 • exec 時にメモリレイアウトを調整 • Code segment descriptor を設定 • Data segment descriptor を設定 • 絶対アドレスの場合には fault handlerが アドレスを再セット • Bypass 方法 • return into libc など 0 text DS data 1.5GB text CS 3GB
検知・防御技術SEGMEXEC $/tmp/cat /proc/self/maps [1] 08048000-0804a000 R-Xp 00000000 00:0b 1190 /tmp/cat [2] 0804a000-0804b000 RW-p 00000000 00:0b 1109 /tmp/cat [3] 0804b000-0804d000 RW-p 00000000 00:00 0 [4] 20000000-20015000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so [5] 20015000-20016000 RW-p 00000000 03:07 110818 /lib/ld-2.2.5.so [6] 2001e000-20143000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so [7] 20014300-20149000 RW-p 00000000 03:07 106687 /lib/libc-2.2.5.so [8] 20149000-2014d000 RW-p 00000000 00:00 0 [9] 5fffe000-60000000 RW-p ffffff000 00:00 0 [10] 68048000-6804a000 R-Xp 00000000 00:0b 1109 /tmp/cat [11] 80000000-80015000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so [12] 8001e000-80143000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so DS CS data segment: 1~9 (1, 4, 6 は 10,11,12 のミラー) code segument: 10~12
検知・防御技術exec-shield • 方式 • x86 のセグメント機能を利用したnon-executable memory • プロセスごとに CS limit を可能な限り小さく設定 • コンテクストスイッチごとに CS descriptor を変更(数クロック程度) • CS limit のあたりは ascii-armor エリア(0x00を含む)ような値にする • strcpy などが途中で止まるように • 実装 • カーネル内で実装 • exec/context switch 時に code segment descriptorを設定 • ld で指定 • -melf-i386-small option の追加 • スタック上のコードを実行させる場合には、バイナリのELFフラグを設定することでnon-exec の対象からはずせる • By pass 方法 • return into libc など
検知・防御技術exec-shield $/home/mingo/cat-lowaddr /proc/self/maps 00101000-00116000 r-xp 00000000 03:01 319365 /lib/ld-2.3.2.so 00116000-00117000 rw-p 00014000 03:01 319365 /lib/ld-2.3.2.so 00117000-0024a000 r-xp 00000000 03:01 319439 /lib/libc-2.3.2.so 0024a000-0024e000 r-xp 00132000 03:01 319439 /lib/libc-2.3.2.so 0024e000-00250000 rw-p 00000000 00:00 0 01000000-01004000 r-xp 00000000 16:01 2036120 /home/mingo/cat-lowaddr 01004000-01005000 rw-p 00000000 16:01 2036120 /home/mingo/cat-lowaddr 01005000-01006000 rw-p 00000000 00:00 0 40000000-40001000 rw-p 00000000 00:00 0 40001000-40201000 r—p 00000000 03:01 464809 locale-archive 40201000-40207000 r—p 00915000 03:01 464809 locale-archive 40207000-40234000 r—p 0091f000 03:01 464809 locale-archive 40234000-40235000 r—p 00955000 03:01 464809 locale-archive bfffe000-c0000000 rw-p fffff000 00:00 0 CS limit 01004000
検知・防御技術libsafe • 方法 • 危険なlibc内の標準関数の置き換え • str系, printf系, gets, getwd, realpath, wcpcpy, wscat, wcscpy, memcpy • 引数チェックを行う • スタックフレームを壊すかどうか? • gcc の__builtin_frame_pointer(0)を使って境界を取得 • StackGuardなどのスタックフレームの構成が変更されたものと組み合わせ不可 • 実装 • 共有ライブラリによる実装 • /etc/ld.so.preload, LD_PRELOAD によって libc より先にリンク • オーバフロー時にメールを送れる • /etc/libsafe.exclude でlibsafe対象外のコマンドの設定ができる • Bypass方法 • 上記以外の関数のオーバフロー • libc の GOT (Global Offset Table), .dtors などを書き換えることで可能
検知・防御技術libverify • 方法 • プロセスのメモリ内のコードを書き換え、return address の改竄チェック • ヒープにオリジナルの関数のコードをコピー • オリジナル関数のテキストは wrapper_entry にジャンプするコードに置換 • wrapper_entry は return アドレスを保存し、ヒープ内のコピーにジャンプ • 関数の最後に wrapper_exit にジャンプし、return address をチェック • 実装 • 共有ライブラリの実装 • /etc/ld.so.preload, LD_PRELOAD によって libc より先にリンク • exec時のリンク処理の中でrewriteする(_initで実行) • Bypass方法 • libc の GOT (Global Offset Table), .dtors などを書き換えることで可能
比較 ※1 RAのみ ※2 アドレスのみ ※3 一部の標準関数のみ ※4 stack のみ
まとめ • 組み合わせて使おう • 適用領域によって最適な組み合わせは変わる • コンパイラ変えて OK or NG • ハードウェアサポートがある or ない • 性能の妥協点など • 性能比較するためのデータが少ないのでオーバヘッドの比較ができないが、だいたいの目安は以下のとおり • コンパイラがコード追加 • 数%~20%くらい、(最悪だと80%?!) • ハードウェアサポート利用 • ~10%くらい, (PAGEEXEC はもっと多いはず) • lib系 • ~20%くらい • libsafe < libverify ≒ stack guard • address obfuscation 系 • 多分そんなに大きくないはず(絶対アドレスがなければ)