虛函數表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中分配了指向這個表的指針的內存,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤爲重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。
虛函數表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中分配了指向這個表的指針的內存,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤爲重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。
無繼承時虛函數表
編寫demo
#include <iostream>
using namespace std;
class base_class
{
private:
int m_base;
public:
virtual void v_func1()
{
cout << "This is base_class's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class's v_func2()" << endl;
}
virtual void v_func3()
{
cout << "This is base_class's v_func3()" << endl;
}
};
OD載入逆向分析
構造函數
我們查看一下虛表指針
內存中應該爲:
虛表單一繼承:
改寫demo:
class base_class
{
public:
virtual void v_func1()
{
cout << "This is base_class's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class's v_func2()" << endl;
}
virtual void v_func3()
{
cout << "This is base_class's v_func3()" << endl;
}
};
class dev_class : public base_class
{
public:
virtual void v_func4()
{
cout << "This is dev_class's v_func4()" << endl;
}
virtual void v_func5()
{
cout << "This is dev_class's v_func5()" << endl;
}
};
構造函數逆向如下
基類構造函數改寫指針
此時虛表
在派生類中又改寫了虛函數指針
此時虛表
在內存中的佈局應該爲:
在改寫虛表指針的時候,按照父類-子類的順序存放在虛表中
重寫父類虛函數繼承
繼續改寫demo:
class base_class
{
public:
virtual void v_func1()
{
cout << "This is base_class's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class's v_func2()" << endl;
}
virtual void v_func3()
{
cout << "This is base_class's v_func3()" << endl;
}
};
class dev_class : public base_class
{
public:
virtual void v_func3()
{
cout << "This is dev_class's v_func4()" << endl;
}
virtual void v_func4()
{
cout << "This is dev_class's v_func5()" << endl;
}
};
OD載入分析構造函數
我們按照上述方法打印虛表
在構造基類的時候
在派生類修改虛表指針後
可以很清楚的發現,在第三個虛函數地址被派生類修改
內存中佈局應該是這樣
多重繼承下的虛函數表_子類沒有改寫父類
繼續改寫demo:
class base_class_A
{
public:
virtual void v_func1()
{
cout << "This is base_class_A's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class_A's v_func2()" << endl;
}
};
class base_class_B
{
public:
virtual void v_func3()
{
cout << "This is base_class_B's v_func1()" << endl;
}
virtual void v_func4()
{
cout << "This is base_class_B's v_func2()" << endl;
}
};
class dev_class : public base_class_A,base_class_B
{
public:
virtual void v_func5()
{
cout << "This is dev_class`s v_func" << endl;
}
};
OD載入分析構造函數
Base_a 虛表:
Base_b虛表:
dev虛表
修改虛表指針
和
通過分析我們可以發現當多重繼承中會存在多張虛表
內存中的佈局應該爲:
多重繼承下的虛函數表_子類改寫父類
繼續改寫demo
class base_class_A
{
public:
virtual void v_func1()
{
cout << "This is base_class_A's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class_A's v_func2()" << endl;
}
};
class base_class_B
{
public:
virtual void v_func3()
{
cout << "This is base_class_B's v_func1()" << endl;
}
virtual void v_func4()
{
cout << "This is base_class_B's v_func2()" << endl;
}
};
class dev_class : public base_class_A,base_class_B
{
public:
virtual void v_func1()
{
cout << "This is dev_class`s v_func1" << endl;
}
virtual void v_func3()
{
cout << "This is dev_class`s v_func3" << endl;
}
virtual void v_fun5()
{
cout << "This is dev_class`s v_func5" << endl;
}
};
虛表爲:
內存中的佈局爲:
我們稍微修改下我們的demo
加入成員變量:
class root
{
private:
int m_r1;
int m_r2;
public:
root()
{
m_r1 = 1;
m_r2 = 2;
}
~root(){};
virtual void v_funr()
{
cout << "This is root" << endl;
}
};
class base_class_A : public root
{
private:
int m_a;
public:
base_class_A()
{
m_a = 3;
}
~base_class_A(){};
virtual void v_func1()
{
cout << "This is base_class_A's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class_A's v_func2()" << endl;
}
};
class base_class_B : public root
{
private:
int m_b ;
public:
base_class_B()
{
m_b = 4;
}
~base_class_B(){};
void v_func3()
{
cout << "This is base_class_B's v_func1()" << endl;
}
void v_func4()
{
cout << "This is base_class_B's v_func2()" << endl;
}
};
class dev_class : public base_class_A,base_class_B
{
private:
int m_a;
int m_b;
int m_c;
public:
dev_class();
~dev_class(){};
virtual void v_func1()
{
cout << "This is dev_class`s v_func1" << endl;
}
virtual void v_func3()
{
cout << "This is dev_class`s v_func3" << endl;
}
virtual void v_fun5()
{
cout << "This is dev_class`s v_func5" << endl;
}
};
dev_class :: dev_class():m_a(1),m_b(2),m_c(3)
{
}
我們看下最開始的基類root的構造:
虛表爲:
虛擬多重繼承
改寫demo:
class root
{
private:
int m_r1;
int m_r2;
public:
root()
{
m_r1 = 1;
m_r2 = 2;
}
~root(){};
virtual void v_funr()
{
cout << "This is root" << endl;
}
};
class base_class : virtual public root
{
private:
int m_a;
int m_b;
public:
base_class()
{
m_a = 3;
m_b = 4;
}
~base_class(){};
virtual void v_funr()
{
cout << "This is base_class_A's v_funcr()" << endl;
}
virtual void v_func1()
{
cout << "This is base_class_A's v_func1()" << endl;
}
virtual void v_func2()
{
cout << "This is base_class_A's v_func2()" << endl;
}
};
class dev_class :virtual public base_class
{
private:
int m_a;
int m_b;
int m_c;
public:
dev_class();
~dev_class(){};
virtual void v_funr()
{
cout << "This is dev_class's v_funcr()" << endl;
}
virtual void v_func1()
{
cout << "This is dev_class`s v_func1" << endl;
}
virtual void v_func3()
{
cout << "This is dev_class`s v_func3" << endl;
}
virtual void v_fun5()
{
cout << "This is dev_class`s v_func5" << endl;
}
};
dev_class :: dev_class():m_a(1),m_b(2),m_c(3)
{
}
Dev_class的時候
此時[eax+0x4]和[eax+0x2c]存放的不再爲虛表指針,而是一個偏轉
我們可以查看下地址
在root構造時,
00A22BA7 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00A22BAA C700 90DCA200 mov dword ptr ds:[eax],offset vft.base_class::`vftable'
00A22BB0 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00A22BB3 8B48 04 mov ecx,dword ptr ds:[eax+0x4] ; 得到偏轉表地址
00A22BB6 8B51 04 mov edx,dword ptr ds:[ecx+0x4] ; 得到偏移地址
00A22BB9 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00A22BBC C74410 04 A0DCA>mov dword ptr ds:[eax+edx+0x4],offset vft.base_class::`vftable' ; 通過偏轉地址計算得到虛基類指針並修改
00A22BC4 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00A22BC7 8B48 04 mov ecx,dword ptr ds:[eax+0x4]
00A22BCA 8B51 04 mov edx,dword ptr ds:[ecx+0x4]
00A22BCD 83EA 10 sub edx,0x10 ; 減去類大小得到相對長度
00A22BD0 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00A22BD3 8B48 04 mov ecx,dword ptr ds:[eax+0x4] ; 得到偏移表地址
00A22BD6 8B41 04 mov eax,dword ptr ds:[ecx+0x4] ; 得到偏移
00A22BD9 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
00A22BDC 891401 mov dword ptr ds:[ecx+eax],edx ; 將偏移大小存放在虛基類前
00A22BDF 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00A22BE2 C740 08 0300000>mov dword ptr ds:[eax+0x8],0x3
00A22BE9 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
可以發現剛剛分析 出來的偏轉地址均指向 虛基類(root)的虛表指針
而FFFFFFFC則爲-4,指向偏轉表的前一個DWORD地址
我們繼續看base類的構造
通過偏移,使子類可以很容易訪問到虛基類,進而對虛基類指針進行改寫
00FE2C8C 837D 08 00 cmp dword ptr ss:[ebp+0x8],0x0 ; 判斷虛基類
00FE2C90 74 51 je Xvft.00FE2CE3
00FE2C92 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2C95 C740 04 58DDFE0>mov dword ptr ds:[eax+0x4],offset vft.dev_class::`vbtable' ; 偏轉表
00FE2C9C 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2C9F C740 2C 68DDFE0>mov dword ptr ds:[eax+0x2C],offset vft.dev_class::`vbtable' ; 偏轉表
00FE2CA6 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00FE2CA9 83C1 18 add ecx,0x18 ; 得到虛基類指針
00FE2CAC E8 5FE7FFFF call vft.00FE1410
00FE2CB1 C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
00FE2CB8 8B85 20FFFFFF mov eax,dword ptr ss:[ebp-0xE0]
00FE2CBE 83C8 01 or eax,0x1
00FE2CC1 8985 20FFFFFF mov dword ptr ss:[ebp-0xE0],eax ; 虛基類已經構造
00FE2CC7 6A 00 push 0x0
00FE2CC9 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00FE2CCC 83C1 28 add ecx,0x28
00FE2CCF E8 BFE6FFFF call vft.00FE1393 ; base構造
00FE2CD4 8B85 20FFFFFF mov eax,dword ptr ss:[ebp-0xE0]
00FE2CDA 83C8 02 or eax,0x2
00FE2CDD 8985 20FFFFFF mov dword ptr ss:[ebp-0xE0],eax
00FE2CE3 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2CE6 C700 30DDFE00 mov dword ptr ds:[eax],offset vft.dev_class::`vftable' ; dev的虛表指針(指向fun3,fun5)
00FE2CEC 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2CEF 8B48 04 mov ecx,dword ptr ds:[eax+0x4]
00FE2CF2 8B51 04 mov edx,dword ptr ds:[ecx+0x4] ; 得到偏移
00FE2CF5 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2CF8 C74410 04 40DDF>mov dword ptr ds:[eax+edx+0x4],offset vft.dev_class::`vftable' ; 通過偏移訪問到虛基類並修改
00FE2D00 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D03 8B48 04 mov ecx,dword ptr ds:[eax+0x4]
00FE2D06 8B51 08 mov edx,dword ptr ds:[ecx+0x8] ; 取到base類偏移
00FE2D09 8B45 EC mov eax,dword ptr ss:[ebp-0x14] ; 得到基址
00FE2D0C C74410 04 4CDDF>mov dword ptr ds:[eax+edx+0x4],offset vft.dev_class::`vftable' ; 修改base類虛表指針
00FE2D14 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D17 8B48 04 mov ecx,dword ptr ds:[eax+0x4]
00FE2D1A 8B51 04 mov edx,dword ptr ds:[ecx+0x4] ; 得到長度
00FE2D1D 83EA 14 sub edx,0x14 ; 減去類大小
00FE2D20 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D23 8B48 04 mov ecx,dword ptr ds:[eax+0x4]
00FE2D26 8B41 04 mov eax,dword ptr ds:[ecx+0x4]
00FE2D29 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00FE2D2C 891401 mov dword ptr ds:[ecx+eax],edx ; 將偏移存放在虛基類前
00FE2D2F 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D32 8B48 04 mov ecx,dword ptr ds:[eax+0x4]
00FE2D35 8B51 08 mov edx,dword ptr ds:[ecx+0x8]
00FE2D38 83EA 24 sub edx,0x24
00FE2D3B 8B45 EC mov eax,dword ptr ss:[ebp-0x14] ; 得到基址
00FE2D3E 8B48 04 mov ecx,dword ptr ds:[eax+0x4] ; 偏轉表
00FE2D41 8B41 08 mov eax,dword ptr ds:[ecx+0x8] ; 偏移大小
00FE2D44 8B4D EC mov ecx,dword ptr ss:[ebp-0x14] ; 基址
00FE2D47 891401 mov dword ptr ds:[ecx+eax],edx ; 將相對偏移存放在base前
00FE2D4A 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D4D C740 08 0100000>mov dword ptr ds:[eax+0x8],0x1 ; m_a = 1
00FE2D54 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D57 C740 0C 0200000>mov dword ptr ds:[eax+0xC],0x2 ; m_b = 2
00FE2D5E 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00FE2D61 C740 10 0300000>mov dword ptr ds:[eax+0x10],0x3 ; m_c = 3
最終虛表爲:
在虛表中我們發現
而我們在funcr時發現有這樣的結構:
在dev.func1也有這樣的結構
我們現在總結在內存中,虛擬繼承結構如下:
總結:
在分析虛函數,當存在多重繼承(虛擬繼承中有虛函數)情況下,虛表的結構會發生變化,將會多出一個偏轉表,通過對偏移地址的操作進而去訪問和改寫父類虛表指針。而其在內存中的結構也與普通繼承有些不同(考慮跟編譯器有關!)。
本文如需轉載,請註明出處