基於VC平臺下C++反彙編與逆向分析研究——No.1

小鄭鄭要飛飛發表於2016-05-27

首先感謝小生我怕怕大神,這個是他的隨筆,作為一個業餘愛好者,確實挺喜歡這門技術,所以做了這個系列的轉載,後面會慢慢更新!

不過時間有限,一邊考研,為了給自己枯燥的生活帶來點樂趣,故選擇隨便看些東西!


————————————————————————————————————————————————

分析環境:WIN7sp1
所用工具:VC++6.0/OllyDBG/IDA
適用人群:有一定計算機基礎,熟悉C/C++程式設計,熟悉X86系列彙編/瞭解OD/IDA等除錯工具使用,對逆向安全有極大興趣者!
————————————————————————————————————————————————

開篇前言:

       1.何為反彙編?簡單來說就是通過讀取可執行檔案的二進位制程式碼,將其還原為彙編程式碼的過程。

   
       2.何為逆向分析? 再簡單來說就是在只有可執行檔案彙編程式碼的情況下,利用逆向思維來還原程式本身的意圖和行為等。

       3.兩者的用處?主要用於軟體破解、WG分析、病毒分析、漏洞分析、軟體逆向、軟體漢化、底層技術等領域。

(注:逆向工程分析不僅僅是一門學科,還是一門藝術,逆向的藝術!想戰勝它往往需要極大費精力、時間、毅力等。這其中還涉及密碼學、社會工程學、數學演算法,以及軟體工程、網路通訊、作業系統底層及硬體等,但付出總是和回報成正比...)

————————————————————————————————————————————————
正文部分:

本帖隱藏的內容

首先了解下VC的兩種編譯方式:Debug和Relese 也就是我們常說的除錯版和釋出版,那具體這兩種編譯方式有什麼區別呢?往下面看:
       Debug 通常稱為除錯版本,它包含除錯資訊,並且不作任何優化,便於程式設計師除錯程式。Release 稱為釋出版本,它往往是進行了各種優化,使得程式在程式碼大小和執行速度上都是最優的,以便使用者很好地使用。
       上面的解釋可能太生硬,那我們就先做個例子來對比下!程式就選擇我們最熟悉的Hello World,下面我們就看下兩個世界的Hello World...
   
原始碼:
————————————————————————————————————————————————

  1. #include<stdio.h>
  2. void main()
  3. {
  4.         printf("Hello World"); 
  5. }
複製程式碼


————————————————————————————————————————————————
我們使用兩種不同的編譯方式編譯生產後載入到OD檢視,先看Debug版本的:載入OD後,停留在00401120處,也就是OEP,先看反彙編程式碼,注意著色地方
————————————————————————————————————————————————
00401120 >/$  55            push ebp                                 ;  程式OEP(Initial CPU selection)
00401121  |.  8BEC          mov ebp,esp
00401123  |.  6A FF         push -0x1
00401125  |.  68 38214200   push Test.00422138
0040112A  |.  68 D0414000   push Test._except_handler3               ;  SE 處理程式安裝
0040112F  |.  64:A1 0000000>mov eax,dword ptr fs:[0]
00401135  |.  50            push eax
00401136  |.  64:8925 00000>mov dword ptr fs:[0],esp
0040113D  |.  83C4 F0       add esp,-0x10
00401140  |.  53            push ebx
00401141  |.  56            push esi
00401142  |.  57            push edi
00401143  |.  8965 E8       mov [local.6],esp
00401146  |.  FF15 4CA14200 call dword ptr ds:[<&KERNEL32.GetVersion>;  kernel32.GetVersion
0040114C  |.  A3 707C4200   mov dword ptr ds:[_osver],eax
00401151  |.  A1 707C4200   mov eax,dword ptr ds:[_osver]
00401156  |.  C1E8 08       shr eax,0x8
00401159  |.  25 FF000000   and eax,0xFF
0040115E  |.  A3 7C7C4200   mov dword ptr ds:[_winminor],eax
00401163  |.  8B0D 707C4200 mov ecx,dword ptr ds:[_osver]
00401169  |.  81E1 FF000000 and ecx,0xFF
0040116F  |.  890D 787C4200 mov dword ptr ds:[_winmajor],ecx
00401175  |.  8B15 787C4200 mov edx,dword ptr ds:[_winmajor]
0040117B  |.  C1E2 08       shl edx,0x8
0040117E  |.  0315 7C7C4200 add edx,dword ptr ds:[_winminor]
00401184  |.  8915 747C4200 mov dword ptr ds:[_winver],edx
0040118A  |.  A1 707C4200   mov eax,dword ptr ds:[_osver]
0040118F  |.  C1E8 10       shr eax,0x10
00401192  |.  25 FFFF0000   and eax,0xFFFF
00401197  |.  A3 707C4200   mov dword ptr ds:[_osver],eax
0040119C  |.  6A 00         push 0x0
0040119E  |.  E8 BD2D0000   call Test._heap_init
004011A3  |.  83C4 04       add esp,0x4
004011A6  |.  85C0          test eax,eax
004011A8  |.  75 0A         jnz short Test.004011B4
004011AA  |.  6A 1C         push 0x1C
004011AC  |.  E8 CF000000   call Test.fast_error_exit
004011B1  |.  83C4 04       add esp,0x4
004011B4  |>  C745 FC 00000>mov [local.1],0x0
004011BB  |.  E8 A0270000   call Test._ioinit
004011C0  |.  FF15 48A14200 call dword ptr ds:[<&KERNEL32.GetCommand>; [GetCommandLineA
004011C6  |.  A3 04964200   mov dword ptr ds:[_acmdln],eax
004011CB  |.  E8 70250000   call Test.__crtGetEnvironmentStringsA
004011D0  |.  A3 487C4200   mov dword ptr ds:[_aenvptr],eax
004011D5  |.  E8 56200000   call Test._setargv
004011DA  |.  E8 011F0000   call Test._setenvp
004011DF  |.  E8 1C1B0000   call Test._cinit
004011E4  |.  8B0D 8C7C4200 mov ecx,dword ptr ds:[_environ]
004011EA  |.  890D 907C4200 mov dword ptr ds:[__initenv],ecx
004011F0  |.  8B15 8C7C4200 mov edx,dword ptr ds:[_environ]
004011F6  |.  52            push edx                    ;第一個引數
004011F7  |.  A1 847C4200   mov eax,dword ptr ds:[__argv]
004011FC  |.  50            push eax                    ;第二個引數
004011FD  |.  8B0D 807C4200 mov ecx,dword ptr ds:[__argc]
00401203  |.  51            push ecx                     ;第三個引數
00401204  |.  E8 FCFDFFFF   call Test.00401005           ;main函式入口點
00401209  |.  83C4 0C       add esp,0xC                              ;  (Initial CPU selection)
0040120C  |.  8945 E4       mov [local.7],eax
0040120F  |.  8B55 E4       mov edx,[local.7]
00401212  |.  52            push edx                                 ; /status
00401213  |.  E8 281B0000   call Test.exit                           ; \exit
————————————————————————————————————————————————

初學者,看到這些是不是很暈?不要緊我們細細品味:

        1.這是VC++6.0典型的入口標誌,大家最好熟記,感興趣的同學可以對比下其他語言編譯器的入口特徵,例如:VB,E語言,Win32Asm等,熟悉入口特徵在以後的逆向脫殼中,對於尋找程式oep也很有幫助!
        2.我們分析的重點在main函式,上面的彙編程式碼主要是VC++6.0初始化啟動的過程,我們大概瞭解下即可,看下我標綠的幾個函式,即啟動函式,這裡借用官方語言瞭解下:
        1.GetVersion函式:獲取當前執行平臺的版本號。
        2._heap_init函式:用於初始化堆空間。在函式實現中使用HeapCreate申請堆空間
        3.GetCommandLineA函式:獲取命令列引數資訊的首地址
        4._crtGetEnvironmentStringA函式:獲取環境變數資訊的首地址
        5._setargv函式:此函式根據GetCommandLineA獲取命令列引數資訊的首地址並進行引數分析
        6._setenvp函式:此函式根據_crtGetEnvironmentStringA函式獲取環境變數資訊的首地址進行分析。
        7._cinit函式:用於全域性變數資料和浮點數暫存器的初始化。

        以上都是編譯器幫我們自動完成,有興趣的同學so一下,越過初始化啟動過程來到main函式,即 00401204 處,很多人問,你怎麼知道這裡是main函式?一般在完成初始化啟動操作以後,下面有3個連續push操作的call就應該是main函式,理由很簡單,main函式有三個引數
        下面開始OD除錯,首先從OEP處一步一步F8跟下去,不要F7(除非你想了解啟動函式具體過程),否則你將迴圈在無盡的call與retn中...我們F8一直執行到main函式處,即00401204處:
————————————————————————————————————————————————
004011F6  |.  52            push edx                    ;第一個引數
004011F7  |.  A1 847C4200   mov eax,dword ptr ds:[__argv]
004011FC  |.  50            push eax                    ;第二個引數
004011FD  |.  8B0D 807C4200 mov ecx,dword ptr ds:[__argc]
00401203  |.  51            push ecx                     ;第三個引數
00401204  |.  E8 FCFDFFFF   call Test.00401005           ;main函式入口點
————————————————————————————————————————————————
        上面的三個連續push就是mian函式的三個引數,這裡不多解釋,這時我們注意2個暫存器的值,首先EIP指向00401204,esp值為0012FF50,OK,這時可以F7了,進去以後,如下:
————————————————————————————————————————————————
00401005  /$ /E9 06000000   jmp Test.main   
0040100A  |  |CC            int3
0040100B  |  |CC            int3
0040100C  |  |CC            int3
0040100D  |  |CC            int3
0040100E  |  |CC            int3
0040100F  |  |CC            int3
00401010 >|> \55            push ebp
00401011  |.  8BEC          mov ebp,esp
00401013  |.  83EC 40       sub esp,40
00401016  |.  53            push ebx
00401017  |.  56            push esi
00401018  |.  57            push edi
00401019  |.  8D7D C0       lea edi,[local.16]
0040101C  |.  B9 10000000   mov ecx,10
00401021  |.  B8 CCCCCCCC   mov eax,CCCCCCCC
00401026  |.  F3:AB         rep stos dword ptr es:[edi]
00401028  |.  68 1C204200   push 42201C                
00401032  |.  83C4 04       add esp,4
00401035  |.  5F            pop edi                                  
00401036  |.  5E            pop esi                                  
00401037  |.  5B            pop ebx                                 
00401038  |.  83C4 40       add esp,40
0040103B  |.  3BEC          cmp ebp,esp
0040103D  |.  E8 9E000000   call 004010E0
00401042  |.  8BE5          mov esp,ebp
00401044  |.  5D            pop ebp                                  
00401045  \.  C3            retn————————————————————————————————————————————————
F7跟進來以後,直接停留到:00401005  /$ /E9 06000000   jmp Test.main  
        好奇的人肯定會問,為什麼不直接指向00401010呢,多一處無條件跳轉為何?這裡應該是考慮到冗餘的問題,畢竟這裡是debug版本,當然利用這個空隙可做很多很多事情...至於為何用int 3來填充,是 因為需要保護這段空間,int 3即中斷
開始詳細分析,先來到第一句彙編指令00401005處,此處是一個無條件跳轉:jmp     _main  此句什麼意思呢?其實這句等價於 jmp  00401010 因為此處的_main只是作為一個地址識別符號,他指向的地址就是00401010處,相當於我們使用暫存器跳轉,如:
mov eax,00401010
jmp eax

反彙編註釋版:
————————————————————————————————————————————————
00401005  /$ /E9 06000000   jmp Test.main            ;這裡無條件跳轉到00401010處
0040100A  |  |CC            int3
0040100B  |  |CC            int3
0040100C  |  |CC            int3    
0040100D  |  |CC            int3
0040100E  |  |CC            int3        
0040100F  |  |CC            int3                                         ;以上空間用int3填充                               
00401010 >|> \55            push ebp                             ;  儲存ebp
00401011  |.  8BEC          mov ebp,esp                       ;  ebp指向棧頂
00401013  |.  83EC 40       sub esp,0x40                     ;  開闢區域性變數空間
00401016  |.  53            push ebx                                ;  ebx入棧儲存
00401017  |.  56            push esi                                 ;  esi入棧儲存
00401018  |.  57            push edi                                 ;  edi入棧儲存,以上幾句用於保護現場
00401019  |.  8D7D C0       lea edi,[local.16]                       ;  設定區域性變數初始化起始地址
0040101C  |.  B9 10000000   mov ecx,0x10                         ;  迴圈次數
00401021  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC      ;  將4個int 3指令,即4個CC放入eax中
00401026  |.  F3:AB         rep stos dword ptr es:[edi]              ;  迴圈拷貝填充區域性空間,以上4句用來CC初始化
00401028  |.  68 1C204200   push Test.0042201C                 ; /Hello World
0040102D  |.  E8 2E000000   call Test.printf                         ; \呼叫printf函式
00401032  |.  83C4 04       add esp,0x4                              ;  呼叫完printf,恢復esp
00401035  |.  5F            pop edi                                  ;  還原edi
00401036  |.  5E            pop esi                                  ;  還原esi
00401037  |.  5B            pop ebx                                  ;  還原ebx
00401038  |.  83C4 40       add esp,0x40                            ;  恢復區域性變數所用空間
0040103B  |.  3BEC          cmp ebp,esp                              ;  檢測棧平衡
0040103D  |.  E8 9E000000   call Test._chkesp                ;  除錯資訊,F7跟進去看看
00401042  |.  8BE5          mov esp,ebp                              ;  恢復esp
00401044  |.  5D            pop ebp                                  ;  恢復ebp
00401045  \.  C3            retn                                     ;  返回主程式,等同ret | add esp,4
————————————————————————————————————————————————
文字敘述: 
push ebp                                   push入棧指令 ,ebp幀指標,意思就是把ebp入棧儲存,(此時esp-4)
mov ebp,esp                              mov傳送指令,esp為棧指標,就是把esp儲存到ebp中,因為下面要用到esp
sub     esp, 40h                         申請區域性變數空間而用,40h的大小根據呼叫子程式中變數多少而不同
連續三句push  也就是我們常說的保護現場,因為子程式中有可能要用到這三個暫存器,需要先儲存起來  
lea edi,[local.16]                        lea是地址傳送指令,此句的意思就是把ebp的地址加上40h放到edi中
mov     ecx, 10h                         ecx一般用於迴圈次數,這裡就是設定迴圈次數
mov     eax, 0CCCCCCCCh       eax賦值為8個C,也就是初始化CC操作,CC彙編指令等於int 3中斷指令
rep stosd                                    迴圈拷貝字串操作,把整個區域設定為CC
push Test.0042201C                 把字串地址指標做為引數入棧
call    _printf                               呼叫printf函式
add     esp, 4                              呼叫完畢,恢復棧操作
連續三個pop 出棧,和之前的3個push相對應,回覆相關暫存器
add     esp, 40h                         對應之前的sub esp,40h   恢復esp的值
cmp ebp,esp                           判斷現在的esp值是否與之前儲存在ebp中的值吻合
call    __chkesp                          棧出錯資訊除錯,下面根據檢視
mov     esp, ebp                         把ebp的值還給esp,還原esp
mov     esp, ebp                         函式呼叫結束,恢復ebp的值
retn                                            返回到主函式

這裡有一個call    __chkesp 函式來顯示除錯資訊, 跟進去看下:
————————————————————————————————————————————————
004010E0 >/$ /75 01         jnz short Test.004010E3  
004010E2  |. |C3            retn
004010E3  |> \55            push ebp
004010E4  |.  8BEC          mov ebp,esp
004010E6  |.  83EC 00       sub esp,0x0
004010E9  |.  50            push eax
004010EA  |.  52            push edx
004010EB  |.  53            push ebx
004010EC  |.  56            push esi
004010ED  |.  57            push edi
004010EE  |.  68 5C204200   push Test.0042205C                       ;  The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
004010F3  |.  68 58204200   push Test.00422058
004010F8  |.  6A 2A         push 0x2A
004010FA  |.  68 48204200   push Test.00422048                       ;  i386\chkesp.c
004010FF  |.  6A 01         push 0x1
00401101  |.  E8 5A150000   call Test._CrtDbgReport
00401106  |.  83C4 14       add esp,0x14
00401109  |.  83F8 01       cmp eax,0x1
0040110C  |.  75 01         jnz short Test.0040110F
0040110E  |.  CC            int3
0040110F  |>  5F            pop edi
00401110  |.  5E            pop esi
00401111  |.  5B            pop ebx
00401112  |.  5A            pop edx
00401113  |.  58            pop eax
00401114  |.  8BE5          mov esp,ebp
00401116  |.  5D            pop ebp
00401117  \.  C3            retn
————————————————————————————————————————————————
        首先是一個jnz跳轉,此跳轉根據zp標誌位來決定,也就是之前的CMP比較命令,如果esp不等於ebp就發生跳轉,彈出除錯資訊視窗,如果相等就直接retn返回到main函式。想知道程式是如何彈出除錯資訊視窗的同學,可以再跟一下,都是一些系統內部函式,也好理解,好了,Debug版本解析到此為止!
————————————————————————————————————————————————

分析過debug版本,再來看看Release 版本,你會感覺到震驚...直接Release 編譯生成後載入OD,來到main入口處:
————————————————————————————————————————————————
004010E3  |.  50            push eax
004010E4  |.  FF35 20994000 push dword ptr ds:[0x409920]        
004010EA  |.  FF35 1C994000 push dword ptr ds:[0x40991C]
004010F0  |.  E8 0BFFFFFF   call Test.00401000        ;main函式入口
————————————————————————————————————————————————
可以看出,main函式的三個引數被直接簡化為三個直接的push,F7跟進去看看:
————————————————————————————————————————————————
00401000  /$  68 30704000   push Test.00407030                       ;  Hello World
00401005  |.  E8 06000000   call Test.00401010
0040100A  |.  59            pop ecx
0040100B  \.  C3            retn
————————————————————————————————————————————————
       怎麼樣,是不是感覺很精簡,push入棧printf函式的一個引數,直接使用call呼叫printf,這裡的pop ecx 等價於 add esp,4,再回頭看下兩種版本的對比:
        Debug 通常稱為除錯版本,它包含除錯資訊,並且不作任何優化,便於程式設計師除錯程式。Release 稱為釋出版本,它往往是進行了各種優化,使得程式在程式碼大小和執行速度上都是最優的,以便使用者很好地使用。
       一切盡在不言中,慢慢品味...

相關文章