昨天在試著逆向一個有時間期限的LIB時,發現一些特別的檢查函式,在之前的VC2003中是沒有的,這些函式可謂是重量級函式。由於個人比較看不慣自己不懂的東西,出於不憤之情緒研究了下這些函式。首先在這裡介紹個人認為較之其他幾個更為重要的一種安全檢查方式——基於Cookie的緩衝區溢位安全檢查!
為了在釋出版本中也能檢測到緩衝區溢位,防止程式因緩衝區而受到***,VS2005(VC8)便增加了基於Cookie的安全檢查。
在計算機領域,Cookie一詞最早出現在網站開發中,是指網站的服務程式通過瀏覽器儲存到客戶端的少量資料,這些資料都是二進位制的,或者是經過加密的,通常用來記錄使用者身份和登入情況等資訊。後來這個詞被泛指一方簽發給另一方的認證或者標誌資訊。
在之前的博文中,都提到過堆疊呼叫及EBP,RET返回地址,還有區域性變數在棧幀中存放的各種微妙關係。相信大家也有清晰的瞭解,如果不清晰呢,可以參見Shell Code,HOOK API這兩篇。嘿嘿!
VC8編譯器在編譯可能發生緩衝區溢位的函式時,會定義一個很特別區域性變數,如果不加分析的話,這個區域性變數還真不知道代表什麼意思。而且它的值又是怎麼算出來的?,它有什麼作用? 可能有的朋友不是沒有注意到這個細節就是覺得沒有必要追究它。不過個人認為熟悉了堆疊呼叫裡面的原理會對我們的除錯能力和差錯能力有很明顯的提高。好了,轉入正題。編譯器增加的這個區域性變數時緊挨著EBP的存放地址的。順序就是:VAR COOKIE EBP RET ARGS。這個Cookie的變數位於函式體內的區域性變數和EBP的存放地址之間,具體表示就是:[EBP-4]。這個專門用於儲存Cookie的變數被稱為Cookie變數。是一個32位的無符號整數。它的值是從全域性變數__security_cookie得到的。我們可以看看這個全域性變數的定義:
#ifdef _WIN64
#define DEFAULT_SECURITY_COOKIE 0x00002B992DDFA232
#else
#define DEFAULT_SECURITY_COOKIE 0xBB40E64E
#endif
DECLSPEC_SELECTANY UINT_PTR __security_cookie = DEFAULT_SECURITY_COOKIE;
DECLSPEC_SELECTANY UINT_PTR __security_cookie_complement = ~(DEFAULT_SECURITY_COOKIE);
由於我測試的機子是32位的,這裡只看32位的。有朋友是64位的可以測試下。這裡個定義位於gs_cookie.c檔案中。檔案位於編譯器目錄的VC/crt/src下。這裡可以看到,最開始的時候這個全域性變數被初始化成了0xBB40E64E。為什麼是這個值,這裡不是討論的重點,還有待研究!嘿嘿。
上面說到最開始的時候被初始化為了0xBB40E64E。言外之意後面還會對它進行處理?答案是肯定的!下一次初始化是在我們程式進入主函式之前的:__tmainCRTStartup函式裡。
拋開預處理和機器的條件判斷,這個函式的原型如下:
int mainCRTStartup( void )
{
/*
* The /GS security cookie must be initialized before any exception
* handling targetting the current p_w_picpath is registered. No function
* using exception handling can be called in the current p_w_picpath until
* after __security_init_cookie has been called.
*/
__security_init_cookie();
return __tmainCRTStartup();
}
我的機子拋開後是呼叫的這個函式。現在的硬體環境大多數也是這個。這裡可以看到在這裡會呼叫__security_init_cookie()這個函式對__security_cookie變數再次初始化。這個函式也是能看到原型的,它位於gs_support.c檔案中。進去看看,拋開一編譯時的判斷條件其原型主體部分為:
void __cdecl __security_init_cookie()
{
UINT_PTR cookie;
FT systime={0};
LARGE_INTEGER perfctr;
GetSystemTimeAsFileTime(&systime.ft_struct);
cookie = systime.ft_struct.dwLowDateTime;
cookie ^= systime.ft_struct.dwHighDateTime;
cookie ^= GetCurrentProcessId();
cookie ^= GetCurrentThreadId();
cookie ^= GetTickCount();
QueryPerformanceCounter(&perfctr);
cookie ^= perfctr.LowPart;
cookie ^= perfctr.HighPart;
__security_cookie = cookie;
__security_cookie_complement = ~cookie;
}
這裡可以看出來,為了取得好的隨機性,先是取出時間,異或之,然後是分別跟其他一些列具有隨機性的資料(程式ID,執行緒ID,TickCount和效能計數器)進行異或運算。這個變數因為是全域性的,在這裡( mainCRTStartup啟動函式)初始化後在程式過程中將不會再改變。如果想要檢視這個cookie變數的值。可以再除錯的時候拉出“即時視窗(Immediate)”,在裡面輸入__security_cookie回車就能看到了。
好了,在上面介紹完了Cookie變數的產生、初始化和作用後,下面來看看使用。
寫個最簡單的測試:
int main( void )
{
char a[ 20 ];
strcpy( a, “masefee” );
return 0;
}
上面說過,編譯器會在可能發生緩衝區溢位的函式插入Cookie變數和安全檢查。這樣一個小例子足以讓它檢查了。他已經發現可能存在危險了。是不是很智慧? – –
要看這個函式一開始怎麼寫入Cookie變數的,可以打斷點在紅色的括號處或者F11單步。這裡又得在反彙編裡面進行了,這裡不厭其煩的從彙編裡面去看問題,包括以前的文章基本跟彙編有聯絡。這裡不是別的,只是個人認為還是很有必要從彙編的角度去了解高階語言的原理。很多是很必要的。當然這裡也只能從彙編去分析這個Cookie變數的寫入過程及檢查過程!忍耐一下! – –
就這裡這個簡單的例子,DEBUG模式下反彙編如下:
0043BEF0 push ebp
0043BEF1 mov ebp,esp
0043BEF3 sub esp,0E0h
0043BEF9 push ebx
0043BEFA push esi
0043BEFB push edi
0043BEFC lea edi,[ebp-0E0h]
0043BF02 mov ecx,38h
0043BF07 mov eax,0CCCCCCCCh
0043BF0C rep stos dword ptr es:[edi]
0043BF0E mov eax,dword ptr [___security_cookie (4B7A74h)]
0043BF13 xor eax,ebp
0043BF15 mov dword ptr [ebp-4],eax
0043BF18 push offset string “masefee” (4AA938h)
0043BF1D lea eax,[ebp-1Ch]
0043BF20 push eax
0043BF21 call @ILT+3880(_strcpy) (437F2Dh)
0043BF26 add esp,8
0043BF29 xor eax,eax
0043BF2B push edx
0043BF2C mov ecx,ebp
0043BF2E push eax
0043BF2F lea edx,[ (43BF5Ch)]
0043BF35 call @ILT+3115(@_RTC_CheckStackVars@8) (437C30h)
0043BF3A pop eax
0043BF3B pop edx
0043BF3C pop edi
0043BF3D pop esi
0043BF3E pop ebx
0043BF3F mov ecx,dword ptr [ebp-4]
0043BF42 xor ecx,ebp
0043BF44 call @ILT+920(@__security_check_cookie@4) (43739Dh)
0043BF49 add esp,0E0h
0043BF4F cmp ebp,esp
0043BF51 call @ILT+7205(__RTC_CheckEsp) (438C2Ah)
0043BF56 mov esp,ebp
0043BF58 pop ebp
0043BF59 ret
這就是main函式的所有反彙編程式碼。首先看紅色的一句指令。是將___security_cookie變數的值給取出來。然後藍色的指令就是將取出來的Cookie全域性變數與當前EBP的值進行異或運算。與EBP異或當然有好處。
1. 可以增加隨機性,儘可能使不同函式的安全Cookie都不同。
2. 可以檢查EBP是否被破壞,因為在函式結束檢查Cookie時,還會將Cookie變數值再次與EBP異或,如果EBP的值沒有變化,那麼就能恢 覆成原來的___security_cookie值。
這些細節地方不得不佩服微軟的設計師們的縝密和擴充套件的思維!
上面綠色的指令便是關鍵的一步,它是將異或後的值存入[ebp-4]中。當前的EBP就是最開始壓入EBP後ESP的值,也就是壓入的EBP的地址。減4就剛好挨著一進函式時壓入的EBP的地址減4。好了!Cookie變數已經在棧幀中了。
下面一步就看最後的檢查部分,粉色的部分前兩句指令是將Cookie變數的值重新取出來並異或還原並儲存到ECX中。儲存到ECX是有目的的。之後再討論。第三句粉色的CALL就是呼叫__security_check_cookie 函式了。其原型非常簡單:
void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie)
{
/* x86 version written in asm to preserve all regs */
__asm
{
cmp ecx, __security_cookie
jne failure
rep ret /* REP to avoid AMD branch prediction penalty */
failure:
jmp __report_gsfailure
}
}
這個函式位於編譯器目錄下的VC/crt/src/intel/secchk.c中。為了降低對可執行檔案大小和執行效能的影響,這個函式直接用匯編寫的。而且只有4條指令,不存在任何變數和暫存器(標誌暫存器除外)的改變。因為是使用的快速呼叫協定。因為唯一的一個引數都是存放在ECX中,直接進行CMP比較的。紅色的部分就是與全域性的Cookie變數進行比較。如果相同就正常,如果不同就jmp failure報錯。跳轉到__report_gsfailure函式。這樣既檢查了EBP是否合法,又檢查了Cookie變數的合法性。
好了,基本上寫完了。上面留了兩個點,一是mian函式裡面我有標誌了一句橙色的語句,這個也是一個檢查。將在後面的博文中提到。二是__report_gsfailure函式的整個過程。也將在後面的博文中深入闡述。上面有什麼不對的地方還望大家批評。我很希望得到大家的指點!
思考:
1. 這種模式的安全檢查能夠移植到我們平常的專案中的哪些地方,在我們使用者程式碼上顯示進行檢查?
2. 這種產生儘可能隨機的方式用於類似於生成物件的全域性唯一ID或者更多的需要唯一性的資料上?