YLUG読書会 バッファオーバフロー防止技術 YASUDA Yasunori [email protected] 11/Sep/2003 目次 バッファオーバフロー概要 スタックオーバフロー ヒープオーバフロー 検知・防御技術 canary系 Address Obfuscation 系 PointGuard Non executable memory系 ASLR (Address Space Layout Randomization) Address Encryption 系 Stack Guard, Stack Smashing Protector, Stack Shield 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 [sample.c] char *func(char *msg) { int var1; char buf[80]; int var2; overflow low strcpy(buf,msg); return msg; func() var2 func() buf func() var1 .. func() saved %ebp func() return address address of code/ stack frame } int main(int argc, char **argv) { main()/func() char *p; main() p = func(argv[1]); main() exit(0); } main() high func()'s arguments p saved %ebp return address start()/main() main()'s arguments code/ stack frame バッファオーバフロー概要 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 data malloc chunk:P data malloc chunk:Q data overflow P->fd malloc chunk: O prev size malloc chunk: P prev size size P->fd+12 malloc chunk: Q prev size size size fd fd = X fd bk bk = Y bk O->bk = P->fd->bk = (P->fd +12) ←Y Q->fd =P->bk->fd =(P->bk+8) ←X P->bk P->bk+8 検知・防御技術 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 func() var2 func() buf func() var1 func() saved %ebp func() canary word func() return address main()/func() func()'s arguments high canary word の種類 1: NULL canary 値: 0x00 2: terminate canary 値: 0x000aff0d 文字列系関数の terminatorを含む NUL(0x0), \n(0x0a) 3: random canary 値: exec 時に乱数を作り、 それとの XOR をとる (MS の /GS もほぼ同じだが 挿入位置が変数と saved %ebp の間) 検知・防御技術 stack guard function_prologue: pushl $0x000aff0d pushl %ebp mov %esp,%ebp ... function_epologue: leave cmpl $0x000aff0d,(%esp) jne canary_changed addl $4,%esp ret // push canary into the stack // save stack frame pointer // saves a copy of current %esp // restore stack frame // check canary // remove canary from stack 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 [sample2.c] typedef struct {char str[32];} string_t; low 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; func() copy of arg1 func() var3 func() var1 func() buf4 func() buf2 func() copy of arg2 func() canary word func() saved %ebp func() return address main()/func() func()'s arguments 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 je standard_epilogue // is canary in stack changed? 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) ステージで実装 検知・防御技術 PointGuard CPU 1. fetch pointer value 0x1234 pointer decryption 2. access data referenced by pointer 0x7239 Memory encrypted pointer 0x7239 data 0x1234 メモリレイアウト 検知・防御技術 non-executable stack 方式 x86 のセグメント機能を利用した non-executable stack コードセグメントのリミット(CS limit)をスタックのアドレス境界以下にし、スタッ ク上のコードを実行できなくする 実装 カーネル内で実装 0x0 exec 時にメモリレイアウトを調整 x86 ではページプロテクションは READ, WRITE しかないため、ハードウェアレベ ルで non-executable の制御はできない Code segment descriptor を設定 CS mprotect(2)でPROT_EXEC設定可 Bypass 方法 return into libc など 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)に分ける。 データセグメントにはデータとテキスト、コードセグメントにはテキストのみ配 置 実装 カーネル内で実装 exec 時にメモリレイアウトを調整 テキストにはデータ(string, function pointer table)も含まれるため、ミラーを data 領域におく Code segment descriptor を設定 Data segment descriptor を設定 絶対アドレスの場合には fault handlerが アドレスを再セット Bypass 方法 return into libc など 0 text DS data 1.5GB text CS 3GB 検知・防御技術 SEGMEXEC DS CS $/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 data segment: 1~9 (1, 4, 6 は 10,11,12 のミラー) code segument: 10~12 検知・防御技術 exec-shield 方式 x86 のセグメント機能を利用したnon-executable memory プロセスごとに CS limit を可能な限り小さく設定 CS limit のあたりは ascii-armor エリア(0x00を含む)ような値にする strcpy などが途中で止まるように 実装 カーネル内で実装 exec/context switch 時に code segment descriptorを設定 ld で指定 コンテクストスイッチごとに CS descriptor を変更(数クロック程度) -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 00116000-00117000 rw-p 00014000 03:01 319365 00117000-0024a000 r-xp 00000000 03:01 319439 0024a000-0024e000 r-xp 00132000 03:01 319439 0024e000-00250000 rw-p 00000000 00:00 0 CS limit 01000000-01004000 r-xp 00000000 16:01 2036120 01004000 01004000-01005000 rw-p 00000000 16:01 2036120 01005000-01006000 rw-p 00000000 00:00 0 40000000-40001000 rw-p 00000000 00:00 0 40001000-40201000 r—p 00000000 03:01 464809 40201000-40207000 r—p 00915000 03:01 464809 40207000-40234000 r—p 0091f000 03:01 464809 40234000-40235000 r—p 00955000 03:01 464809 bfffe000-c0000000 rw-p fffff000 00:00 0 /lib/ld-2.3.2.so /lib/ld-2.3.2.so /lib/libc-2.3.2.so /lib/libc-2.3.2.so /home/mingo/cat-lowaddr /home/mingo/cat-lowaddr locale-archive locale-archive locale-archive locale-archive 検知・防御技術 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 などを書き換えることで可能 検知・防御技術 libsafe 検知・防御技術 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 などを書き換えることで可能 検知・防御技術 libverify 比較 方式 H/Wサポート 要 ソースコード 要 Stack frame 改竄チェック データ領域 アドレスの ランダム化 実行禁止 StackGuard No Yes Yes (※1) No No StackShield No Yes Yes (※1) No No SSP(Propolice) No Yes Yes No No PointGuard No Yes Yes (※2) No No ASLR No No No No Yes non-exec stack Yes No No Yes (※4) No PAGEEXEC Yes No No Yes No SEGMEXEC Yes No No Yes No exec-shield Yes No No Yes No libsafe No No Yes (※3) No No libexec No No Yes No No ※1 RAのみ ※2 アドレスのみ ※3 一部の標準関数のみ ※4 stack のみ まとめ 組み合わせて使おう 適用領域によって最適な組み合わせは変わる コンパイラ変えて OK or NG ハードウェアサポートがある or ない 性能の妥協点など 性能比較するためのデータが少ないのでオーバヘッドの比較ができ ないが、だいたいの目安は以下のとおり コンパイラがコード追加 ハードウェアサポート利用 数%~20%くらい、(最悪だと80%?!) ~10%くらい, (PAGEEXEC はもっと多いはず) lib系 ~20%くらい libsafe < libverify ≒ stack guard address obfuscation 系 多分そんなに大きくないはず(絶対アドレスがなければ)
© Copyright 2024 ExpyDoc