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

亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

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

系統 2758 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 來仔細介紹更多相關的細節。雖然我很想一開始的時候就開始直接介紹 C++ 和匯編代碼的對應關系,不過由于 VC 編譯器會在代碼中插入各種檢查, SEH C++ 異常等代碼,因此我覺得有必要先寫一下一些在閱讀 VC 生成的匯編代碼的時候常見的一些東西,然后再開始具體的分析 C++ 代碼的反匯編。這篇文章會首先涉及到運行時檢查( Runtime Checking

Runtime Checking

運行時檢查是 VC 編譯器提供了運行時刻的對程序正確性 / 安全性的一種動態檢查,可以在項目的 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 )發生。以下面的代碼為例:

char ch = 0;

short s = 0x101;

ch = s;

VC 執行到 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 已經超過了 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 函數來檢查是否有數據截斷的問題,結果放在 al 中,最后將 al 放到 ch 之中。 _RTC_Check_2_to_1@4 顧名思義是檢查 2 byte 的數據被轉換成 1 byte 的數據( 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 或者 的正數,沒有超過范圍,直接跳轉到 00411924 獲得結果并返回

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

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

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

char ch = 0;

short s = 0x101;

ch = s & 0xff;

/RTCu 開關

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

int a;

char ch;

scanf("%c", &ch);

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

printf("%d", a);

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

函數的開始代碼如下:

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’ 的時候,編譯器除了執行 a=10 之外還會將 $T5147 設置為 1

mov BYTE PTR $T5147[ebp], 1

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

之后,在 printf 之前,編譯器會檢查 $T5147 這個變量的值,如果為 0 ,說明沒有初始化,執行 __RTC_UninitUse 報告錯誤,否則跳轉到相應代碼執行 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. 檢查數組變量的 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 一般情況下足夠大,而且是負數,容易引起注意,而且一般變量的值很有可能不允許是 0xcc ,比較容易造成錯誤

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

檢查數組變量的 Overrun

假設我們有下面的代碼:

void func

{

char buf[104];

scanf("%s", buf);

return 0;

}

scanf 調用之后,會執行下面的代碼:

mov ecx, ebp

push eax

lea edx, DWORD PTR $LN5@wmain

call @_RTC_CheckStackVars@8

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

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

檢查 ESP

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

1. 在調用其他外部函數的時候:

假設我們有下面的代碼:

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. 函數返回的時候:

以下面的代碼為例:

void func()

{

__asm

{

push eax

}

}

Func 函數故意 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

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

?func@@YAXXZ PROC ; func, COMDAT

push ebp

mov ebp, esp

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

分享到:
評論
happmaoo
  • 瀏覽: 1292026 次
  • 性別: Icon_minigender_1
  • 來自: 杭州
最新評論

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


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

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

【本文對您有幫助就好】

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

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 人人爱天天做夜夜爽毛片 | 综合亚洲一区二区三区 | 热久久视久久精品18国产 | 一级毛片不收费 | 91免费国产高清观看 | 欧美精品免费在线观看 | 婷婷综合色 | 成人综合网站 | 亚洲国产女人aaa毛片在线 | 老司机久久精品 | 欧美一级成人一区二区三区 | 97视频精品 | 亚洲国产中文字幕 | 九九热在线观看 | 国产视频一区在线播放 | 在线视频免费国产成人 | 91视频爱爱 | 国产精品一区二区三区免费 | 成人国产欧美精品一区二区 | 亚洲最大色网站 | 色偷偷成人网免费视频男人的天堂 | 狠狠色噜噜狠狠狠狠97老肥女 | 四虎国产免费 | 一级大片视频 | 国产福利在线观看精品 | 日韩不卡 | 中文字幕一区二区三区在线观看 | 成人欧美一级毛片免费观看 | 日日拍夜夜操 | 欧美久久一区二区三区 | 亚洲主播在线 | 久久久久国产一级毛片高清板 | 日韩一区精品视频在线看 | 日本久久精品免视看国产成人 | 精品美女视频在线观看2023 | 欧美成人午夜 | 色桃花网 | 日本中文字幕一区二区高清在线 | 亚洲日本欧美在线 | 日本一级毛一级毛片短视频 | 欧美日本高清视频在线观看 |