淺析VS2010反匯編


第一篇

1. 怎樣進行反匯編

在調試的環境下,我們能夠很方便地通過反匯編窗體查看程序生成的反匯編信息。

例如以下圖所看到的。

image

記得中斷程序的運行,不然看不到反匯編的指令

image

看一個簡單的程序及其生成的匯編指令

復制代碼
#include<stdio.h>
#include<windows.h>
const long Lenth=5060000/5;
int main(){
    while(true){
        for(long i=0;i<Lenth;i++){
            ;
        }
        Sleep(10);
    }
}
復制代碼

匯編窗體

image


2.  預備知識

函數調用大家都不陌生,調用者向被調用者傳遞一些參數,然后運行被調用者的代碼,最后被調用者向調用者返回結果,還有大家比較熟悉的一句話。就是函數調用是在棧上發生的,那么在計算機內部究竟是怎樣實現的呢?
 
對於程序,編譯器會對其分配一段內存。在邏輯上能夠分為代碼段。數據段,堆,棧
代碼段:保存程序文本。指令指針EIP就是指向代碼段。可讀可運行不可寫
數據段:保存初始化的全局變量和靜態變量,可讀可寫不可運行
BSS:未初始化的全局變量和靜態變量
堆(Heap):動態分配內存,向地址增大的方向增長。可讀可寫可運行
棧(Stack):存放局部變量。函數參數,當前狀態。函數調用信息等, 向地址減小的方向增長。很很重要,可讀可寫可運行
如圖所看到的
寄存器
EAX:累加(Accumulator)寄存器,經常使用於函數返回值
EBX:基址(Base)寄存器。以它為基址訪問內存
ECX:計數器(Counter)寄存器,經常使用作字符串和循環操作中的計數器
EDX:數據(Data)寄存器,經常使用於乘除法和I/O指針
ESI:源變址寄存器
DSI:目的變址寄存器
ESP堆棧(Stack)指針寄存器。指向堆棧頂部
EBP基址指針寄存器,指向當前堆棧底部
EIP指令寄存器,指向下一條指令的地址

第二篇

一、VS反匯編方法

1、調出反匯編窗體。

2、調用寄存器窗體(僅僅有在反匯編下才可見)

假設在調試狀態還是沒有此菜單項。可試着以下操作:

在VS中點擊“工具”->“導入和導出設置”,選擇“重置全部設置”,下一步。這時你能夠保存當前設置或不保存,我覺得無所謂,下一步,選擇“Visual C**開發設置”。“完畢”。

這樣,“調試”->“窗體”->“寄存器”菜單項應該用顯示出來了,記得要確保你的程序是在調試的過程中。

3、查看內存

點擊“調試”->“窗體”->“內存”->“內存1”...“內存4”(選一個就能夠了。)。在內存窗體中的“地址”欄輸入地址,按回車就可以看到該地地址處的內存信息。

 二、經常使用匯編指令介紹

1、經常使用指令

為了照應到沒學過匯編程序的同志們,這里簡介一下常見的幾種匯編指令。

A、add:加法指令。第一個是目標操作數。第二個是源操作數,格式為:目標操作數 = 目標操作數 + 源操作數。

B、sub:減法指令。格式同 add;

C、call:調用函數,一般函數的參數放在寄存器中;

D、ret:跳轉會調用函數的地方。

相應於call,返回到相應的call調用的下一條指令。若有返回值。則放入eax中;

E、push:把一個32位的操作數壓入堆棧中,這個操作在32位機中會使得esp被減4(字節),esp通常是指向棧頂的(這里要指出的是:學過單片機的同學請注意單片機種的堆棧與Windows下的堆棧是不同的,請參考相應資料),這里頂部是地址小的區域,那么,壓入堆棧的數據越多,esp也就越來越小

F、pop:與push相反,esp每次加4(字節),一個數據出棧。pop的參數通常是一個寄存器,棧頂的數據被彈出到這個寄存器中。

一般不會把sub、add這樣的算術指令,以及call、ret這樣的跳轉指令歸入堆棧相關指令中。可是實際上在函數參數傳遞過程中,sub和add最經常使用來操作堆棧;call和ret對堆棧也有影響。

 

G、mov:數據傳送。第一個參數是目的操作數,第二個參數是源操作數,就是把源操作數復制到目的一份。

H、xor:異或指令,這本身是一個邏輯運算指令,但在匯編指令中一般會見到它被用來實現清零功能。

              用 xor eax,eax這樣的操作來實現 mov eax,0。能夠使速度更快,占用字節數更少。

I、lea:取得第二個參數地址后放入到前面的寄存器(第一個參數)中

               然而lea也相同能夠實現mov的操作,比如:

                                  lea edi,[ebx-0ch]

方括號表示存儲單元。也就是提取方括號里的數據所指向的內容,然而lea提取內容的地址,這樣就實現了把(ebx-0ch)放入到了edi中,可是mov指令是不支持第二個操作數是一個寄存器減去一個數值的

 

J、stos:串行存儲指令。它實現把eax中的數據放入到edi所指的地址中。同一時候edi后移4個字節,這里的stos實際上相應的是stosd,其它的還有stosb,stosw分別相應1,2個字節。

K、jmp:無條件跳轉指令,相應於大量的條件跳轉指令。

L、jg:條件跳轉。大於時成立。進行跳轉。通常條件跳轉之前會有一條比較指令(用於設置標志位)。

M、jl:小於時跳轉

N、jge:大於等於時跳轉

O、cmp:比較大小指令。結果用來設置標志位。

P 。rep 依據ECX寄存器的值進行反復循環操作

注:

mov ax,[bx]
[ ]表示是間接尋址。bx和[bx]的差別是。前者操作數就是bx中存放的數,后者操作數是以bx中存放的數為地址的單元中的數。比方bx中存放的數是40F6H,40F6H、40F7H兩個單元中存放的數是22H、23H,則
mov ax,[bx]。2223H傳送到ax中
mov ax,bx。40F6H傳送到ax中


ILT是INCREMENTAL LINK TABLE的縮寫。這個@ILT事實上就是一個靜態函數跳轉的表。它記錄了一些函數的入口然后跳過去,每一個跳轉jmp占一個字節,然后就是一個四字節的內存地址,加起為五個字節
比方代碼中有多處地方調用boxer函數,別處的調用也通過這個ILT表的入口來間接調用,而不是直接call 該函數的偏移。這樣在編譯程序時。假設boxer函數更新了,地址變了,僅僅須要改動跳表中的地址就能夠。有利於提高鏈接生成程序的效率。

這個是用在程序的調試階段。當編譯release程序時。就不再用這樣的方法。


我試着將HEX數據改成00 00。相應的匯編指令變成了add byte ptr [eax],al ,反過來,假設將一個地方的匯編指令改成add byte ptr [eax],al ,相應的HEX數據就成了00 00,也就是說,他們是一一相應的。編譯器覺得。00 00 這樣兩個字節寬度的二進制數相應的匯編指令就是add byte ptr [eax],al


dword 雙字 就是四個字節
ptr pointer縮寫 即指針

2  、函數參數傳遞方式

函數調用規則指的是調用者和被調用函數間傳遞參數及返回參數的方法,在Windows上,經常使用的有Pascal方式、WINAPI方式(_stdcall)、C方式(_cdecl)。

A、_cdecl C調用規則:

(a)參數從右到左進入堆棧

(b)在函數返回后,調用者要負責清除堆棧。這樣的調用方式一般會生成較大的可運行程序。

B、_stdcall又稱為WINAPI,調用規則例如以下:

(a)參數從右到左進入堆棧。

(b)被調用的函數在返回前自行清理堆棧,這樣的方式生成的代碼比cdecl小

C、Pascal調用規則(主要用於Win16函數庫中。如今基本不用):

(a)參數從左到右進入堆棧;

(b)被調用的函數在返回前自行清理堆棧。

(c)不支持可變參數的函數調用

第三篇

了解反匯編的一些小知識對於我們在開發軟件時進行編程與調試大有優點,以下以簡介一下反匯編的一些小東西。假設有些解釋有問題的地方,希望大家能夠指出。

1、新建簡單的VC控制台應用程序(對此熟悉的同學能夠略過)

A、打開Microsoft Visual Studio 2010,選擇主菜單“File”

B、選擇子菜單“New”以下的“Project”,打開“New Project”對話框。

C、左邊選擇Visual C++下的win32,右邊選擇Win32 Console Application,然后輸入一個project名,點擊“OK”就可以。在出現的向導中,一切默認。點擊Finish就可以。

D、在出現的編輯區域內會出現以你設定的project名命名的CPP文件。內容例如以下:

      #include "stdafx.h"

      int _tmain(int argc, _TCHAR* argv[])

      {

            return 0;

      }

2、VS查看匯編代碼

A、VC處於調試狀態才干看到匯編指令窗體。因此,能夠在 return 0 上設置一個斷點:把光標移到 return 0 那一行上。然后按下F9鍵設置一個斷點。

B、按下F5鍵進入調試狀態,當程序停在 return 0 這一行上時,打開菜單“Debug”下的“Windows”子菜單,選擇“Disassembly”。

這樣,出現一個反匯編的窗體。顯示以下的信息:

--- d:/my documents/visual studio 2008/projects/casmtest/casmtest/casmtest_main.cpp 
// CAsmTest.cpp : 定義控制台應用程序的入口點。
//

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
00411370  push        ebp  
00411371  mov         ebp,esp 
00411373  sub         esp,0C0h 
00411379  push        ebx  
0041137A  push        esi  
0041137B  push        edi  

0041137C  lea         edi,[ebp-0C0h] 
00411382  mov         ecx,30h 
00411387  mov         eax,0CCCCCCCCh 
0041138C  rep stos    dword ptr es:[edi] 

 return 0;
0041138E  xor         eax,eax 
}
00411390  pop         edi  
00411391  pop         esi  
00411392  pop         ebx  

00411393  mov         esp,ebp 
00411395  pop         ebp  
00411396  ret  

上面就是系統生成的main函數原型,確切的說是_tmain()的反匯編的相關信息。相信學過匯編語言的肯定就能夠了解它所做的操作了。


VC中訪問無效變量出錯原因

我們看上面主函數反匯編后的當中一段代碼例如以下:

0041137C  lea         edi,[ebp-0C0h] 
00411382  mov         ecx,30h 
00411387  mov         eax,0CCCCCCCCh 
0041138C  rep stos    dword ptr es:[edi]

從代碼的表面上看。它是實現把從ebp-0C0h開始的30h個字的空間寫入0CCCCCCCCh。當中eax為四位的數據,這樣能夠計算:

                      0C0h = 30h * 4

也就是把從ebp-0C0h 到ebp之間的空間初始化為0CCCCCCCCh。大家在學習反匯編的過程中會發現。事實上編譯器會依據情況把相應長度的這樣一段作為局部變量的空間,而這里把局部變量區域全都初始化成0CCCCCCCCh也是有其用意的,做VC編程的工作者,特別是剛開始學習的人可能不會對0CCCCCCCCh這個常量陌生。

0cch實際上是int 3指令的機器碼,這是一個斷點中斷指令(在反編譯出的信息中大家會看到int 3),因為局部變量不可被運行,或者假設在沒有初始化的時候進行了訪問,則就會出現訪問失敗錯誤。這個在VC編譯Debug版本號中才干看到提示這個錯誤,在Release版本號中,會以第二種錯誤形式體現。

以下,我們改動主程序看下new與delete的反匯編的效果(凝視直接加到反匯編的代碼中了)。

VC生成project,寫入源碼例如以下:

(1)情況1

// ASM_Test.cpp : Defines the entry point for the console application.                    (  源碼1 )
//
#include "stdafx.h"
#include "stdlib.h"
int _tmain(int argc, _TCHAR* argv[])
{
    int *pTest = new int(3);                //定義一個整型指針,並初始化為 3
    printf( "*pTest = %d/r/n", *pTest );    //調用庫函數printf輸出數據
    delete []pTest;                            //刪除這個指針

    return 0;
}
這里僅僅看下在new與delete進行空間管理時進行反匯編時可能出現的一些情況。我們把上面源碼稱為源碼(1),我們依照前面解說的查看VS下反匯編的方法能夠看到相應於上面代碼的反匯編代碼例如以下:
--- f:/mysource/asm_test/asm_test/asm_test.cpp ---------------------------------                      ( 反匯編代碼 1)
// ASM_Test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdlib.h"

int _tmain(int argc, _TCHAR* argv[])
{

;(1)函數預處理部分
004113C0  push        ebp  
004113C1  mov         ebp,esp ;保存堆棧的棧頂位置
004113C3  sub         esp,0E8h ;要置為0CCCCCCCCh 保留變量空間長度
004113C9  push        ebx       ;保存寄存器ebx、esi、edi
004113CA  push        esi  
004113CB  push        edi  
004113CC  lea         edi,[ebp-0E8h]    ;提出要置為0CCCCCCCCh 的空間起始地址
004113D2  mov         ecx,3Ah      ;要置為0CCCCCCCCh 的個數,每一個占4個字節
004113D7  mov         eax,0CCCCCCCCh  ;於是3Ah * 4 = 0E8h
004113DC  rep stos    dword ptr es:[edi]  ;進行置為0CCCCCCCCh操作


(2)定義一個int 型指針。分配空間后。並初始化為 3 。

    int *pTest = new int(3);                //定義一個整型指針,並初始化為 3
004113DE  push        4    ;要分配的空間長度,會依據定義的數據類型而不同
004113E0  call        operator new (411186h)   ;分配空間。並把分配空間的起始地址放入eax中
004113E5  add         esp,4    ;因為new與delete函數本身沒有對棧進行彈出操作,所以,要編寫者自己處理
004113E8  mov         dword ptr [ebp-0E0h],eax  ;比較分配的空間是否為0,假設為0 
004113EE  cmp         dword ptr [ebp-0E0h],0 
004113F5  je          wmain+51h (411411h) 
004113F7  mov         eax,dword ptr [ebp-0E0h]      ;對於分配的地址分配空間進行賦值為:3
004113FD  mov         dword ptr [eax],3 
00411403  mov         ecx,dword ptr [ebp-0E0h] 
00411409  mov         dword ptr [ebp-0E8h],ecx   ;似乎用[ebp - 0E0h]和[ebp - 0E8h]作為了中間存儲單元
0041140F  jmp         wmain+5Bh (41141Bh) 
00411411  mov         dword ptr [ebp-0E8h],0     ;上面分配空間失敗是的操作
0041141B  mov         edx,dword ptr [ebp-0E8h] 
00411421  mov         dword ptr [pTest],edx           ;數據最后送入pTest變量中

;調用printf函數進行數據輸出
    printf( "*pTest = %d/r/n", *pTest );    //調用庫函數printf輸出數據
00411424  mov         esi,esp   ;用於調用printf后的Esp檢測。不明確編譯器為什么這樣做
00411426  mov         eax,dword ptr [pTest]   ;提取要打印的數據。先是地址。以下一條是提取詳細數據
00411429  mov         ecx,dword ptr [eax] 
0041142B  push        ecx         ;兩個參數入棧
0041142C  push        offset string "*pTest = %d/r/n" (41573Ch) 
00411431  call        dword ptr [__imp__printf (4182C4h)]      ;調用函數
00411437  add         esp,8         ;因為庫函數無出棧管理操作,同new與delete。所以要加 8,進行堆棧處理
0041143A  cmp         esi,esp        ;對堆棧的棧頂進行測試
0041143C  call        @ILT+325(__RTC_CheckEsp) (41114Ah) 

;進行指針變量的清理工作
    delete []pTest;                            //刪除這個指針
00411441  mov         eax,dword ptr [pTest]   ;[pTest] 中放入的是分配的地址,以下幾條指令轉悠一圈
00411444  mov         dword ptr [ebp-0D4h],eax   ;就是要把要清理的地址送入堆棧,然后調用delete函數
0041144A  mov         ecx,dword ptr [ebp-0D4h] 
00411450  push        ecx  
00411451  call        operator delete (411091h) 
00411456  add         esp,4     ;對堆棧進行處理,同new與printf函數

;函數結束后,進行終於的清理工作
    return 0;
00411459  xor         eax,eax   ;做相應的清理工作,堆棧中保存的變量送回原寄存器
}
0041145B  pop         edi  
0041145C  pop         esi  
0041145D  pop         ebx  
0041145E  add         esp,0E8h       ;進行堆棧的棧頂推斷
00411464  cmp         ebp,esp 
00411466  call        @ILT+325(__RTC_CheckEsp) (41114Ah) 
0041146B  mov         esp,ebp 
0041146D  pop         ebp  
0041146E  ret  

--- No source file -------------------------------------------------------------;后面不再是源碼


在列出反匯編程序時把反匯編代碼的上下的分解凝視也列了出來,親手去查看的朋友可能會發如今這段代碼的之外的其它部分會有大量的int 3匯編中的中斷指令,這個是與上面的所說的0CCCCCCCCh具有一致性,這些區域是無效區域,但代碼訪問這些區域時就會出現非法訪問提示

當然,你應該能夠想到。那個提示是能夠被屏蔽掉的,你能夠把這部分區域填充上數據或者改動 iint 3 調用的中斷程序。

從以上反匯編程序,我們能夠發現幾點:

A、一些內部的庫函數是不會對堆棧進行出棧管理的,所以若要對反匯編程序進行操作時,一點要注意這一點

B、編譯器會自己主動的加上一些對棧頂的檢查工作,這個是我們在做VC調試時經常遇到的一個問題。就是堆棧錯誤

當然以上僅僅是對debug版本號下的程序進行反匯編,假設為release 版本號,代碼就會進行大量的優化,在理解時會有一定的難度。有興趣朋友能夠試着反匯編一下,推薦大家有IDA返回工具。感覺挺好用的。







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM