成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

網絡安全編程:C語言逆向之函數的識別

安全
下面借助IDA來分析由VC6編譯連接C語言的代碼,從而來學習掌握逆向的基礎知識。

 [[391373]]

在學習編程的過程中,需要閱讀大量的源代碼才能提高自身的編程能力。同樣,在做產品的時候也需要大量參考同行的軟件才能改善自己產品的不足。如果發現某個軟件的功能非常不錯,是自己急需融入自己軟件產品的功能,而此時又沒有源代碼可以參考,那么程序員唯一能做的只有通過逆向分析來了解其實現方式。除此之外,當使用的某個軟件存在 Bug,而該軟件已經不再更新時,程序員能做的并不是去尋找同類的其他軟件,而是可以通過逆向分析來自行修正其軟件的 Bug,從而很好地繼續使用該軟件。逆向分析程序的原因很多,有些情況不得不進行逆向分析,比如病毒分析、漏洞分析等。

可能病毒分析、漏洞分析等高深技術對于有些人來說目前還無法達到,但是其基礎知識部分都離不開逆向知識。下面借助IDA來分析由VC6編譯連接C語言的代碼,從而來學習掌握逆向的基礎知識。

1. 簡單的C語言函數調用程序

為了方便介紹關于函數的識別,這里寫一個簡單的C語言程序,用VC6進行編譯連接。C語言的代碼如下: 

  1. #include <stdio.h>  
  2. #include <windows.h>  
  3. int test(char *szStr, int nNum)  
  4.  
  5.   printf("%s, %d \r\n", szStr, nNum);  
  6.   MessageBox(NULL, szStr, NULL, MB_OK);  
  7.   return 5;  
  8.  
  9. int main(int argc, char ** argv)  
  10.  
  11.   int nNum = test("hello", 6);  
  12.   printf("%d \r\n", nNum);  
  13.   return 0;  

在程序代碼中,自定義函數test()由主函數main()所調用,test()函數的返回值為int類型。在test()函數中調用了printf()函數和MessageBox()函數。將代碼在VC6下使用DEBUG方式進行編譯連接來生成一個可執行文件,對該可執行文件通過IDA進行逆向分析。

以上代碼的擴展名為“.c”,而不是“.cpp”。這里用來進行逆向分析的例子均使用DEBUG方式在VC6下進行編譯連接。

2. 函數逆向分析

大多數情況下程序員都是針對自己比較感興趣的程序部分進行逆向分析,分析部分功能或者部分關鍵函數。因此,確定函數的開始位置和結束位置非常重要。不過通常情況下,函數的起始位置和結束位置都可以通過反匯編工具自動識別,只有在代碼被刻意改變后才需要程序員自己進行識別。IDA可以很好地識別函數的起始位置和結束位置,如果在逆向分析的過程中發現有分析不準確的時候,可以通過Alt + P快捷鍵打開“Edit function”(編輯函數)對話框來調整函數的起始位置和結束位置。“Edit function”對話框的界面如圖1所示。在圖1中,被選中的部分可以設定函數的起始地址和結束地址。

圖1  “Edit function”對話框

用IDA打開VC6編譯好的程序,在打開的時候,IDA會有一個提示,如圖2所示。該圖詢問是否使用PDB文件。PDB文件是程序數據庫文件,是編譯器生成的一個文件,方便程序調試使用。PDB包含函數地址、全局變量的名字和地址、參數和局部變量的名字和在堆棧的偏移量等很多信息。這里選擇“Yes”按鈕。

圖2  提示是否使用PDB文件

在分析其他程序的時候,通常沒有PDB文件,那么這里會選擇“No”按鈕。在有PDB和無PDB文件時,IDA的分析結果是截然不同的。請大家在自己分析時,嘗試對比不加載編譯器生成的PDB文件和加載了PDB文件IDA生成的反匯編代碼的差異。

當IDA完成對程序的分析后,IDA直接找到了main()函數的跳表項,如圖3所示。

圖3  main()函數的跳表

所謂main()函數的跳表項,意思是這里并不是main()函數的真正的起始位置,而是該位置是一個跳表,用來統一管理各個函數的地址。從圖3中看到,有一條jmp _main的匯編代碼,這條代碼用來跳向真正的main()函數的地址。在IDA中查看圖3上下位置,可能只能找到這么一條跳轉指令。在圖3的靠下部分有一句注釋為“[00000005 BYTES: COLLAPSED FUNCTION j__test. PRESS KEYPAD "+" TO EXPAND]”。這里是可以展開的,在該注釋上單擊右鍵,出現右鍵菜單后選擇“Unhide”項,則可以看到被隱藏的跳表項,如圖4所示。

圖4  展開后的跳表

在實際的反匯編代碼時,jmp _main和jmp _test是緊挨著的兩條指令,而且jmp后面是兩個地址。這里的顯示函數形式、_main和_test是由IDA進行處理的。在OD下觀察跳表的形式,如圖5所示。

圖5  OD中跳表的指令位置

并不是每個程序都能被IDA識別出跳轉到main()函數的跳表項,而且程序的入口點也并非main()函數。首先來看一下程序的入口函數位置。在IDA上單擊窗口選項卡,選擇“Exports”窗口(Exports窗口是導出窗口,用于查看導出函數的地址,但是對于EXE程序來說通常是沒有導出函數的,這里將顯示EXE程序的入口函數),在“Exports”窗口中可以看到_mainCRTStartup,如圖6所示。

圖6  Exports窗口

雙擊_mainCRTStartup就可以到達啟動函數的位置了。在C語言中,main()不是程序運行的第一個函數,而是程序員編寫程序時的第一個函數,main()函數是由啟動函數來調用的。現在看一下

  1. _mainCRTStartup函數的部分反匯編代碼:  
  2. .text:004011D0 public _mainCRTStartup  
  3. .text:004011D0 _mainCRTStartup proc near  
  4. .text:004011D0  
  5. .text:004011D0 Code = dword ptr -1Ch  
  6. .text:004011D0 var_18 = dword ptr -18h  
  7. .text:004011D0 var_4 = dword ptr -4  
  8. .text:004011D0  
  9. .text:004011D0 push ebp  
  10. .text:004011D1 mov ebp, esp  
  11. .text:004011D3 push 0FFFFFFFFh  
  12. .text:004011D5 push offset stru_422148  
  13. .text:004011DA push offset __except_handler3  
  14. .text:004011DF mov eax, large fs:0  
  15. .text:004011E5 push eax  
  16. .text:004011E6 mov large fs:0, esp  
  17. .text:004011ED add esp, 0FFFFFFF0h  
  18. .text:004011F0 push ebx  
  19. .text:004011F1 push esi  
  20. .text:004011F2 push edi  
  21. .text:004011F3 mov [ebp+var_18], esp  
  22. .text:004011F6 call ds:__imp__GetVersion@0 ; GetVersion()  
  23. .text:004011FC mov __osver, eax  
  24. .text:00401201 mov eax, __osver  
  25. .text:00401206 shr eax, 8  
  26. .text:00401209 and eax, 0FFh  
  27. .text:0040120E mov __winminor, eax  
  28. .text:00401213 mov ecx, __osver  
  29. .text:00401219 and ecx, 0FFh  
  30. .text:0040121F mov __winmajor, ecx  
  31. .text:00401225 mov edx, __winmajor  
  32. .text:0040122B shl edx, 8  
  33. .text:0040122E add edx, __winminor  
  34. .text:00401234 mov __winver, edx  
  35. .text:0040123A mov eax, __osver  
  36. .text:0040123F shr eax, 10h  
  37. .text:00401242 and eax, 0FFFFh  
  38. .text:00401247 mov __osver, eax  
  39. .text:0040124C push 0  
  40. .text:0040124E call __heap_init  
  41. .text:00401253 add esp, 4  
  42. .text:00401256 test eax, eax  
  43. .text:00401258 jnz short loc_401264  
  44. .text:0040125A push 1Ch  
  45. .text:0040125C call fast_error_exit 
  46. .text:00401261; ------------------------------------------------  
  47. .text:00401261 add esp, 4  
  48. .text:00401264  
  49. .text:00401264 loc_401264: ; CODE XREF: _mainCRTStartup+88j  
  50. .text:00401264 mov [ebp+var_4], 0  
  51. .text:0040126B call __ioinit  
  52. .text:00401270 call ds:__imp__GetCommandLineA@0 ; GetCommandLineA()  
  53. .text:00401276 mov __acmdln, eax  
  54. .text:0040127B call ___crtGetEnvironmentStringsA  
  55. .text:00401280 mov __aenvptr, eax  
  56. .text:00401285 call __setargv  
  57. .text:0040128A call __setenvp 
  58. .text:0040128F call __cinit  
  59. .text:00401294 mov ecx, __environ  
  60. .text:0040129A mov ___initenv, ecx  
  61. .text:004012A0 mov edx, __environ  
  62. .text:004012A6 push edx  
  63. .text:004012A7 mov eax, ___argv  
  64. .text:004012AC push eax  
  65. .text:004012AD mov ecx, ___argc 
  66. .text:004012B3 push ecx  
  67. .text:004012B4 call _main_0  
  68. .text:004012B9 add esp, 0Ch  
  69. .text:004012BC mov [ebp+Code], eax  
  70. .text:004012BF mov edx, [ebp+Code]  
  71. .text:004012C2 push edx ; Code  
  72. .text:004012C3 call _exit  
  73. .text:004012C3 _mainCRTStartup endp 

從反匯編代碼中可以看到,main()函數的調用在004012B4位置處。啟動函數從004011D0地址處開始,期間調用GetVersion()函數獲得了系統版本號、調用__heap_init函數初始化了程序所使用的堆空間、調用GetCommandLineA()函數獲取了命令行參數、調用___crtGetEnviro nmentStringsA函數獲得了環境變量字符串……在完成一系列啟動所需的工作后,終于在004012B4處調用了_main_0。由于這里使用的是調試版且有PDB文件,因此在反匯編代碼中直接顯示出程序中的符號,在分析其他程序時是沒有PDB文件的,這樣_main_0就會顯示為一個地址,而不是一個符號。不過依然可以通過規律來找到_main_0所在的位置。

沒有PDB文件,如何找到_main_0所在的位置呢?在VC6中,啟動函數會依次調用GetVersion()、GetCommandLineA()、GetEnvironmentStringsA()等函數,而這一系列函數即是一串明顯的特征。在調用完GetEnvironmentStringsA()后,不遠處會有3個push操作,分別是main()函數的3個參數,代碼如下: 

  1. .text:004012A0 mov edx, __environ  
  2. .text:004012A6 push edx  
  3. .text:004012A7 mov eax, ___argv  
  4. .text:004012AC push eax  
  5. .text:004012AD mov ecx, ___argc  
  6. .text:004012B3 push ecx  
  7. .text:004012B4 call _main_0 

該反匯編代碼對應的C代碼如下: 

  1. #ifdef WPRFLAG  
  2.   __winitenv = _wenviron 
  3.   mainret = wmain(__argc, __wargv, _wenviron);  
  4. #else /* WPRFLAG */  
  5.   __initenv = _environ 
  6.   mainmainret = main(__argc, __argv, _environ);  
  7. #endif /* WPRFLAG */ 

該部分代碼是從CRT0.C中得到的,可以看到啟動函數在調用main()函數時有3個參數。

接著上面的內容,在3個push操作后的第1個call處,即是_main_0函數的地址。往_main_0下面看,_main_0后地址為004012C3的指令為call _exit。確定了程序是由VC6編寫的,那么找到對_exit的調用后,往上找一個call指令就找到_main_0所對應的地址。大家可以依照該方法進行測試。

在順利找到_main_0函數后,直接雙擊反匯編的_main_0,到達函數跳轉表處。在跳轉表中雙擊_main,即可到真正的_main函數的反匯編代碼處。_main函數的返匯編代碼如下: 

  1. .text:004010A0 _main proc near ; CODE XREF: _main_0j  
  2. .text:004010A0  
  3. .text:004010A0 var_44 = byte ptr -44h  
  4. .text:004010A0 var_4 = dword ptr -4  
  5. .text:004010A0  
  6. .text:004010A0 push ebp  
  7. .text:004010A1 mov ebp, esp  
  8. .text:004010A3 sub esp, 44h  
  9. .text:004010A6 push ebx  
  10. .text:004010A7 push esi  
  11. .text:004010A8 push edi  
  12. .text:004010A9 lea edi, [ebp+var_44]  
  13. .text:004010AC mov ecx, 11h  
  14. .text:004010B1 mov eax, 0CCCCCCCCh  
  15. .text:004010B6 rep stosd  
  16. .text:004010B8 push 6  
  17. .text:004010BA push offset aHello ; "hello"  
  18. .text:004010BF call j__test  
  19. .text:004010C4 add esp, 8  
  20. .text:004010C7 mov [ebp+var_4], eax  
  21. .text:004010CA mov eax, [ebp+var_4]  
  22. .text:004010CD push eax  
  23. .text:004010CE push offset aD ; "%d \r\n"  
  24. .text:004010D3 call _printf  
  25. .text:004010D8 add esp, 8  
  26. .text:004010DB xor eax, eax  
  27. .text:004010DD pop edi 
  28. .text:004010DE pop esi  
  29. .text:004010DF pop ebx  
  30. .text:004010E0 add esp, 44h  
  31. .text:004010E3 cmp ebp, esp  
  32. .text:004010E5 call __chkesp  
  33. .text:004010EA mov esp, ebp  
  34. .text:004010EC pop ebp  
  35. .text:004010ED retn  
  36. .text:004010ED _main endp 

短短幾行C語言代碼,在編譯連接生成可執行文件后,再進行反匯編竟然生成了比C語言代碼多很多的代碼。仔細觀察上面的反匯編代碼,通過特征可以確定這是寫的主函數,首先代碼中有一個對test()函數的調用在004010BF地址處,其次有一個對printf()函數的調用在004010D3地址處。_main函數的入口部分代碼如下: 

  1. .text:004010A0 push ebp  
  2. .text:004010A1 mov ebp, esp  
  3. .text:004010A3 sub esp, 44h  
  4. .text:004010A6 push ebx  
  5. .text:004010A7 push esi  
  6. .text:004010A8 push edi  
  7. .text:004010A9 lea edi, [ebp+var_44]  
  8. .text:004010AC mov ecx, 11h  
  9. .text:004010B1 mov eax, 0CCCCCCCCh  
  10. .text:004010B6 rep stosd 

大多數函數的入口處都是push ebp/mov ebp, esp/sub esp, ×××這樣的形式,這幾句代碼完成了保存棧幀,并開辟了當前函數所需的棧空間。push ebx/push esi/push edi是用來保存幾個關鍵寄存器的值,以便函數返回后這幾個寄存器中的值還能在調用函數處繼續使用而沒有被破壞掉。lea edi, [ebp + var_44]/mov ecx, 11h/move ax , 0CCCCCCCCh/rep stosd,這幾句代碼是開辟的內存空間,全部初始化為0xCC。0xCC被當作機器碼來解釋時,其對應的匯編指令為int 3,也就是調用3號斷點中斷來產生一個軟件中斷。將新開辟的棧空間初始化為0xCC,這樣做的好處是方便調試,尤其是給指針變量的調試帶來了方便。

以上反匯編代碼是一個固定的形式,唯一會發生變化的是sub esp, ×××部分,在當前反匯編代碼處是sub esp, 44h。在VC6下使用Debug方式編譯,如果當前函數沒有變量,那么該句代碼是sub esp, 40h;如果有一個變量,其代碼是sub esp, 44h;有兩個變量時,為sub esp, 48h。也就是說,通過Debug方式編譯時,函數分配棧空間總是開辟了局部變量的空間后又預留了40h字節的空間。局部變量都在棧空間中,棧空間是在進入函數后臨時開辟的空間,因此局部變量在函數結束后就不復存在了。與函數入口代碼對應的代碼當然是出口代碼,其代碼如下: 

  1. .text:004010DD pop edi  
  2. .text:004010DE pop esi  
  3. .text:004010DF pop ebx  
  4. .text:004010E0 add esp, 44h  
  5. .text:004010E3 cmp ebp, esp  
  6. .text:004010E5 call __chkesp  
  7. .text:004010EA mov esp, ebp  
  8. .text:004010EC pop ebp 
  9. .text:004010ED retn  
  10. .text:004010ED _main endp 

函數的出口部分(或者是函數返回時的部分)也屬于固定格式,這個格式跟入口的格式基本是對應的。首先是pop edi/pop esi/pop ebx,這里是將入口部分保存的幾個關鍵寄存器的值進行恢復。push和pop是對堆棧進行操作的指令。堆棧結構的特點是后進先出,或先進后出。因此,在函數的入口部分的入棧順序是push ebx/push esi/push edi,出棧順序則是倒序pop edi/pop esi/pop ebx。恢復完寄存器的值后,需要恢復esp指針的位置,這里的指令是add esp, 44h,將臨時開辟的棧空間釋放掉(這里的釋放只是改變寄存器的值,其中的數據并未清除掉),其中44h也是與入口處的44h對應的。從入口和出口改變esp寄存器的情況可以看出,棧的方向是由高地址向低地址方向延伸的,開辟空間是將esp做減法操作。mov esp, ebp/pop ebp是恢復棧幀,retn就返回上層函數了。在該反匯編代碼中還有一步沒有講到,也就是cmp ebp, esp/call __chkesp,這兩句是對__chkesp函數的一個調用。在Debug方式下編譯,對幾乎所有的函數調用完成后都會調用一次__chkesp。該函數的功能是用來檢查棧是否平衡,以保證程序的正確性。如果棧不平,會給出錯誤提示。這里做個簡單的測試,在主函數的return語句前加一條內聯匯編__asm push ebx(只要是改變esp或ebp寄存器值的操作都可以達到效果),然后編譯連接運行,在輸出后會看到一個錯誤的提示,如圖7所示。

圖7  調用__chkesp后對棧平衡進行檢查后的出錯提示

圖7就是__chkesp函數在檢測到ebp與esp不平時給出的提示框。該功能只在DEBUG版本中存在。

主函數的反匯編代碼中還有一部分沒有介紹,反匯編代碼如下: 

  1. .text:004010B8 push 6  
  2. .text:004010BA push offset aHello ; "hello"  
  3. .text:004010BF call j__test  
  4. .text:004010C4 add esp, 8  
  5. .text:004010C7 mov [ebp+var_4], eax  
  6. .text:004010CA mov eax, [ebp+var_4]  
  7. .text:004010CD push eax  
  8. .text:004010CE push offset aD ; "%d \r\n"  
  9. .text:004010D3 call _printf  
  10. .text:004010D8 add esp, 8 
  11. .text:004010DB xor eax, eax 

首先幾條反匯編代碼是push 6/push offset aHello/call j_test/add esp, 8/mov [ebp+var_ 4], eax,這幾條反匯編代碼是主函數對test()函數的調用。函數參數的傳遞可以選擇寄存器或者內存。由于寄存器數量有限,幾乎大部分函數調用都是通過內存進行傳遞的。當參數使用完成后,需要把參數所使用的內存進行回收。對于VC開發環境而言,其默認的調用約定方式是cdecl。這種函數調用約定對參數的傳遞依靠棧內存,在調用函數前,會通過壓棧操作將參數從右往左依次送入棧中。在C代碼中,對test()函數的調用形式如下: 

  1. int nNum = test("hello", 6); 

而對應的反匯編代碼為push 6 / push offset aHello / call j_test。從壓棧操作的push指令來看,參數是從右往左依次入棧的。當函數返回時,需要將參數使用的空間回收。這里的回收,指的是恢復esp寄存器的值到函數調用前的值。而對于cdecl調用方式而言,平衡堆棧的操作是由函數調用方來做的。從上面的反匯編代碼中可以看到反匯編代碼add esp, 8,它是用于平衡堆棧的。該代碼對應的語言為調用函數前的兩個push操作,即函數參數入棧的操作。

函數的返回值通常保存在eax寄存器中,這里的返回值是以return語句來完成的返回值,并非以參數接收的返回值。004010C7地址處的反匯編代碼mov [ebp+var_4], eax是將對j_test調用后的返回值保存在[ebp + var_4]中,這里的[ebp + var_4]就相當于C語言代碼中的nNum變量。逆向分析時,可以在IDA中通過快捷鍵N來完成對var_4的重命名。

在對j_test調用完成并將返回值保存在var_4中后,緊接著push eax/push offset aD/call _printf/add esp, 8的反匯編代碼應該就不陌生了。而最后面的xor eax, eax這句代碼是將eax進行清0。因為在C語言代碼中,main()函數的返回值為0,即return 0;,因此這里對eax進行了清0操作。

雙擊004010BF地址處的call j__test,會移到j_test的函數跳表處,反匯編代碼如下: 

  1. .text:0040100A j__test proc near ; CODE XREF: _main+1Fp  
  2. .text:0040100A jmp _test  
  3. .text:0040100A j__test endp 

雙擊跳表中的_test,到如下反匯編處: 

  1. .text:00401020 ; int __cdecl test(LPCSTR lpText, int)  
  2. .text:00401020 _test proc near ; CODE XREF: j__testj  
  3. .text:00401020  
  4. .text:00401020 var_40 = byte ptr -40h  
  5. .text:00401020 lpText = dword ptr 8  
  6. .text:00401020 arg_4 = dword ptr 0Ch  
  7. .text:00401020  
  8. .text:00401020 push ebp  
  9. .text:00401021 mov ebp, esp  
  10. .text:00401023 sub esp, 40h  
  11. .text:00401026 push ebx  
  12. .text:00401027 push esi  
  13. .text:00401028 push edi  
  14. .text:00401029 lea edi, [ebp+var_40]  
  15. .text:0040102C mov ecx, 10h  
  16. .text:00401031 mov eax, 0CCCCCCCCh  
  17. .text:00401036 rep stosd  
  18. .text:00401038 mov eax, [ebp+arg_4]  
  19. .text:0040103B push eax  
  20. .text:0040103C mov ecx, [ebp+lpText]  
  21. .text:0040103F push ecx  
  22. .text:00401040 push offset Format ; "%s, %d \r\n"  
  23. .text:00401045 call _printf  
  24. .text:0040104A add esp, 0Ch  
  25. .text:0040104D mov esi, esp  
  26. .text:0040104F push 0 ; uType  
  27. .text:00401051 push 0 ; lpCaption  
  28. .text:00401053 mov edx, [ebp+lpText]  
  29. .text:00401056 push edx ; lpText  
  30. .text:00401057 push 0 ; hWnd  
  31. .text:00401059 call ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x)  
  32. .text:0040105F cmp esi, esp  
  33. .text:00401061 call __chkesp  
  34. .text:00401066 mov eax, 5 
  35. .text:0040106B pop edi  
  36. .text:0040106C pop esi 
  37. .text:0040106D pop ebx  
  38. .text:0040106E add esp, 40h  
  39. .text:00401071 cmp ebp, esp  
  40. .text:00401073 call __chkesp  
  41. .text:00401078 mov esp, ebp  
  42. .text:0040107A pop ebp  
  43. .text:0040107B retn  
  44. .text:0040107B _test endp 

該反匯編代碼的開頭部分和結尾部分,這里不再重復,主要看一下中間的反匯編代碼部分。中間的部分主要是printf()函數和MessageBoxA()函數的反匯編代碼。

調用printf()函數的反匯編代碼如下: 

  1. .text:00401038 mov eax, [ebp+arg_4]  
  2. .text:0040103B push eax  
  3. .text:0040103C mov ecx, [ebp+lpText]  
  4. .text:0040103F push ecx  
  5. .text:00401040 push offset Format ; "%s, %d \r\n"  
  6. .text:00401045 call _printf  
  7. .text:0040104A add esp, 0Ch 

調用MessageBoxA()函數的反匯編代碼如下: 

  1. .text:0040104F push 0 ; uType  
  2. .text:00401051 push 0 ; lpCaption  
  3. .text:00401053 mov edx, [ebp+lpText] 
  4. .text:00401056 push edx ; lpText  
  5. .text:00401057 push 0 ; hWnd  
  6. .text:00401059 call ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x) 

比較以上簡單的兩段代碼會發現很多不同之處,首先在調用完_printf后會有add esp, 0Ch的代碼進行平衡堆棧,而調用MessageBoxA后沒有。為什么對MessageBoxA函數的調用則沒有呢?原因在于,在Windows系統下,對API函數的調用都遵循的函數調用約定是stdcall。對于stdcall這種調用約定而言,參數依然是從右往左依次被送入堆棧,而參數的平棧是在API函數內完成的,而不是在函數的調用方完成的。在OD中看一下MessageBoxA函數在返回時的平棧方式,如圖8所示。

圖8  MessageBoxA函數的平棧操作

從圖8中可以看出,MessageBoxA函數在調用retn指令后跟了一個10。這里的10是一個16進制數,16進制的10等于10進制的16。而在為MessageBoxA傳遞參數時,每個參數是4字節,4個參數等于16字節,因此retn 10除了有返回的作用外,還包含了add esp, 10的作用。

上面兩段反匯編代碼中除了平衡堆棧的不同外,還有另外一個明顯的區別。在調用printf時的指令為call _printf,而調用MessageBoxA時的指令為call ds:__imp__MessageBoxA@16。printf()函數在stdio.h頭文件中,該函數屬于C語言的靜態庫,在連接時會將其代碼連接入二進制文件中。而MessageBoxA函數的實現在user32.dll這個動態連接庫中。在代碼中,這里只留了進入MessageBoxA函數的一個地址,并沒有具體的代碼。MessageBoxA的具體地址存放在數據節中,因此在反匯編代碼中給出了提示,使用了前綴“ds:”。“__imp__”表示導入函數。MessageBoxA后面的“@16”表示該API函數有4個參數,即16 / 4 = 4。

多參的API函數仍然在調用方進行平棧,比如wsprintf()函數。原因在于,被調用的函數無法具體明確調用方會傳遞幾個參數,因此多參函數無法在函數內完成參數的堆棧平衡工作。

stdcall是Windows下的標準函數調用約定。Windows提供的應用層及內核層函數均使用stdcall的調用約定方式。cdecl是C語言的調用函數約定方式。

3. 結語

在逆向分析函數時,首先需要確定函數的起始位置,這通常會由IDA自動進行識別(識別不準確的話,就只能手動識別了);其次需要掌握函數的調用約定和確定函數的參數個數,確定函數的調用約定和參數個數都是通過平棧的方式和平棧時對esp操作的值來進行判斷的;最后就是觀察函數的返回值,這部分通常就是觀察eax的值,由于return通常只返回布爾類型、數值類型相關的值,因此通過觀察eax的值可以確定返回值的類型,確定了返回值的類型后,可以進一步考慮函數調用方下一步的動作。 

 

責任編輯:龐桂玉 來源: 計算機與網絡安全
相關推薦

2021-04-14 15:53:58

網絡安全C語言wcslen

2021-04-13 11:15:54

網絡安全C語言循環結構

2021-04-08 11:10:22

網絡安全C語言if…else…

2021-03-31 11:35:00

網絡安全OllyDbg分析工具

2021-05-08 11:50:59

網絡安全API函數代碼

2021-04-01 10:40:22

網絡安全軟件

2016-10-10 00:18:27

2021-03-24 09:46:46

網絡安全軟件反匯編

2011-03-17 13:32:45

2021-05-24 11:55:55

網絡安全Windows鉤子函數

2021-03-03 12:20:42

網絡安全DLL編程

2021-02-07 10:55:01

網絡安全文件API

2021-12-28 00:11:40

網絡安全攻擊

2021-05-21 12:52:47

網絡安全Android App虛擬機

2022-01-05 00:05:07

安全設備網絡

2021-04-30 18:50:44

網絡安全PE編程添加節區

2021-04-26 10:32:38

網絡安全PE編程工具

2022-10-08 07:30:17

網絡安全編程語言C++

2021-03-05 13:46:56

網絡安全遠程線程

2021-01-26 13:45:03

網絡安全Winsock編程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人精品一区二区 | 欧美乱码精品一区二区三区 | 国产精品视频久久 | 久久福利| 一级一级一级毛片 | 欧美日韩大陆 | 久草视频在线播放 | 最新国产精品精品视频 | 日本一区二区三区在线观看 | 精品欧美激情在线观看 | 日韩靠逼 | 午夜精品久久久久久 | 日韩欧美一区二区三区四区 | 久久国产精品72免费观看 | aaaaa毛片| 中文字幕av在线 | 精品一区二区免费视频 | 午夜精品久久久久久不卡欧美一级 | 欧美日韩中文字幕在线 | 国产一区日韩在线 | 欧美国产日韩一区二区三区 | 精品一二区 | 久久久区 | 91国内精精品久久久久久婷婷 | 亚洲成人免费视频在线观看 | 欧美久操网 | 日韩视频免费看 | 日韩一三区 | 欧美1区| 国产片网站 | 亚洲 中文 欧美 日韩 在线观看 | 99久热 | 精品国产一区二区国模嫣然 | 久久香蕉精品视频 | 免费视频色 | 91精品久久久久久久久久入口 | 精品乱码一区二区三四区 | 欧美成人第一页 | 亚洲一区二区三区高清 | 日韩一二区| xxx国产精品视频 |