Buffer Overrun定義
buffer是程式用來儲存資料的連續記憶體地區,一旦分配完成,其起始地址和大小便固定下來。程式運行過程中,如果使用了超出buffer的地區,那麼就發生了buffer overflow(緩衝區溢位)或者buffer overrun(緩衝區越界)。如果該緩衝區分配在stack上,就稱之為stack buffer overrun;如果分配在heap,就稱為heap overflow。
stack buffer overrun會沖毀thread自身及其父線程的棧資訊,我們知道在當前stack frame中,EBP+4為函數返回地址,EBP+8為最後一個參數,EBP+C:倒數第二個參數...。如果EBP+4處的內容被寫入一個非法地址,那麼函數調用退出的時候就會跳轉到一個非法地址,轉而執行非法程式。這就是利用stack buffer overrun進行惡意攻擊的基本原理。
在vc6.0中,如果程式為debug 版本,by default會啟用stack buffer overrun的檢測。而在release版本中,則關閉了對其的檢測。而在vc8以上版本編譯其中,則採用了基於Cookie的stack buffer安全檢查機制。
防止Buffer overrun的措施
本文將介紹在VS8及以上版本編譯器中採用Cookie機制來防止stack overrun的方法。
首先簡要介紹在Debug版本下的stack orverrun(VC8及以上)的檢測機制:
1.VC8在緊鄰棧指標的位置分配4個位元組用於存放安全Cookie。安全Cookie就像一個安全護欄,用於保護其下的棧指標和函數返回地址,他位於局部變數和重要的棧幀資訊(棧幀指標及函數返回地址)。這樣,如果Cookie之上的局部變數發生溢出,在漫延到棧幀資訊前會覆蓋Cookie,因此檢查Cookie的完整性便可以探測到可能危及棧資訊的溢出情況。
2.在給每個局部變數分配空間時,除了補齊記憶體空間所需的零頭部分,編譯器還會給每個變數多分配8個位元組,分別放置於變數的前後各4個位元組,並將其初始化為0xcccccccc。這樣,每個變數相當於有了前後2個屏障。一旦這2道屏障受損,則會產生中斷(正是cc編碼)。
基於Cookie的Stack Buffer安全檢測
但是以上開銷較大,通常只用於調試版本。為了在release version中也能檢測出stack buffer overrun,防止程式因此而受到攻擊,VS2005及以上支援了基於Cookie的安全檢查機制。
基於Cookie的安全檢查機制原理如下:
1.編譯器在編譯可能發生stack buffer orverrun的函數時,會定義一個特別的局部變數,該局部變數被分配在棧幀中所有其他局部變數和棧幀與函數返回之間,這個變數專門用來儲存Cookie,我們將其稱為Cookie變數。Cookie變數是一個32位的整數,他的值從全域變數_security_cookie中得到。(該全域變數是定義在C運行庫中,我們可以看到預設安裝路徑下的原始碼:c:\program...\visu..8\VC\crt\src\gs_cookie.c可以看到其定義:
#define DEFAULT_SECURITY_COOKIE 0xBB40E64E
DECLSPEC_SELECTRANY UINT_PRT _security_cookie=DEFAULT_SECURITY_COOKIE;
......
(可見_security_cookie變數賦了預設值)。在VC8編譯器啟動的啟動函數中(如WinMainCRTStartup)還會調用_security_init_cookie函數對該變數進行正式的初始化。在crt0.c中,可以看到啟動函數的源碼如下:
int _tmainCRTStartup(void)
{
_security_init_cookie();//初始化cookie
......
}
因為是在啟動函數中初始化全域變數_security_cookie,所以在該process以後的運行過程中,Cookie值是保持不變的。
編譯器在為一個函數插入安全Cookie時,他還會與當時的EBP做一次xor,然後將其儲存到Cookie變數中,這些指令通常出現在函數的序言(prolog)之後,其典型過程如下:
push ebp
mov ebp,esp
sub esp,0x18//為局部變數分配棧空間
mov eax,[applica!_security_Cookie];//將_security_cookie存入eax
xor eax,ebp;//與ebp xor
mov [ebp-0x4],eax;//存入cookie變數
......
與EBP xor的兩個好處:
1. 提高安全cookie的隨機性,儘可能使每個函數的_security_cookie都不同。
2.檢查EBP值是否被破壞。因為在檢查Cookie變數時,會再與EBP再進行一次xor,如果EBP沒有發生變化,那麼兩次xor後Cookie變數的值就應該成全域安全變數_security_cookie的值。
其典型的代碼如下:
mov ecx, [ebp-0x4];
pop edi;
xor ecx,ebp;//再xor 一次
pop esi;
call applica!_security_check_cookie(0x***);//開始檢查_security_cookie
mov esp,ebp
pop ebp
ret;
而_security_check_cookie是一個只含有4條彙編代碼的函數,在crt\intel\secchk.c 中對函數定義如下:
void _security_check_cookie(UINT _PTR cookie)
{
_asm
{
cmp ecx,_security_cookie;
jne failure;
rep ret;
failure:
jmp _report_gsfailure;
}
}
2.安全檢查失敗處理
因為stack buffer overrun可能覆蓋掉函數的本來返回地址,使函數返回到未知的地方,從而使程式出現不可預期的後果。因此這種exception被看作是fatal error,一旦遇到這種錯誤,程式就會馬上終止。但為了debug,在terminate process之前,_report_gsfailure函數會記錄下錯誤發生時的重要訊息,並提供JIT調試機會。
[本文摘錄於《軟體調試》張銀奎]