个简单C++程序反汇编解析 (Rev. 3)
如果想要了解C++內部的實現原理,沒有什么比觀察C++代碼對應的匯編代碼來的更直接了。本系列主要從匯編角度研究C++代碼和匯編的對應關系,揭示C++內部的機制和原理。在第一篇文章中我將從一個簡單的C++程序著手快速解釋一下C++反匯編代碼的基本的結構和內容,相當于一個簡單的Preview。而在后續的文章中,我將根據不同的Topic,詳細解釋C++代碼對應的反匯編代碼。
一個簡單的C++程序示例如下:
| class my_class { public : ????my_class() ????{ ????????m_member = 1; ????} ? ????void method(int n) ????{ ????????m_member = n; ????} ? ????~my_class() ????{ ????????m_member = 0; ????} ? private : ????int m_member; }; ? int _tmain(int argc, _TCHAR* argv[]) { ????my_class a_class; ????a_class.method(10); ? ????return 0; } |
可以直接Debug的時候看到Assembly代碼,不過這樣獲得的代碼注釋比較少。比較理想的方法是利用VC編譯器的一個選項/FAs來生成對應的匯編代碼。/FAs還會在匯編代碼中加入注釋注明和C++代碼的對應關系,十分有助于分析。在VS2005中可以這樣打開/FAs:
Build代碼,可以在輸出目錄下發現對應的.ASM文件。本文將逐句分析匯編代碼和C++的對應關系。
首先是WinMain:
| _TEXT?SEGMENT _wmain??????PROC ??????push??ebp?????????????????????????????????;?保存舊的ebp ??????mov???ebp, esp????????????????????????????; ebp保存當前棧的位置 ??????push??-1??????????????????????????????????;?建立SEH(Structured Exception Handler)鏈 ????????????????????????????????????????????????; -1表示表頭,沒有Prev ??????push??__ehhandler$_wmain??????????????????; SEH異常處理程序的地址 ??????mov???eax, DWORD PTR fs:0?????????????????; fs:0指向TEB的內容,頭4個字節是當前SEH鏈的地址 ??????push??eax?????????????????????????????????;?保存起來 ??????sub???esp, d8H????????????????????????????;?分配d8H字節的空間 ??????push??ebx ??????push??esi ??????push??edi ??????lea???edi, DWORD PTR [ebp-e4H]????????????; 確定局部變量的起始地址。e4H = d8H + 4 * 3,跳過之前建立SEH鏈所用的3個Push指令所占用的棧的空間,以及sub esp, d8h為局部變量分配的d8H字節空間 ??????mov???ecx, 36H????????????????????????????; 36H*4H=d8H,也就是用36H個ccccccccH填滿剛才分配的d8H字節空間 ??????mov???eax, ccccccccH ??????rep stosd ??????mov???eax, DWORD PTR ___security_cookie?? ??????xor???eax, ebp??? ??????push??eax?????????????????????????????????; ebp ^ __security_cookie壓棧保存 ??????lea???eax, DWORD PTR [ebp-0cH]????????????; ebp-0cH是之前main的起始代碼中在堆棧中建立的SEH結構的首地址 ??????mov???DWORD PTR fs:0, eax?????????????????;?設置到TEB中作為當前Active的SEH鏈表末尾 |
到此為止棧的內容是這樣的:
低地址
| Security cookie after XOR |
| Edi |
| Esi |
| Ebx |
| Local stack: d8H |
| Old fs:0 |
| __ehhandler$_wmain |
| ffffffffH |
| Old ebp |
高地址
main接著后面調用my_class的構造函數
| ??????lea???ecx, DWORD PTR [ebp-14H] ??????call????0my_class@@QAE@XZ?????????????????;?調用my_class::my_class, ??my_class@@QAE@XZ是經過Name Mangling后的名字 ??????mov???DWORD PTR [ebp-4], 0????????????????;?進入__try塊,在Main中有一個隱式的__try/__except塊 |
接著調用my_class::method:
| ??????push??10??????????????????????????????????;?參數入棧 ??????lea???ecx, DWORD PTR [ebp-14H]????????????;?遵循thiscall調用協定,ecx存放的是this指針 ??????call???method@my_class@@QAEXH@Z???????????;?調用子程序my_class:method(10) |
之后是析構:
| ??????mov???DWORD PTR [ebp-e0H], 0??????????????;?用來放置返回值 ??????mov???DWORD PTR [ebp-4], -1???????????????;?標記TRY的正常結束 ??????lea???ecx, DWORD PTR [ebp-14H]????????????; a_class的地址作為this存入ECX ??????call????1my_class@@QAE@XZ?????????????????; my_class::~my_class ??????mov???eax, DWORD PTR [ebp-e0H]????????????;?返回值按照約定放入eax中 |
Main函數退出代碼如下:
| ??????push??edx ??????mov???ecx, ebp ??????push??eax ??????lea???edx, DWORD PTR $LN7@wmain ??????call??@_RTC_CheckStackVars@8??????????????;?檢查棧 ??????pop???eax ??????pop???edx ??????mov???ecx, DWORD PTR [ebp-0cH]????????????;?取出之前保存的舊的fs:0,并恢復 ??????mov???DWORD PTR fs:0, ecx ??????pop???ecx ??????pop???edi ??????pop???esi ??????pop???ebx ??????add???esp, e4H????????????????????????????;?退掉分配的d8H +?建立SEH鏈所需的0cH字節 ??????cmp???ebp, esp ??????call??__RTC_CheckEsp??????????????????????;?檢查esp值,這個時候esp應該和ebp匹配,否則說明出現了棧不平衡的情況,這種情況下調用子程序報錯 ??????mov???esp, ebp????????????????????????????;?恢復ebp到esp ??????pop???ebp?????????????????????????????????;?恢復原來的ebp值 ??????ret???0 _wmain??????ENDP |
專門用于SEH的子程序。__unwindfunclet$_wmain$0當異常發生的時候被調,負責進行棧展開,主要是調用析構函數。__ehhandler$_wmain則是在exception被拋出的時候調用。
| Text$x??????SEGMENT __unwindfunclet$_wmain$0:???????????????????????;?當SEH發生的時候會調用該函數,析購a_class ??????lea???ecx, DWORD PTR [ebp-14H]????????????; ecx = [ebp – 14H],也就是a_class的地址 ??????jmp?????1my_class@@QAE@XZ?????????????????;?調用my_class::~my_class __ehhandler$_wmain: ??????mov???edx, DWORD PTR [esp+8]??????????????; esp =?當前的fs:0, [esp + 8] =?之前的SEH結構,也就是main中建立的 ??????lea???eax, DWORD PTR [edx+0cH]????????????; edx + 0Ch =?當前的ebp,也就是main的ebp,此時不能直接使用ebp因為可能會從任意函數調過來,此時ebp是該函數的ebp,而不是main的ebp ??????mov???ecx, DWORD PTR [edx-e0H]????????????;?之前存下去的__security_cookie ^ ebp ??????xor???ecx, eax????????????????????????????;?再次和ebp相異或 ??????call??@__security_check_cookie@4??????????;?此時ecx應該等于__security_cookie,否則說明棧的內容被惡意改動(或者編程錯誤) ??????mov???eax, OFFSET __ehfuncinfo$_wmain ??????jmp???___CxxFrameHandler3 text$x??????ENDS |
My_class::my_class構造函數如下。構造函數本質上就是一個全局函數,名字是經過打亂的(Name Mangling),這樣可以和同一Class和其他Class的同名方法區別開來。不同編譯器有不同規則,因此不必過于深究。
| _TEXT?SEGMENT ??0my_class@@QAE@XZ PROC ??????push??ebp?????????????????????????????????;?保存舊的ebp ??????mov???ebp, esp????????????????????????????; ebp保存當前棧的位置 ??????sub???esp, ccH????????????????????????????;?給棧分配ccH個字節 ??????push??ebx?????????????????????????????????;?保存常用寄存器 ??????push??esi ??????push??edi ??????push??ecx ??????lea???edi, DWORD PTR [ebp-ccH]????????????;?從分配的位置開始 ??????mov???ecx, 33H????????????????????????????;?寫33H個ccccccccH ??????mov???eax, ccccccccH??????????????????????;?也就是33H*4H=ccH,正好是分配的大小 ??????rep stosd?????????????????????????????????;?從而把整個棧上當前分配的空間用ccH填滿 ??????pop???ecx ??????mov???DWORD PTR [ebp-8], ecx??????????????;?按照約定,一般用ECX保存this指針 ????????????????????????????????????????????????;?把this存入到ebp-8,并不是很必要,因為這是Debug版本 ??????????????????????????????????????????????? ? ; 10???:?????{ ; 11???:?????????m_member = 1; ? ??????mov???eax, DWORD PTR [ebp-8]??????????????; eax中存放this ??????mov???DWORD PTR [eax], 1??????????????????; this的頭四個byte是m_member的內容 ? ; 12???:?????} ? ??????mov???eax, DWORD PTR [ebp-8]??????????????;?多余的一句話,可以優化掉 ??????pop???edi ??????pop???esi ??????pop???ebx ??????mov???esp, ebp????????????????????????????;?恢復esp,因此就算是中間棧運算出錯,最后也不會導致災難性的結果,只要ebp還是正確的 ??????pop???ebp ??????ret???0 ??0my_class@@QAE@XZ ENDP |
My_class::method的實現如下:
| _TEXT?SEGMENT ?method@my_class@@QAEXH@Z PROC??????????????????; my_class::method ? ; 15???:?????{ ? ??????push??ebp ??????mov???ebp, esp ??????sub???esp, ccH ??????push??ebx ??????push??esi ??????push??edi ??????push??ecx ??????lea???edi, DWORD PTR [ebp-ccH] ??????mov???ecx, 33H ??????mov???eax, ccccccccH ??????rep stosd ??????pop???ecx ??????mov???DWORD PTR [ebp-8], ecx ? ; 16???:?????????m_member = n; ? ??????mov???eax, DWORD PTR [ebp-8]??????????????; eax中存放this ??????mov???ecx, DWORD PTR [ebp+8]??????????????; ebp -> ebp ????????????????????????????????????????????????; ebp + 4 -> IP ????????????????????????????????????????????????; ebp + 8 -> n ????????????????????????????????????????????????;?把n存入ecx中 ??????mov???DWORD PTR [eax], ecx????????????????; this頭四個字節是m_member,?因此這句話就是m_member = n ? ; 17???:?????} ? ??????pop???edi ??????pop???esi ??????pop???ebx ??????mov???esp, ebp ??????pop???ebp ??????ret???4???????????????????????????????????;?等價于 ????????????????????????????????????????????????; ret?恢復EIP,返回調用地址 ????????????????????????????????????????????????; add esp, 4 ->?把n從棧上Pop掉 ?method@my_class@@QAEXH@Z ENDP |
最后的析構函數,和前面的代碼并無區別。
| _TEXT??SEGMENT ??1my_class@@QAE@XZ PROC????????????????????????; my_class::~my_class ? ; 20???:?????{ ? ???????push???ebp ???????mov????ebp, esp ???????sub????esp, 204????????????????????????? ???????push???ebx ???????push???esi ???????push???edi ???????push???ecx ???????lea????edi, DWORD PTR [ebp-204] ???????mov????ecx, 33H????????????????????????? ???????mov????eax, ccccccccH??????????????????? ???????rep stosd ???????pop????ecx ???????mov????DWORD PTR _this$[ebp], ecx ? ; 21???:?????????m_member = 0; ? ???????mov????eax, DWORD PTR [ebp-8] ???????mov????DWORD PTR [eax], 0 ? ; 22???:?????} ? ???????pop????edi ???????pop????esi ???????pop????ebx ???????mov????esp, ebp ???????pop????ebp ???????ret????0 ??1my_class@@QAE@XZ ENDP????????????????????????; my_class::~my_class _TEXT??ENDS |
?
總結
以上是生活随笔為你收集整理的个简单C++程序反汇编解析 (Rev. 3)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解C程序内存布局
- 下一篇: 永远不要相信用户的输入