逆向第十九講——類繼承和成員類、運算符重載、模板逆向20171211
阿新 • • 發佈:2017-12-12
指針 emp 繼承方式 virtual n) stp 定義 調試 不同的 一、類繼承逆向
在C++中使用到繼承,主要是為了實現多態,那麽多態就必須會用到虛函數,即會產生虛表指針。
(1)父類和子類中有沒用到虛函數的四種情形
1)父類和子類中都沒有用到虛函數
如果父類和子類中都沒有用到虛函數,那麽子類中就只是繼承了父類中的成員變量和成員函數,當然還得視父類中成員變量和成員函數的公有私有性質和繼承方式而定,在此繼承中有一種特殊形式,當父類和子類中含有同名同參數同返回值的函數時,用父類對象指針調該函數,則調的是父類中的該函數,用子類對象指針調該函數時,調的是子類中的而該函數,類似的當父類子類中都含有同名同類型的成員變量時,用各自的類型指針調各自的成員變量,這裏嚴格上說就沒用上繼承的了,因為子類自己也有,對於父類中和子類中同名的成員變量,不會是合並,而是各自存放在各自的對象範疇中,當然父類對象包含在子類對象內。
2)父類有申明虛函數、子類中沒申明虛函數
這種情形下,子類對象中自然也是有虛表的,調試時可發現有虛表覆蓋的過程,如果子類中有同名同參同返回值的函數,那麽子類虛表中相應偏移處函數指針就是子類中的那個同名同參同返回值的函數指針,如果沒有同名同參同返回值的函數,那麽子類虛表中相應偏移處函數指針就是父類中相應函數的指針。子類中沒析構函數,父類中沒析構函數,那麽父類中沒有默認析構函數,父類中有析構函數,子類中會有默認析構函數。對於構造函數也是一樣,子類中有,父類中沒默認,父類中,子類中會有默認析構函數。當一個父類中有虛函數時,並且沒有構造和析構函數時,子類對象定義時會有默認構造,無默認析構。
3)父類沒有申明虛函數、子類中有申明虛函數
這種情形下,父類對象中會沒有虛表指針。
4)父類和子類中都有用到虛函數
類似2),父類和子類中都會有虛表指針。
(2)類的構造和析構函數中是否調用虛函數
類的構造和析構函數中一般不調用虛函數,因為父類和子類各自構造和析構自己,所以沒必要使用虛函數。從粗略的角度分析,當在父類的構造函數中調用虛函數時,會調到子函數的成員函數去了,但這時子函數還沒有構造,但析構父類時,又回調到子函數的析構函數,這時子類已經析構掉了,當然從編譯器的操作上是表面了這種情形發生的,因為在父類構造和析構時都會填一次自己的虛表指針,即不會出現隱患,這是編譯器做的防止隱患的一招。當構造和析構函數中調用虛函數時,會直接使用虛函數的指針,不會經過虛表指針。
class(3)父類和成員類的區別 如果父類、成員類和子類中都沒有虛表,則當結構體處理。對於有虛表的情形,得從以下三點進行識別:1)虛表指針個數,2)初始化時機,3)各虛表覆蓋的情況。汗一個父類、一個成員類的情形各類主要情況如下: 1)父類、成員類和子類中都有虛表 父類的虛表指針最先初始化,再次是成員類初始化,初始偏移從緊接父類和成員類在子類中前邊成員偏移開始,緊接的是其它的子類成員,父類的虛表指針被覆蓋,析構時反向。 2)父類中無虛表、子類中都有虛表 構造時,對象首四個字節會騰出來給子類填虛表指針,父類構造時,從子類對象地址的後四個字節開始。 3)其它的情形都比較好認識,當有多重繼承和多個成員類時,直接用遞歸的方法。在對象首四個字節下寫入斷點時,可以看到虛表指針被覆蓋多少次,被覆蓋多少次就有多少重繼承。有的時候,父類和成員類沒法分清,那麽這是還原成哪一種都行。在逆向C++時,得想對類成員函數,不管是虛函數和是非成員函數(野成員函數), 進行建模,建模好之後,再分發逆向。建模也是一個很重要的過程,加上這一過程,就相當於是逆向工程。 二、運算符重載和模板 運算符重載和模板是分辨不出來的,只能還原成相應的函數,當然可以根據自己分析的情況,進行還原成運算符重載和模板。CParent { public: CParent() { Show(); } ~CParent(); virtual void Show() { printf("class CParent"); } int m_nInt; }; //匯編代碼 15: Show(); 004010D0 mov ecx,dword ptr [ebp-4] 004010D3 call @ILT+35(CParent::Show) (00401028) //普通指針調用虛函數Show(); 11: pobj->Show(); 0040109E mov edx,dword ptr [ebp-10h] 004010A1 mov eax,dword ptr [edx] 004010A3 mov esi,esp 004010A5 mov ecx,dword ptr [ebp-10h] 004010A8 call dword ptr [eax] 004010AA cmp esi,esp
intoperator+(CChild1 obj1, CChild1 obj2) { return obj1.m_nInt + obj2.m_nInt; }
;17: int m = obj1 + obj2; 004011DF sub esp,0Ch 004011E2 mov ecx,esp 004011E4 mov dword ptr [ebp-38h],esp 004011E7 lea edx,[ebp-28h] 004011EA push edx 004011EB call @ILT+45(CChild1::CChild1) (00401032) ;18: m = operator+(obj1, obj2); 00401226 sub esp,0Ch 00401229 mov ecx,esp 0040122B mov dword ptr [ebp-40h],esp 0040122E lea edx,[ebp-28h] 00401231 push edx 00401232 call @ILT+45(CChild1::CChild1) (00401032)
template<typename T> T My_Add(T m, T n) { return m + n; } 20: My_Add(1, 2); //兩個不同的函數 00401158 push 2 0040115A push 1 0040115C call @ILT+25(My_Add) (0040101e) 00401161 add esp,8 21: My_Add(1.0, 2.0); //兩個不同的函數 00401164 push 40000000h 00401169 push 0 0040116B push 3FF00000h 00401170 push 0 00401172 call @ILT+10(My_Add) (0040100f) 00401177 fstp st(0)
對於運算符的書寫順序分中綴式、波蘭式、逆波蘭式,中綴式在數學書中公式常用,波蘭式在編譯器中常用,逆波蘭式在公式文字描述中用的較多,各式轉換方式如下:
1.中綴式 a + b/c - d*e; 2.中綴式轉波蘭式,先按序轉換成指令 sub(add(a, div(b,c)), mul(d,e)) -+a/bc*de 即為波蘭式 3.文字描述 (a與(b、c之商)之和)與(d、e之積)的差 abc/+de*- 即為逆波蘭式三.純虛函數怎麽實現 VC6.0中通過19號錯誤來實現:
;__purecall proc near ; DATA XREF: .rdata:const CParent::`vftable‘o .text:004018A0 push ebp .text:004018A1 mov ebp, esp .text:004018A3 push 19h .text:004018A5 call __amsg_exit .text:004018A5 __purecall endp 34: { 004018A0 push ebp 004018A1 mov ebp,esp 35: _amsg_exit(_RT_PUREVIRT); 004018A3 push 19h 004018A5 call _amsg_exit (004019e0) 004018AA add esp,4 36: }
;VS2013中虛函數編譯器操作,用了DecodePointer和_abort sub_40115E proc near ; DATA XREF: .rdata:off_40D154o .text:0040115E push Ptr ; Ptr .text:00401164 call ds:DecodePointer .text:0040116A test eax, eax .text:0040116C jz short loc_401170 .text:0040116E call eax .text:00401170 .text:00401170 loc_401170: ; CODE XREF: sub_40115E+Ej .text:00401170 push 1 .text:00401172 push 0 .text:00401174 call sub_402CCD .text:00401179 pop ecx .text:0040117A pop ecx .text:0040117B jmp _abort .text:0040117B sub_40115E endp
逆向第十九講——類繼承和成員類、運算符重載、模板逆向20171211