Document

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 系

多分そんなに大きくないはず(絶対アドレスがなければ)