C++反彙編-繼承和多重繼承

學無止盡,積土成山,積水成淵-《C++反彙編與逆向分析技術揭祕》 讀書筆記ios

1、單類繼承

  • 在父類中聲明爲私有的成員,子類對象沒法直接訪問,可是在子類對象的內存結構中,父類私有的成員數據依然存在。C++語法規定的訪問限制僅限於編譯層面,在編譯過程當中進行語法檢查,所以訪問控制不會影響對象的內存結構。
  • 子類未提供構造函數或析構函數,而父類卻須要構造函數與析構函數時,編譯器會爲子類提供默認的構造函數與析構函數。可是子類有構造函數,而父類不存在構造函數,且沒有虛函數,則編譯器不會爲父類提供默認的構造函數。
 

1. 內存結構:

①先安排父類的數據
②後安排子類新定義的數據
 
說明:基於上述的內存排列方法,即父類數據成員被安排前面。無論是父類的對象,仍是子類的對象,父類的數據成員在內存中相對於對象的首地址的偏移值都是同樣的。並且成員數據訪問都是基於this指針間接尋址的。因此,對於子類對象而言,使用父類指針或者子類指針均可以正確訪問其父類數據。
 

2. 虛表

  • 虛表的排列順序是按虛函數在類繼承層次中首次聲明的順序依次排列的。
  • 只要繼承了父類,其派生類的虛函數表中的父類部分的排列就與父類同樣。
  • 重載的虛函數在虛函數表中獲得了更新(即override)。
  • 子類新定義的虛函數會按照聲明順序緊跟其後。
 
 

3. 構造函數

①先調用父類構造函數
②而後按照聲明順序調用成員數據變量的構造函數和初始化列表中指定的成員
③最後再執行子類構造函數的函數體。
 
說明
①父類構造函數,虛表指針修改成指向父類的虛表,因此在父類構造函數內調用虛函數,調用的是父類的虛函數。
②子類構造函數,虛表指針修改成指向子類的虛表
 

4. 析構函數

①先調用子類析造函數
②而後成員對象的析構函數,按照聲明的順序以倒序方式依次調用成員對象的析構函數。
③再執行父類構造函數
 
說明
  • 析構函數執行會首先設置虛表指針爲自身虛表,再調用自身的析構函數。防止父類析構函數內調用子類對象的虛函數。
  • 類有派生與繼承關係,須要聲明析構函數爲虛函數。若析構函數不是虛函數時,當使用父類指針指向堆中的子類對象,並使用delete釋放對象空間時,編譯器會按照指針類型調用父類的析構函數,從而引起錯誤。
 
識別類之間的關係:
先定位構造函數,根據構造前後順序獲得與之相關的其餘類。
再根據虛表,利用IDA中使用引用參考功能可獲得全部的構造和析構函數。
 
 

2、多重繼承

1. 內存排列:

  • 數據成員的排列順序由繼承父類的前後順序所決定,從左向右依次排列。
  • 子類虛表指針的個數取決於所繼承的父類的個數,有幾個父類便對應幾個虛表指針(虛基類除外)。

 2.虛表

  • 每一個父類都有一個虛表,有幾個父類便對應幾個虛表;
  • 子類的成員函數被放到了第一個父類的表中;
  • 若是某個父類的虛函數都被子類重寫(override),則該父類的虛表中會更新爲子類重寫(override)後的函數地址。

 3.其餘ide

  • 將一個子類對象賦值給某個父類指針時,該父類指針便指向該父類所對應的虛表指針。

3、單類繼承與多重繼承比較:

  • 單繼承類
    • 在類對象佔用的內存空間中,只保存一份虛表指針
    • 只有一個虛表指針,對應的也只有一個虛表
    • 虛表中各項保存了類中各虛函數的首地址
    • 構造時先構造父類,再構造自身,而且只調用一次父類構造函數
    • 析構時限析構自身,再析構父類,而且只調用一次父類析構函數

  • 多重繼承類
    • 在類中所佔用的內存空間中,根據繼承父類的個數保存對應的虛表指針
    • 根據所保存的虛表指針的個數,對應產生相應個數的虛表。
    • 轉換父類指針時,須要跳轉到對象的首地址。
    • 構造時須要按照繼承順序調用多個父類構造函數。
    • 析構時先析構自身,而後以與構造函數相反的順序調用全部父類的析構函數
    • 當對象做爲成員時,整個類對象的內存結構和多重繼承類似。當類中無虛函數時,整個類對象內存結構和多重繼承徹底同樣,可酌情還原;當父類或成員對象存在虛函數時,經過過觀察虛表指針的位置和構造函數、析構函數中填寫虛表指針的數目及目標地址,來還原繼承或成員關係。

4、 示例

1. 單類繼承:

C++源碼
 1 #include <iostream>
 2 using namespace std;
 3  
 4 class Base {
 5 public:
 6             Base(){ nBase= 1;printf("CBase"); }
 7             ~Base(){ printf("~CBase"); }
 8             virtual void f() { printf("Base:f()");}
 9             virtual void g() { printf("Base:g()");}
10 private:
11             int nBase;
12  
13 };
14  
15 
16 class Derive : public Base {
17 public:
18             Derive(){ nDerive=2;printf("Derive"); }
19             ~Derive(){ printf("~Derive"); }
20             virtual void g(){ printf("Dervie:g()");}
21             virtual void h(){ printf("Dervie:h()");}
22 private:
23             int nDerive;
24 };
25  
26  
27  
28 int main()
29 {
30             Derive d;
31             Base *b = &d;
32             b->g();
33             return 0;

34 } 函數

 彙編代碼(VS2010編譯)

 1. 內存分佈佈局

1 類Derive對象 2 0019FD30  0139583C  =>.rdata:const Derive::`vftable'
3 0019FD34 00000001 =>Base.nBase 4 0019FD38 00000002 =>Derive.nDerive 5  
6 虛函數表 7 0139583C 01391163 Base::f(void) 8 01395840 0139110E Derive::g(void) ;Derive類重寫(override)後的函數地址 9 01395844 013911AE Derive::h(void) ;Derive類重寫(override)後的函數地址

 2. 構造函數this

1 pop     ecx                    ;=>this指針出棧
2 mov     [ebp+this], ecx        ;=>保存this指針
3 mov ecx, [ebp+this] 4 call    j_??0Base@@QAE@XZ      ;=>調用基類構造函數Base::Base(void)
5 mov     eax, [ebp+this]        ;=>eax=this指針
6 mov     dword ptr [eax], offset ??_7Derive@@6B@ ;=>初始化虛表指針爲const Derive::`vftable'

 

3. 析構函數spa

 1 pop     ecx                    ;=>this指針出棧
 2 mov     [ebp+this], ecx        ;=>保存this指針
 3 mov eax, [ebp+this]  4 mov     dword ptr [eax], offset ??_7Derive@@6B@  ;=>重置虛表指針爲const Derive::`vftable'
 5 mov esi, esp  6 push    offset aDerive  ; "~Derive"
 7 call    ds:__imp__printf  8 add     esp, 4
 9 cmp esi, esp 10 call j___RTC_CheckEsp 11 mov     ecx, [ebp+this]        ;=>ecx傳參this指針 
12 call    j_??1Base@@QAE@XZ      ;=>調用基類析構函數 Base::~Base(void)

 

2. 多重繼承: 

C++源碼
 1 #include <iostream>  2 using namespace std;  3  
 4 class Base1 {  5 public:
 6             virtual void f() { cout << "Base1::f" << endl; }
 7             virtual void g() { cout << "Base1::g" << endl; }
 8             Base1(){b1 = 1; printf("Base1"); }
 9             ~Base1(){ printf("~Base1"); }
10 private:
11             int b1; 12  
13 }; 14  
15 class Base2 { 16 public:
17             virtual void f() { cout << "Base2::f" << endl; }
18             virtual void g() { cout << "Base2::g" << endl; }
19             Base2(){b2 = 2; printf("Base2"); }
20             ~Base2(){ printf("~Base2"); }
21 private:
22             int b2; 23 }; 24  
25 class Derive : public Base1, public Base2{ 26 public:
27             virtual void f() { cout << "Derive::f" << endl; }
28             virtual void g1() { cout << "Derive::g1" << endl; }
29             Derive(){ d1 = 3; printf("Derive"); }
30             ~Derive(){ printf("~Derive"); }
31 private:
32             int d1; 33             
34 }; 35  
36 typedef void(*Fun)(void); 37  
38 int main() 39 { 40  
41             Derive d; 42             Base1 *b1 = &d; 43             b1->f(); 44             b1->g(); 45             Base2 *b2 = &d; 46             b2->f(); 47             b2->g(); 48             return 0; 49 } 
彙編代碼(VS2010編譯) 

 1.內存分佈指針

;內存佈局
0019FA0C  008F584C    ;=>.rdata:const Derive::`vftable'{for `Base1'} 第一個虛表
0019FA10  00000001    ;=>Base1.b1
0019FA14  008F583C    ;=>.rdata:const Derive::`vftable'{for `Base2'} 第二個虛表
0019FA18  00000002    ;=>Base2.b2
0019FA1C  00000003    ;=>Derive.d1
 
 
;第一個虛函數表Derive::`vftable'{for `Base1'}
00FB584C  00FB1041  ;=>Derive::f(void), Dervie類重寫(override)後的函數地址
00FB5850  00FB1118  ;=>Base1::g(void)
00FB5854  00FB111D  ;=>Derive::g1(void), Derive類中新添加的虛函數地址
 
;第二個虛函數表Derive::`vftable'{for `Base2'}
00FB583C  00FB1113   ;=>Base2::g(void)
 00FB5840  00FB1028  ;=>[thunk]:Derive::f`adjustor{8}' (void)
;追蹤地址:00FB1028
00FB1028 jmp     ?f@Derive@@W7AEXXZ    ;=>[thunk]:Derive::f`adjustor{8}' (void)
;追蹤函數:?f@Derive@@W7AEXXZ
00FB1F30 ?f@Derive@@W7AEXXZ proc near 00FB1F30 sub     ecx, 8                 ;=>調整this指針爲this+8,即ecx-->Derive::`vftable'{for `Base2'}
00FB1F33 jmp     j_?f@Derive@@UAEXXZ    ;=>Derive::f(void),即Derive類重寫後的函數地址

 

 

2.構造函數code

 1 00FB14D9 mov     [ebp-4], ecx            ;=>this指針保存在esp-4處
 2 00FB14DC mov     ecx, [ebp-4]           ;=>ecx得到this指針
 3 00FB14DF call    j_??0Base1@@QAE@XZ      ;=>調用構造函數 Base1::Base1(void)
 4 00FB14E4 mov     ecx, [ebp-4]           ;=>ecx得到this指針
 5 00FB14E7 add     ecx, 8                 ;=>ecx得到this+8
 6 00FB14EA call    j_??0Base2@@QAE@XZ      ;=>調用構造函數 Base2::Base2(void)
 7 00FB14EF mov     eax, [ebp-4]            ;=>eax得到this指針
 8 00FB14F2 mov     dword ptr [eax], offset ??_7Derive@@6BBase1@@@      ;=>初始化第一個虛表指針爲const Derive::`vftable'{for `Base1'}
 9 00FB14F8 mov     eax, [ebp-4]            ;=>eax得到this指針
10 00FB14FB mov     dword ptr [eax+8], offset ??_7Derive@@6BBase2@@@    ;=>初始化第二個虛表指針const Derive::`vftable'{for `Base2'}
11 00FB1502 mov     eax, [ebp-4]            ;=>eax得到this指針
12 00FB1505 mov     dword ptr [eax+10h], 3
13 00FB150C push    offset Format   ; "Derive"
14 00FB1511 call    ds:__imp__printf 15 00FB1517 add     esp, 4

3.析構函數orm

00FB17C9 mov     [ebp-4], ecx        ;=>this指針保存在esp-4處
00FB17CC mov     eax, [ebp-4]        ;=>ecx得到this指針
00FB17CF mov     dword ptr [eax], offset ??_7Derive@@6BBase1@@@        ;=>重置第一個虛表指針爲const Derive::`vftable'{for `Base1'}
00FB17D5 mov     eax, [ebp-4] 00FB17D8 mov     dword ptr [eax+8], offset ??_7Derive@@6BBase2@@@      ;=>重置第二個虛表指針爲const Derive::`vftable'{for `Base2'}
00FB17DF push    offset aDerive  ; "~Derive"
00FB17E4 call    ds:__imp__printf 00FB17EA add     esp, 4
00FB17ED mov     ecx, [ebp-4]             ;=>ecx得到this指針
00FB17F0 add     ecx, 8                   ;=>ec;得this+8
00FB17F3 call    j_??1Base2@@QAE@XZ       ;=>調用虛構函數Base2::~Base2(void)
00FB17F8 mov     ecx, [ebp-4]             ;=>ecx得到this指針
00FB17FB call    j_??1Base1@@QAE@XZ       ;=>調用虛構函數Base1::~Base1(void)

 

4.虛函數調用對象

 

;Base1 *b1 = &d;
00FB1431 lea     eax, [ebp-14h]        ;=>eax得到this指針
00FB1434 mov     [ebp-18h], eax        ;=>局部變量b1賦值爲this指針

;b1->f();
00FB1437 mov     eax, [ebp-18h]        ;=>eax得到b1值
00FB143A mov     edx, [eax]            ;=>edx指向第一個虛表
00FB143C mov     ecx, [ebp-18h]        ;=>ecx傳遞this指針
00FB143F mov     eax, [edx]            ;=>eax得到成員函數Derive::f(void)的地址
00FB1441 call    eax                   ;=>調用成員函數Derive::f(void)

;b1->g();
00FB1443 mov     eax, [ebp-18h]        ;=>eax得到b1值
00FB1446 mov     edx, [eax]            ;=>edx指向第一個虛表
00FB1448 mov     ecx, [ebp-18h]        ;=>ecx傳遞this指針
00FB144B mov     eax, [edx+4]          ;=>eax得到成員函數Derive::g(void)的地址
00FB144E call    eax                   ;=>調用成員函數Derive::g(void)

;Base2 *b2 = &d;
00FB1457 lea     ecx, [ebp-14h]        ;=>ecx得到this指針
00FB145A add     ecx, 8                ;=>ecx=this+8指向第二個虛表
00FB145D mov     [ebp-64h]             ;=>保存ecx到臨時變量中
00FB1460 jmp     short loc_FB1469;----|
                                      ;|
00FB1469 mov     edx, [ebp-64h];<----| 
00FB146C mov     [ebp-1Ch], edx        ;=>局部變量b2在棧[ebp-1Ch],賦值爲this+8,指向第二個虛表

;b2->f();
00FB146F mov     eax, [ebp-1Ch]        ;=>eax得到b2值
00FB1472 mov     edx, [eax]            ;=>edx得到指向第二個虛表
00FB1474 mov     ecx, [ebp-1Ch]        ;=>ecx傳參this+8
00FB1477 mov     eax, [edx+4]          ;=>eax爲第二個虛表的第二個元素=>[thunk]:Derive::f`adjustor{8}' (void),而後間接調用Derive::f(void)
00FB147A call eax ;b2->g();
00FB147C mov     eax, [ebp+b2]         ; =>eax得到b2值
00FB147F mov     edx, [eax]            ;=>edx得到指向第二個虛表
00FB1481 mov     ecx, [ebp+b2]         ;=>ecx傳參this+8
00FB1484 mov     eax, [edx]            ;=>eax爲第二個虛表的第一個元素=>Base2::g(void)
00FB1486 call    eax                   ;=>調用Base2::g(void)
相關文章
相關標籤/搜索