1. 程式人生 > >深入C/C++之基於CheckStackVars的安全檢查(VS2008)

深入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剪下板是可以暫存資料,剪下板在後臺起作用,存放在記憶體中。如果把隱私資料,特別是密碼,存放在剪下板中是不安全的,因為任何的應用程式都可以訪問剪下板中的資料。如果一個惡意應用,註冊了系統剪下板的監聽器事件,當剪下板資料發生變化的時候,就

深入理解AndroidJava 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 我們也要時刻清醒,有時候右值會轉為左值,左值會轉為右值。 (也許“轉換”二字用的不是很準確) 如果我們要避免這種轉換呢? 我們需要一種方法能按照參數原來的類型轉發到另一個函

課程設計四位加法計算器2C程式碼

#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+