深入C/C++之基於CheckStackVars的安全檢查(VS2008)
最近一直忙畢業的相關事情,加上工作,轉眼間,又到月底了,之前承諾的每月一篇博文,前幾天就一直在尋找到底要寫什麼,近兩天又突然發現有很多東西可以寫。本篇就先延續之前的一篇基於Cookie的安全檢查機制(深入C/C++之基於Cookie的安全檢查(VS2005))來介紹下另外一種在DEBUG版本下的安全檢查,也就是CheckStackVars檢查,話不多說,直接進入正題。
在VS2008下,函式的棧空間裡如果存在陣列,就會自動加上CheckStackVars檢查,顧名思義,就是用來檢查區域性資料是否訪問越界。相對來說,這種檢查只能起到一定的作用,並不會所有越界訪問都能檢查到,根據後面的原理介紹會了解到這點。既然是檢查區域性的,那麼在函式內定義的static型別陣列或者函式外部的全域性陣列並不會採用此檢查,既然是檢查陣列,那麼如果函式內沒有區域性陣列時,此檢查也不會存在。
首先來看一個簡單的例子,驗證這個檢查的存在:
void TestVars( void )
{
int bf = 0xeeeeeeee;
char array[10] = { 0 };
int bk = 0xffffffff;
strcpy( array, "masefee" );
}
int main( void )
{
TestVars();
return 0;
}
在這個例子中,存在一個數組array,這裡刻意定義了另外兩個變數,用於看這兩個變數與陣列array的記憶體分佈情況。這樣就能清晰的瞭解到CheckStackVars這個檢查的原理。然後來看看Debug下,TestVars函式內部的3個區域性變數的記憶體分佈情況。斷點打在strcpy這句上,分佈如下:
ff ff ff ff cc cc cc cccc cc cc cc 00 00 00 00 00 00 00 00 00 00cc cc cc cc cc cc cc cc cc cc ee ee ee ee
bk array bf
上面的關係已經很明確了,我們發現,在C++的程式碼中看,bf、array、bk三者在記憶體分佈上應該是連續的,緊挨著的。但是這裡並不是這樣的,看看bf和array之間居然像個10個位元組之遠。原因在於,在VS2008的debug版本下,區域性變數之間並不是連續存放在棧記憶體裡的,而是以4位元組對齊的方式,前後都會有保護位元組的。這裡的保護位元組佔4個位元組,值為0xcc,很明顯這是彙編指令int 3中斷的程式碼位元組。因此這裡bk和bf變數前後都會有4個位元組的0xcc。上面綠色的部分就是,在陣列array兩端也有4位元組的0xcc。上面黑色加粗的部分即是,array陣列一共佔10位元組,要以4位元組對齊,所以要補兩位元組,因此多了兩個0xcc,因此導致bf和array之間相隔10位元組。上面array後面緊挨著的本應該是兩個0xcc,用於補充對齊。這裡故意標識到後面去了。這裡這樣標識的意圖是為了說明CheckStackVars這個檢查的原理。
好了,清楚了記憶體分佈情況,那麼CheckStackVars在什麼時間執行檢查的呢,在C++程式碼上並不能顯示的看到,於是來翻翻TestVars函式的反彙編程式碼:
TestVars:
004113B0 push ebp
004113B1 mov ebp,esp
004113B3 sub esp,0F0h
004113B9 push ebx
004113BA push esi
004113BB push edi
004113BC lea edi,[ebp-0F0h]
004113C2 mov ecx,3Ch
004113C7 mov eax,0CCCCCCCCh
004113CC rep stos dword ptr es:[edi]
004113CE mov eax,dword ptr [___security_cookie (417004h)]
004113D3 xor eax,ebp
004113D5 mov dword ptr [ebp-4],eax
004113D8 mov dword ptr [ebp-0Ch],0EEEEEEEEh
004113DF mov byte ptr [ebp-20h],0
004113E3 xor eax,eax
004113E5 mov dword ptr [ebp-1Fh],eax
004113E8 mov dword ptr [ebp-1Bh],eax
004113EB mov byte ptr [ebp-17h],al
004113EE mov dword ptr [ebp-2Ch],0FFFFFFFFh
004113F5 push offset string "masefee" (415804h)
004113FA lea eax,[ebp-20h]
004113FD push eax
004113FE call @ILT+160(_strcpy) (4110A5h)
00411403 add esp,8
00411406 push edx
00411407 mov ecx,ebp
00411409 push eax
0041140A lea edx,[ (411438h)]
00411410 call @ILT+130(@[email protected]) (411087h)
00411415 pop eax
00411416 pop edx
00411417 pop edi
00411418 pop esi
00411419 pop ebx
0041141A mov ecx,dword ptr [ebp-4]
0041141D xor ecx,ebp
0041141F call @ILT+25(@[email protected]) (41101Eh)
00411424 add esp,0F0h
0041142A cmp ebp,esp
0041142C call @ILT+320(__RTC_CheckEsp) (411145h)
00411431 mov esp,ebp
00411433 pop ebp
00411434 ret
00411435 lea ecx,[ecx]
00411438 db 01h
00411439 db 00h
0041143A db 00h
0041143B db 00h
0041143C db 40h
0041143D db 14h
0041143E db 41h
0041143F db 00h
00411440 db e0h
00411441 db ffh
00411442 db ffh
00411443 db ffh
00411444 db 0ah
00411445 db 00h
00411446 db 00h
00411447 db 00h
00411448 db 4ch
00411449 db 14h
0041144A db 41h
0041144B db 00h
0041144C db 61h
0041144D db 72h
0041144E db 72h
0041144F db 61h
00411450 db 79h
00411451 db 00h
從TestVars的反彙編程式碼可以清楚的看到,黑色加粗的部分就是前一篇博文介紹的,在本篇注意看在strcpy呼叫之後,又呼叫了_RTC_CheckStackVars函式,這是一個什麼樣的函式?先來看看他的原型:
void __fastcall _RTC_CheckStackVars( void *_Esp, _RTC_framedesc *_Fd );
這是一個fastcall函式,因此兩個引數都是通過暫存器進行傳遞的。第二個引數是一個結構體型別,再來看看這個結構體的定義:
typedef struct _RTC_framedesc
{
int varCount; // 要檢查的陣列的個數
_RTC_vardesc *variables; // 要檢查的陣列的相關資訊
} _RTC_framedesc;
這個結構體定義在rtcapi.h標頭檔案中的,_RTC_vardesc 也是一個結構體型別,看看定義:
typedef struct _RTC_vardesc
{
int addr; // 陣列的首地址相對於EBP的偏移量
int size; // 陣列的大小位元組數
char *name; // 陣列的名字
} _RTC_vardesc;
以上面的例子來填充這個結構體之後,結構體的資料就是:
_RTC_framedesc.varCount = 1;
_RTC_vardesc->addr = array - EBP; // 這裡array在低地址,所以addr最終為負
_RTC_vardesc->size = 10;
_RTC_vardesc->name = "array";
好了,這下清楚了資訊的儲存,再回到上面的反彙編程式碼,在呼叫_RTC_CheckStackVars函式之前,注意紅色粗體的一句指令,將ebp賦值給了ecx暫存器,再將411438h這個地址值賦值給了edx,由於_RTC_CheckStackVars函式是fastcall,因此通過這兩個暫存器進行傳遞引數,而不是push操作。ecx就是儲存的TestVars函式的棧幀,edx這個地址有點奇怪,本來是應該傳遞_RTC_framedesc結構指標的,難道這個411438h地址值就是_RTC_framedesc結構體變數所在的記憶體地址?從上面的反彙編程式碼可以看到,下面從411438h地址開始,多了一段奇怪的資料,本應該函式下面不會有這麼一段資料的,在Debug下大多數情況都是0xcc填充的。咱們仔細觀察下這段資料,或者直接將411438h這個地址值copy到記憶體窗口裡看:
0x00411438 01 00 00 00 40 14 41 00 e0 ff ff ff 0a 00 00 00 4c 14 41 00 61 72 72 61 79 00
看看上面的資料,是不是就是_RTC_framedesc結構應該有的資料?答案是肯定的,紅色的部分就是_RTC_framedesc.variables指標的值,指向的位置就是緊跟其後,這是編譯器故意這麼處理的。當然可以是其它地方。這是編譯器直接把這些資訊記錄在程式碼段的,並且緊跟在所記錄的函式程式碼之後。因此不要誤認為這些資訊是在程式執行期間才寫進去或填充的_RTC_framedesc結構。
瞭解到這裡,發現整個規則都是有理有據的,並且設計都是很良好的。也能又一次感受MS的偉大。呵呵,廢話了!
上面既然將兩個引數都給了_RTC_CheckStackVars函式,再來看看此函式內部是怎麼檢測的,看看此函式的反彙編:
_RTC_CheckStackVars:
00411500 mov edi,edi
00411502 push ebp
00411503 mov ebp,esp
00411505 push ecx
00411506 push ebx
00411507 push esi
00411508 push edi
00411509 xor edi,edi // 清零
0041150B mov esi,edx // 將_RTC_framedesc結構指標賦值給esi
0041150D cmp dword ptr [esi],edi // 比較varCount是否為0,if( _Fd->varCount != 0 )
0041150F mov ebx,ecx // 將TestVars的棧幀賦值給ebx
00411511 mov dword ptr [i],edi // 這裡的i應該是迴圈變數,將陣列的個數賦值給i,i = _Fd->varCount ;
00411514 jle _RTC_CheckStackVars+58h (411558h)
00411516 mov eax,dword ptr [esi+4] // +4之後就是_RTC_framedesc.variables指標
00411519 mov ecx,dword ptr [eax+edi] // _RTC_vardesc->addr了,就是陣列的首地址相對於TestVars的EBP的偏移量
0041151C add eax,edi // 將eax定位到_RTC_vardesc結構首地址
0041151E cmp dword ptr [ecx+ebx-4],0CCCCCCCCh // [ecx+ebx-4]等價於ebp-addr-4,也就是array的前面4個保護位元組
00411526 jne _RTC_CheckStackVars+36h (411536h) // 如果不等於0xcccccccc就報錯_RTC_StackFailure
00411528 mov edx,dword ptr [eax+4] // eax+4就是_RTC_vardesc->size,表示陣列的大小
0041152B add edx,ecx // ecx當前是偏移量,加上size後就是array陣列尾部相對於ebp的偏移量
0041152D cmp dword ptr [edx+ebx],0CCCCCCCCh // edx+ebx即是陣列array尾部的後4個保護位元組,然後比較
00411534 je _RTC_CheckStackVars+4Ah (41154Ah)
00411536 mov eax,dword ptr [esi+4] // esi+4為_RTC_framedesc.variables指標
00411539 mov ecx,dword ptr [eax+edi+8] // eax+edi+8即是_RTC_vardesc->name,用於報錯提示
0041153D mov edx,dword ptr [ebp+4]
00411540 push ecx // 傳入越界的陣列名
00411541 push edx // 傳入EBP+4的地址,此地址正是_RTC_CheckStackVars的返回地址,用於定位
00411542 call _RTC_StackFailure (4110CDh) // 呼叫此函式後,彈出異常MessageBox,提示哪個陣列越界
00411547 add esp,8
0041154A mov eax,dword ptr [i] // 存在多個數組需要檢查時有用
0041154D inc eax
0041154E add edi,0Ch // 定位到下一個_RTC_vardesc結構
00411551 cmp eax,dword ptr [esi]
00411553 mov dword ptr [i],eax
00411556 jl _RTC_CheckStackVars+16h (411516h) // 迴圈
00411558 pop edi
00411559 pop esi
0041155A pop ebx
0041155B mov esp,ebp
0041155D pop ebp
0041155E ret
以上過程稍微解析得有點複雜,其主要原理就是讀取_RTC_vardesc結構,挨個對每個陣列進行前後邊界檢查,如果發生更改,則呼叫_RTC_StackFailure函式,最後彈出錯誤資訊框,資訊如:
Run-Time Check Failure #2 - Stack around the variable 'array' was corrupted.
這裡需要說明一點,如果存在多個數組需要檢查時,每個陣列的name是緊挨著的,同時緊接著跟在多個_RTC_vardesc結構之後,記憶體分佈如下:
[陣列個數, _RTC_vardesc地址] [ 多個_RTC_vardesc結構(陣列)][ 每個陣列的name]
這些位置分佈都是編譯器直接寫在程式碼裡的。
這樣就能實現簡單的邊界檢查了,前面提到了,這種檢查只是會檢查前後邊界,如果在程式中越界訪問,但是沒有修改或者寫的值就是邊界檢查的值0xcccccccc,那也不會檢測出程式碼已經有越界隱患。因此最主要的還是要小心謹慎。編譯器總不能為我們做所有的事情。以上過程會在棧記憶體里加上邊界檢查值,所以在Debug版本下是比較實用的。在Release下不會這麼浪費空間,因此越界就顯得更加危險了。
從上面的分析過程來看,可以寫出_RTC_CheckStackVars函式的虛擬碼,如下:
這段程式碼可以直接通過編譯,並起到相應的檢查功能,上面檢查失敗我這裡暫時使用的__asm int 3進行中斷,後面的註釋是真正的_RTC_CheckStackVars函式呼叫的錯誤函式,_RTC_StackFailure用於彈出錯誤資訊和定位偵錯程式的游標到這個返回地址。
以下程式碼是用於測試這段虛擬碼的功能:
上面的程式碼是合法的,呼叫了檢查函式之後沒有任何的越界訪問,如果要測試失敗的情況,則將:
//array1[ 10 ] = 0;
//array2[ 10 ] = 0;
這兩句的註釋取消,就會在第二個__asm int 3出中斷。
以上就是CheckStackVars的所有原理,基於這種檢查機制還能發散出很多的東西,並且也可以自己實現一套規則,在一些關鍵的程式碼處設定這道檢測關卡,也是非常有用的。本文到此結束,希望大家多提意見,歡迎拍磚!
相關推薦
深入C/C++之基於CheckStackVars的安全檢查(VS2008)
最近一直忙畢業的相關事情,加上工作,轉眼間,又到月底了,之前承諾的每月一篇博文,前幾天就一直在尋找到底要寫什麼,近兩天又突然發現有很多東西可以寫。本篇就先延續之前的一篇基於Cookie的安全檢查機制(深入C/C++之基於Cookie的安全檢查(VS2005))來介紹下另外一種
C++學習之迴圈和關係表示式(1)
在c語言中我這部分還算學習的挺好,所以這部分我只新增一些我不太懂的點: 1.通常,cout在顯示bool值之前將它們轉換為int,但是在前面使用cout.setf(ios:boolalpha)函式呼叫設定了一個標記,該標記命令cout顯示true和false,而不是1和0;
C++實戰之OpenCL矩陣相乘優化(二)
前言 上一篇文章,分析了簡單的矩陣相乘在opencl裡面的優化kernel程式碼,每個work-item只負責計算結果矩陣的一個元素。下一步準備每次計算出結果矩陣的塊元素,看看計算時間是如何。 具體分析 這裡引入opencl記憶體的概念: 比較常
Android靜態安全檢查(十三):剪下板使用檢測
Android剪下板使用風險Android剪下板是可以暫存資料,剪下板在後臺起作用,存放在記憶體中。如果把隱私資料,特別是密碼,存放在剪下板中是不安全的,因為任何的應用程式都可以訪問剪下板中的資料。如果一個惡意應用,註冊了系統剪下板的監聽器事件,當剪下板資料發生變化的時候,就
深入理解Android之Java Security第二部分(Final)
深入理解Android之Java Security(第二部分,最後)程式碼路徑:Security.java:libcore/lunl/src/main/java/java/security/TrustedCertificateStore.java:libcore /crypt
深入理解系列之JAVA多執行緒(2)——synchronized同步原理
多執行緒中為了解決執行緒安全問題,一個重要的手段就是同步!所謂同步其實就是使得原本各個執行緒交叉執行(非同步),變成排隊執行(同步)。同步策略使得不同執行緒操作共享資料遵循“先來後到“,從而避免某個執行緒沒有處理完資料就被另一執行緒搶佔操作出現資料被覆蓋或
C程式設計之基於UDP的網路通訊
/* * udp-c.c * Author: Alvin * 基於UDP的客戶端程式碼 * 功能:向伺服器傳送內容,並接收來自服務端的回覆 * */#include <stdio.h>#include <string.h>#include <netinet/in.h>#in
objective-c 中數據類型之二 字符串(NSString)
option 大小 edas 字符串長度 seq scan 後者 code form // 1. 聲明一個NSString對象,註意對象前要加‘*’。 NSString *string1; // 賦值方
修羅場第二天:C#之面向對象基礎(下)
dog 主函數 div 接口 對象 blank 返回值 情況 抽象 ------------接(上)http://www.cnblogs.com/HoloSherry/p/7100795.html 抽象類 抽象類也可以實現多態,使用關鍵字abstract。那麽什
C# ABP源碼詳解 之 BackgroundJob,後臺工作(一)
技術分享 轉發 cbac wid 性能 更新 strong ron bst 本文歸屬作者所有,轉發請註明本文鏈接。 1. 前言 ABP的BackgroundJob,用來處理耗時的操作。比如客戶端上傳文件,我們要把文件(Excel)做處理,這耗時的操作我們應該放到後臺工作
C#設計模式之四抽象工廠模式(AbstractFactory)【創建型】
抽象 抽象工廠 album 代碼 ctf bst actor 抽象工廠模式 .cn 一、引言 寫了3篇有關設計模式的文章了,大家有了些反饋,說能從中學到一些東西,我感到很欣慰,那就繼續努力。今天我要寫第四個模式了,該模式叫抽象工廠。上一篇文章我們講了【工廠方法】模式,它是為
C#設計模式之八橋接模式(Bridge)【結構型】
升級 方向 implement 詳細 .cn mage names 這樣的 意圖 一、引言 今天我們要講【結構型】設計模式的第二個模式,該模式是【橋接模式】,也有叫【橋模式】的。大家第一次看到這個名稱會想到什麽呢?我第一次看到這個模式根據名稱猜肯定是連接什麽東西的。因為
C++11新特性之 std::forward(完美轉發)(轉)
tails array sin .com std utili res details calling 我們也要時刻清醒,有時候右值會轉為左值,左值會轉為右值。 (也許“轉換”二字用的不是很準確) 如果我們要避免這種轉換呢? 我們需要一種方法能按照參數原來的類型轉發到另一個函
課程設計之四位加法計算器(2)(C程式碼)
#include<reg52.h> typedef unsigned char uint8; typedef unsigned int uint16; sbit rw=P2^5; sbit rs=P2^6; sbit e=P2^7; sbit led=P3
C++知識點備忘錄之多檔案程式編寫(六)
使用標頭檔案來定義使用者型別,為操作使用者型別的函式提供函式原型;並將函式定義放在一個獨立的原始碼檔案中。標頭檔案和原始碼檔案一起定義和實現了使用者定義的型別以及使用方式。最後,將main()和其他使用這些函式的函式放在第三個檔案中。 #include<iostr
【Visual C++】遊戲開發筆記之十 基礎動畫顯示(三) 透明動畫的實現
作者:毛星雲 郵箱: [email protected] 歡迎郵件交流程式設計心得"透明動畫”是遊戲中一定會用到的基本技巧,它通過圖案的連續顯示及圖案本身背景的透明化處理,在背景圖上產生出栩栩如生的動畫效果。看過之前筆記的朋友們應該知道,在筆記六裡我們介紹
Android NDK——必知必會之JNI的C++操作函式詳解和小結(三)
引言 上一篇講解了一些關於JNI和NDK的必知必會的理論知識和機制,由於篇幅問題把關於JNI的重要的函式放到這篇,具體使用留到下一篇,此係列文章基連結: 一、JNI中的函式概述 在JNI層我們基本上都是通過env指標來呼叫jni.h標頭檔案裡定義的函式,JNI
C++之STL迭代器(iterator)
1、vector #include <iostream> #include <vector> int main() { std::vector<char> charVector; int x; for (x=0; x&l
資料結構之---C語言實現二叉排序樹(BinarySortTree)
wechat:812716131 ------------------------------------------------------ 技術交流群請聯絡上面wechat ----------------------------------------------
C++之STL總結精華筆記 (轉)
一、一般介紹 STL(StandardTemplate Library),即標準模板庫,是一個具有工業強度的,高效的C++程式庫。它被容納於C++標準程式庫(C++Standard Library)中,是ANSI/ISOC+