我在上篇文章舉了一個簡單的C++程序非常簡略的解釋C++代碼和匯編代碼的對應關系,在后面的文章中我將按照不同的Topic來仔細介紹更多" />

日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

C++反匯編揭秘2 – VC編譯器的運行時錯誤檢查(R

系統(tǒng) 2858 0
<iframe align="center" marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog336280.html" frameborder="0" width="336" scrolling="no" height="280"></iframe>

我在上篇文章舉了一個簡單的 C++ 程序非常簡略的解釋 C++ 代碼和匯編代碼的對應關系,在后面的文章中我將按照不同的 Topic 來仔細介紹更多相關的細節(jié)。雖然我很想一開始的時候就開始直接介紹 C++ 和匯編代碼的對應關系,不過由于 VC 編譯器會在代碼中插入各種檢查, SEH C++ 異常等代碼,因此我覺得有必要先寫一下一些在閱讀 VC 生成的匯編代碼的時候常見的一些東西,然后再開始具體的分析 C++ 代碼的反匯編。這篇文章會首先涉及到運行時檢查( Runtime Checking

Runtime Checking

運行時檢查是 VC 編譯器提供了運行時刻的對程序正確性 / 安全性的一種動態(tài)檢查,可以在項目的 C++ 選項中打開 Small Type Check Basic Runtime Checks 來啟用 Runtime Check

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="Picture_x0020_4" style="VISIBILITY: visible; WIDTH: 453pt; HEIGHT: 28.5pt; mso-wrap-style: square" type="#_x0000_t75" o:spid="_x0000_i1027"><imagedata o:title="" src="file:///D:%5Ctmp%5Cmsohtmlclip1%5C01%5Cclip_image001.png"></imagedata></shape>

同時,也可以使用 /RTC 開關來打開檢查, /RTC 后面跟 c, u, s 代表啟用不同類型的檢查。 Smaller Type Check 對應 /RTCc, Basic Runtime Checks 對應 /RTCs /RTCu

/RTCc 開關

RTCc 開關可以用來檢查在進行類型轉換的保證沒有不希望的截斷( Truncation )發(fā)生。以下面的代碼為例:

char ch = 0;

short s = 0x101;

ch = s;

VC 執(zhí)行到 ch = s 的時候會報告如下錯誤:

C++反匯編揭秘2 – VC編譯器的運行時錯誤檢查(RTC)

<shape id="Picture_x0020_1" style="VISIBILITY: visible; WIDTH: 327pt; HEIGHT: 144.75pt; mso-wrap-style: square" type="#_x0000_t75" o:spid="_x0000_i1026"><imagedata o:title="" src="file:///D:%5Ctmp%5Cmsohtmlclip1%5C01%5Cclip_image003.png"></imagedata></shape>

原因是 0x101 已經(jīng)超過了 char 的表示范圍。

之前會導致錯誤地的代碼對應的匯編代碼如下所示:

; 42 : char ch = 0;

mov BYTE PTR _ch$[ebp], 0

; 43 : short s = 0x101;

mov WORD PTR _s$[ebp], 257 ; 00000101H

; 44 : ch = s;

mov cx, WORD PTR _s$[ebp]

call @_RTC_Check_2_to_1@4

mov BYTE PTR _ch$[ebp], al

可以看到,賦值的時候, VC 編譯器先將 s 的值放到 cx 寄存器中,然后調用 _RTC_Check_2_to_1@4 函數(shù)來檢查是否有數(shù)據(jù)截斷的問題,結果放在 al 中,最后將 al 放到 ch 之中。 _RTC_Check_2_to_1@4 顧名思義是檢查 2 byte 的數(shù)據(jù)被轉換成 1 byte 的數(shù)據(jù)( short 2 byte char 是一個 byte ),代碼如下:

_RTC_Check_2_to_1:

00411900 push ebp

00411901 mov ebp,esp

00411903 push ebx

00411904 mov ebx,ecx

00411906 mov eax,ebx

00411908 and eax,0FF00h

0041190D je _RTC_Check_2_to_1+24h (411924h)

0041190F cmp eax,0FF00h

00411914 je _RTC_Check_2_to_1+24h (411924h)

00411916 mov eax,dword ptr [ebp+4]

00411919 push 1

0041191B push eax

0041191C call _RTC_Failure (411195h)

00411921 add esp,8

00411924 mov al,bl

00411926 pop ebx

00411927 pop ebp

00411928 ret

1. 00411904~00411906 ecx 保存著 s 的值,然后又被轉移到 eax 中。

2. 00411908~0041190D :檢查 eax 0xff00 相與,并檢查是否結果為 0 ,如果結果為 0 ,說明這個 short 值是 0 或者 的正數(shù),沒有超過范圍,直接跳轉到 00411924 獲得結果并返回

3. 0041190F~00411914 :檢查 eax 是否等于 0xff00 ,如果相等,說明這個 short 值是負數(shù),并且 >=-128 ,在 char 的表示范圍之內,可以接受,跳轉到 00411924

4. 如果上面檢查都沒有通過,說明這個值已經(jīng)超過了范圍,調用 _RTC_Failure 函數(shù)報錯

要解決這個問題,很簡單,把代碼改為下面這樣就可以了:

char ch = 0;

short s = 0x101;

ch = s & 0xff;

/RTCu 開關

這個開關的作用是打開對未初始化變量的檢查,比靜態(tài)的警告要有用一些。考慮下面的代碼:

int a;

char ch;

scanf("%c", &ch);

if( ch = 'y' ) a = 10;

printf("%d", a);

編譯器無從通過 Flow Analysis 知道 a printf 之前是否被正確初始化,因為 a = 10 這個分支是由外部條件決定的,所以只有動態(tài)的監(jiān)測方法才可以知道到底程序有沒有 Bug (當然從這里我們可以很明顯的看出這個程序必然是有 Bug 的)。顯然把變量的值和一個具體值來比較是無法知道變量是否被初始化的,所以編譯器需要通過一個額外的 BYTE 來跟蹤此變量是否被初始化:

函數(shù)的開始代碼如下:

push ebp

mov ebp, esp

sub esp, 228 ; 000000e4H

push ebx

push esi

push edi

lea edi, DWORD PTR [ebp-228]

mov ecx, 57 ; 00000039H

mov eax, -858993460 ; ccccccccH

rep stosd

mov BYTE PTR $T5147[ebp], 0

最后一句很關鍵,把 $T5147 變量的值設置為 0 ,表示并沒有初始化 a 這個變量。

ch = ‘y’ 的時候,編譯器除了執(zhí)行 a=10 之外還會將 $T5147 設置為 1

mov BYTE PTR $T5147[ebp], 1

mov DWORD PTR _a$[ebp], 10 ; 0000000aH

之后,在 printf 之前,編譯器會檢查 $T5147 這個變量的值,如果為 0 ,說明沒有初始化,執(zhí)行 __RTC_UninitUse 報告錯誤,否則跳轉到相應代碼執(zhí)行 printf 語句:

cmp BYTE PTR $T5147[ebp], 0

jne SHORT $LN4@wmain

push OFFSET $LN5@wmain

call __RTC_UninitUse

add esp, 4

$LN4@wmain:

mov esi, esp

mov eax, DWORD PTR _a$[ebp]

push eax

push OFFSET ??_C@_02DPKJAMEF@?$CFd?$AA@

call DWORD PTR __imp__printf

add esp, 8

cmp esi, esp

call __RTC_CheckEsp

/RTCs 開關

這個開關是用來檢查和 Stack 相關的問題:

1. Debug 模式下把 Stack 上的變量初始化為 0xcc ,檢查未初始化的問題

2. 檢查數(shù)組變量的 Overrun

3. 檢查 ESP 是否被毀壞

Debug 模式下初始化變量為 0xcc

假設我們有下面的代碼:

void func()

{

int a;

int b;

int c;

}

對應的匯編代碼如下:

?func@@YAXXZ PROC ; func, COMDAT

; 38 : {

push ebp

mov ebp, esp

sub esp, 228 ; 000000e4H

push ebx

push esi

push edi

lea edi, DWORD PTR [ebp-228]

mov ecx, 57 ; 00000039H

mov eax, -858993460 ; ccccccccH

rep stosd

; 39 : int a;

; 40 : int b;

; 41 : int c;

; 42 :

; 43 : }

pop edi

pop esi

pop ebx

mov esp, ebp

pop ebp

ret 0

?func@@YAXXZ ENDP

1. sub esp, 228 s 編譯器為 棧分配了 228 byte

2. 接著 3 push 指令保存寄存器

3. Lea edi, DWORD PTR [ebp-228] 一直到 repstosd 指令是初始化從 ebp-228 開始寫 57 0xcccccccc ,也就是 57*4=228 0xcc ,正好填滿之前 sub esp, 228 所分配的空間。這段代碼會把所有的變量初始化為 0xcc

選擇 0xcc 是有一定理由的 :

1. 0xcc 不同于一般的初始化值,人們一般傾向于把變量初始化為 0, 1, -1 等比較簡單的值,而 0xcc 一般情況下足夠大,而且是負數(shù),容易引起注意,而且一般變量的值很有可能不允許是 0xcc ,比較容易造成錯誤

2. 0xcc = int 3 ,如果作為代碼執(zhí)行,則會引發(fā)斷點異常,比較容易引起注意

檢查數(shù)組變量的 Overrun

假設我們有下面的代碼:

void func

{

char buf[104];

scanf("%s", buf);

return 0;

}

scanf 調用之后,會執(zhí)行下面的代碼:

mov ecx, ebp

push eax

lea edx, DWORD PTR $LN5@wmain

call @_RTC_CheckStackVars@8

這段代碼會調用 _RTC_CheckStackVars@8 函數(shù)會在數(shù)組的開始和結束的地方檢查 0xcccccccc 有否被破壞,如果是,則報告錯誤。 _RTC_CheckStackVars 由于代碼過長這里就不給出了,這個函數(shù)主要是利用編譯器保存的數(shù)組位置和長度信息,檢查數(shù)組的開頭和結尾:

$LN5@func:

DD 1

DD $LN4@func

$LN4@func:

DD -112 ; ffffff90H

DD 104 ; 00000068H

DD $LN3@func

$LN3@func:

DB 98 ; 00000062H

DB 117 ; 00000075H

DB 102 ; 00000066H

DB 0

$LN5@func 紀錄了數(shù)組的個數(shù),而 $LN4@func 保存了數(shù)組的偏移量 ebp - 112 和數(shù)組的長度 104 ,而 $LN3@func 則保存了變量的名稱( 0x62, 0x75, 0x66, 0 = “buf” )。

檢查 ESP

ESP 的錯誤很有可能是由調用協(xié)定的 mistach 造成,或者 Stack 本身沒有平衡。編譯器會在調用其他函數(shù)和在函數(shù) Prolog Epilog (開始和結束代碼)的時候插入對 ESP 的檢查:

1. 在調用其他外部函數(shù)的時候:

假設我們有下面的代碼:

printf( "%d", 1 );

對應的匯編代碼如下:

mov esi, esp

push 1

push OFFSET ??_C@_02DPKJAMEF@?$CFd?$AA@

call DWORD PTR __imp__printf

add esp, 8

cmp esi, esp

call __RTC_CheckEsp

可以看到檢查的代碼非常簡單直接,把 ESP 保存在 ESI 之中,當調用 printf ,平衡堆棧之后,檢查 esp esi 的是否一致,然后調用 __RTC_CheckESP __RTC_CheckESP 代碼也很簡單:

_RTC_CheckEsp:

00412730 jne esperror (412733h)

00412732 ret

esperror:

……

00412744 call _RTC_Failure (411195h)

……

00412754 ret

如果不一致,跳轉到 esperror 標號報告錯誤。

2. 函數(shù)返回的時候:

以下面的代碼為例:

void func()

{

__asm

{

push eax

}

}

Func 函數(shù)故意 push eax 來破壞堆棧的平衡性,對應的匯編代碼如下:

?func@@YAXXZ PROC ; func, COMDAT

; 38 : {

push ebp

mov ebp, esp

sub esp, 192 ; 000000c0H

push ebx

push esi

push edi

lea edi, DWORD PTR [ebp-192]

mov ecx, 48 ; 00000030H

mov eax, -858993460 ; ccccccccH

rep stosd

; 39 : __asm

; 40 : {

; 41 : push eax

push eax

; 42 : }

; 43 : }

pop edi

pop esi

pop ebx

add esp, 192 ; 000000c0H

cmp ebp, esp

call __RTC_CheckEsp

mov esp, ebp

pop ebp

ret 0

?func@@YAXXZ ENDP

在函數(shù)的初始化代碼中, func 會將 ebp 保存在 Stack 中,并且把當前 esp 保存在 ebp 中。

?func@@YAXXZ PROC ; func, COMDAT

push ebp

mov ebp, esp

關鍵的檢查代碼在后面,當 func 函數(shù)恢復了堆棧之后,堆棧會恢復到之前剛保存 esp ebp 的那個狀態(tài),這個時候 ebp 必然等于 esp ,否則出錯

分享到:
評論
happmaoo
  • 瀏覽: 1292026 次
  • 性別: Icon_minigender_1
  • 來自: 杭州
社區(qū)版塊
最新評論

C++反匯編揭秘2 – VC編譯器的運行時錯誤檢查(RTC)


更多文章、技術交流、商務合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 肃南| 邓州市| 宁明县| 金川县| 河源市| 隆德县| 吴旗县| 临泉县| 大姚县| 伊宁市| 民县| 吉林省| 清涧县| 永清县| 泾阳县| 泰州市| 宜兰市| 桐城市| 庄浪县| 固阳县| 广东省| 英山县| 遵义县| 南宫市| 五大连池市| 高邮市| 恩施市| 新田县| 遵义县| 梁河县| 永善县| 大厂| 贺兰县| 白玉县| 新龙县| 沂水县| 义乌市| 山东| 绵竹市| 山东省| 保德县|