基於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...
原始碼:
————————————————————————————————————————————————
-
#include<stdio.h>
-
void main()
-
{
-
printf("Hello World");
- }
————————————————————————————————————————————————
我們使用兩種不同的編譯方式編譯生產後載入到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 稱為釋出版本,它往往是進行了各種優化,使得程式在程式碼大小和執行速度上都是最優的,以便使用者很好地使用。
一切盡在不言中,慢慢品味...
相關文章
- C++反彙編基礎:Win32平臺(逆向例項、動態除錯例項)2014-08-01C++Win32除錯
- iOS逆向之旅(基礎篇) — 彙編(五) — 彙編下的Block2018-10-25iOSBloC
- iOS逆向之旅(基礎篇) — 彙編(四) — 彙編下的函式2018-10-25iOS函式
- iOS逆向之旅(基礎篇) — 彙編(二) — 彙編下的 IF語句2018-10-25iOS
- 基於arm的C++反彙編 qemu-arm環境搭建2016-05-20C++
- iOS逆向之旅(基礎篇) — 彙編(三) — 彙編下的 Switch語句2018-10-25iOS
- iOS逆向之旅(基礎篇) — 彙編(一)— 彙編基礎2018-10-25iOS
- iOS逆向-彙編基礎(一)2018-11-05iOS
- C++ 反彙編:關於函式呼叫約定2022-02-17C++函式
- x64dbg(程式逆向反彙編修改神器)2022-03-29
- C++ 反彙編:關於Switch語句的優化措施2022-01-28C++優化
- 《C++反彙編與逆向分析技術揭祕》讀書總結——從記憶體角度看繼承2018-10-06C++記憶體繼承
- 逆向之彙編筆記2020-12-30筆記
- 反彙編命令U2018-03-22
- iOS逆向學習筆記 - 彙編(一) - 初識彙編2018-05-16iOS筆記
- Java 反彙編、反編譯、volitale解讀2018-08-14Java編譯
- Mac平臺反編譯Unity編譯的安卓apk2017-09-06Mac編譯Unity安卓APK
- 安卓逆向之Luac解密反編譯2018-08-03安卓解密編譯
- 逆向學習筆記3——暫存器與彙編指令2018-04-27筆記
- 編譯安裝基於nginx與lua的高效能web平臺-openresty2018-04-10編譯NginxWebREST
- 反彙編專用工具——objdump2018-11-18OBJ
- 基於 Serverless 的部署平臺構建與思考2020-01-18Server
- 關於 VC 編譯的猜想與試驗 (轉)2007-08-14編譯
- Android平臺下基於XMPP的IM研究(一)2014-08-06Android
- 反彙編器-javap.exe(轉)2007-08-15Java
- 彙編基礎2024-04-28
- 【JS 逆向百例】網洛者反爬練習平臺第一題:JS 混淆加密,反 Hook 操作2021-12-14JS加密Hook
- 微服務平臺下基於 GraphQL 構建 BFF 的思考2021-11-30微服務
- 基於Android平臺的RouterSDK設計與實現2017-02-23Android
- 基於Linux平臺的Domino Server安裝與配置2010-02-02LinuxServer
- 反彙編工具objdump的使用簡介2016-03-27OBJ
- 基於Saltstack、Artifactory打造傳統模式下持續部署平臺2020-01-14模式
- 逆向平臺Binary Ninja介紹2018-01-17
- 安卓平臺編寫C++演算法SDK流程2021-01-05安卓C++演算法
- C++跨平臺庫boost和Poco的編譯2024-05-28C++編譯
- VC在windows下編寫用於序列通訊的程式 (轉)2007-12-04Windows
- 關於VC的編譯模式 (轉)2008-06-09編譯模式
- mingw32 exception在sjlj與dwarf差別-反彙編分析2020-06-16Exception