菱形继承,多继承,虚继承、虚表的内存结构全面剖析(逆向分析基础)
// 聲明:以下代碼均在Win32_Sp3 ? VC6.0_DEBUG版中調(diào)試通過..
在逆向還原代碼的時(shí)候,必須得掌握了菱形繼承,多繼承,虛繼承虛函數(shù)的內(nèi)存虛表結(jié)構(gòu)。所以,這篇文章獻(xiàn)給正在學(xué)習(xí)C++的朋友們。
當(dāng)然,由于水平有限,必定錯(cuò)漏百出!所以,希望耽誤您的時(shí)間,懇求您的指點(diǎn)。在這里萬分感謝!
首先,我們定義如下類:
class A { public:A(){m_nData = 1;}virtual void fun(){}int m_nData; };class B { public:B(){m_nData = 2;}virtual void fun(){}int m_nData; };class AB :public A, public B { public:AB(){m_nData = 3;}virtual void fun(){}int m_nData; };int main(int argc, char* argv[]) {AB the;return 0; }類的構(gòu)造順序:先基類-->在成員對象-->在派生類(自己)
所以the對象的構(gòu)造過程如下:
按照繼承定義時(shí)寫的順序:
1、基類A構(gòu)造:虛表賦值,成員數(shù)據(jù)
2、基類B構(gòu)造:虛表賦值,成員數(shù)據(jù)
3、派生類AB構(gòu)造:虛表覆蓋,成員數(shù)據(jù)
內(nèi)存中結(jié)構(gòu)如下圖:
在做如下修改:
class A { public:A(){m_nData = 1;}virtual void fun(){}virtual void fun1() // 新增加 {}int m_nData; };class B { public:B(){m_nData = 2;}virtual void fun(){}virtual void fun2() // 新增加 {}int m_nData; };class AB :public A, public B { public:AB(){m_nData = 3;}virtual void fun(){}virtual void fun3() // 新增加 {}int m_nData; };int main(int argc, char* argv[]) {AB the;return 0; }對于the對象來說,它的內(nèi)存結(jié)構(gòu)的內(nèi)存結(jié)構(gòu)還是不會改變,但是虛表的內(nèi)容會改變,改變后的虛表如下:
1、A::Vtable如下:
&AB::fun ?&A::fun1 ?AB::fun3
2、B::Vtable如下:
&AB::fun ?&B::fun2
總結(jié)一下:先按繼承聲明順序依次構(gòu)造虛表,如果子類有虛函數(shù),并且不同名,則填寫到聲明順序首位的基類虛表中的末尾項(xiàng)。
我們從淺到深慢慢的剖析虛繼承的內(nèi)存結(jié)構(gòu),首先看源碼如下:
class A { public:A(){m_nDataA = 1;}int m_nDataA; };class B :virtual public A { public:B(){m_nDataB = 2;}int m_nDataB; };int main(int argc, char* argv[]) {B the;return 0; }假設(shè)沒有virtual虛繼承關(guān)鍵字,the對象在內(nèi)存中的結(jié)構(gòu)如下:
A::m_nDataA
B::m_nDataB
現(xiàn)在我們的源碼中有virtual繼承關(guān)鍵字,那么內(nèi)存結(jié)構(gòu)必然會有區(qū)別,那么內(nèi)存結(jié)構(gòu)是怎么樣的呢?如下:
B::base Offset m_nDataA ? ? // A::m_nDataA數(shù)據(jù)的偏移
B::m_nDataB
A::m_nDataA
編譯器為什么要這么做呢?這個(gè)偏移值是什么?這么做的意義又何在?
首先,這么做是為了只存在于一份虛基類數(shù)據(jù)。后面會講解。
B::base Offset?偏移的值,一般為全局?jǐn)?shù)據(jù)區(qū)中。編譯器為了和虛表區(qū)別。這個(gè)指針指向的地址的值一般為:0x00000000, 或者某些特殊值
而在他后面的4個(gè)字節(jié)中。才是真正數(shù)據(jù)的偏移地址
為什么取這兩個(gè)值?為什么不直接寫偏移呢?
編譯器為了在內(nèi)存中只產(chǎn)生一份基類數(shù)據(jù),當(dāng)然就必須得寫偏移值,可是又為了和虛表區(qū)分。所以只能取特殊值作為區(qū)分。(當(dāng)然這里僅個(gè)人猜想,不作參考)
繼續(xù)看源碼:
class A { public:A(){m_nDataA = 1;}virtual void fun(){}int m_nDataA; };class B :virtual public A { public:B(){m_nDataB = 2;}virtual void fun(){}int m_nDataB; };int main(int argc, char* argv[]) {B the;return 0; }現(xiàn)在多了虛表的加入。內(nèi)存結(jié)構(gòu)有了大的變化
the對象的內(nèi)存結(jié)構(gòu)如下:
B::base Offset A ? // B的父類A的偏移
B::m_nDataB
0x00000000 ?// 虛基類的非虛基類的分隔符
B::Virtual
A::m_nDataA
劃紅線的地方,產(chǎn)生覆蓋,我們慢慢剖析編譯器構(gòu)造的過程。
the對象初始化空間如下:
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
先是虛基類的構(gòu)造。內(nèi)存結(jié)構(gòu)如下:
1、第一步
B::base Offset A? ? ? // 填入A類的偏移?
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
2、第二步
B::base Offset A? ? ? // 由此處指向內(nèi)容向下4個(gè)字節(jié)為B的父類A的偏移。取出內(nèi)容偏移地址后。當(dāng)前地址 + 偏移地址 ?== ?填寫A類虛表的地址
0xCCCCCCCC
0xCCCCCCCC
A::virtual ? ? ? ?// A類的虛表
0xCCCCCCCC
3、第三步
B::base Offset A? ? ??
0xCCCCCCCC
0xCCCCCCCC
A::virtual ? ? ? ?
A::m_nDataA
4、第四步(程序流程返回到派生類B構(gòu)造函數(shù))
B::base Offset A? ? ??
0xCCCCCCCC
0x00000000 ? ? ?// 填充全0,作為虛基類和非基類的分隔符
A::virtual ? ? ? ?
A::m_nDataA
5、第五步(虛表賦值)
A::Offset A ? ? ?
0xCCCCCCCC
0x00000000
B::virtual ? ? ? ? ? // 由于派生類的有寫fun虛函數(shù)。構(gòu)成覆蓋關(guān)系。所以覆蓋A的虛表
A::m_nDataA
6、第六步
B::base Offset A? ? ?
B::m_nDataB ? ?// B::m_nDataB
0x00000000
B::virtual
A::m_nDataA
我們繼續(xù)看源碼:
class A { public:A(){m_nDataA = 1;}virtual void fun(){}virtual void fun1(){}int m_nDataA; };class B :virtual public A { public:B(){m_nDataB = 2;}virtual void fun(){}virtual void fun2(){}int m_nDataB; };int main(int argc, char* argv[]) {B the;return 0; }加入了虛函數(shù),構(gòu)成多個(gè)虛表的the對象內(nèi)存結(jié)構(gòu)如下:
B::Vtable
B::base Offset A
B::m_nDataB
0x00000000
A::Vtable
A::m_nDataA
我們繼續(xù)慢慢剖析內(nèi)存結(jié)構(gòu)。the對象初始化內(nèi)存空間如下:
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
1、第一步
0xCCCCCCCC
B::base offset A ? // B的虛基類A的偏移地址. 規(guī)律找出來了。不用看偏移基本也可以推測出以下上個(gè)區(qū)域
0xCCCCCCCC
0xCCCCCCCC ? ? // 這里應(yīng)該是虛基類和非虛基類的分隔符。
0xCCCCCCCC ? ? // 后面的步驟會填充為A的虛表
0xCCCCCCCC ? ? // 后面的步驟會填充為A的數(shù)據(jù)成員m_nDataA
結(jié)果會是我們推測的這樣嗎?這是構(gòu)造完虛基類A的情況。
果然和我們猜想的一樣.到這里了。你肯定會問。為什么不在是對象的首地址開始填充偏移地址了。
這里要搞清楚的是。現(xiàn)在派生類有了自己虛函數(shù)Fun2(). 并且和父類不同名。所以必須單獨(dú)建立一張?zhí)摫砹恕S谑蔷幾g器就這樣安排內(nèi)存結(jié)構(gòu)了.
繼續(xù)往下剖析:
由于重復(fù)的操作,省略...... ?我們直接來看第五步
5、第五步(派生類B的構(gòu)造函數(shù))
B::virtual
B::base offset A
0xCCCCCCCC
0x00000000 ? ? ?// ?虛基類和非虛基類的分隔符
A::Virtual ? ? ? ? ?
A::m_nDataA
這里紅色標(biāo)記的地方產(chǎn)生了派生類虛函數(shù)的覆蓋,虛表中的結(jié)構(gòu)如下:
B::fun ? ?// 覆蓋掉了 A::fun
A::fun1
并且,產(chǎn)生一個(gè)B虛表,虛表中的結(jié)構(gòu)如下:
B::fun2
6、第六步
B::virtual
B::base offset A
B::m_nDataB ? ? // B的成員數(shù)據(jù)
0x00000000 ? ? ?
A::Virtual?? ? ? ? ?
A::m_nDataA
好了,現(xiàn)在有了前面的講解,我們來剖析下較為復(fù)雜菱形繼承的內(nèi)存結(jié)構(gòu),源碼如下:
class A { public:A(){m_nDataA = 1;}int m_nDataA; };class B :virtual public A { public:B(){m_nDataB = 2;}int m_nDataB; };class C :virtual public A { public:C(){m_nDatac = 3;}int m_nDatac; };class BC :public B, public C { public:BC(){m_nDataBC = 4;}int m_nDataBC; };int main(int argc, char* argv[]) {BC the;return 0; }由于是虛繼承,所以虛基類只會產(chǎn)生一份拷貝.內(nèi)存結(jié)構(gòu)必然如下:
B::base offset A
B::m_nDataB
C::base offset A
C::m_nDataC
BC::m_nDataBC
A::m_nDataA?
在變形下.源碼如下:
class A { public:A(){m_nDataA = 1;}int m_nDataA;virtual void fun(){} // 新增加 };class B :virtual public A { public:B(){m_nDataB = 2;}int m_nDataB;virtual void fun(){} // 新增加 };class C :virtual public A { public:C(){m_nDatac = 3;}int m_nDatac;virtual void fun(){} // 新增加 };class BC :public B, public C { public:BC(){m_nDataBC = 4;}int m_nDataBC;virtual void fun(){} // 新增加 };int main(int argc, char* argv[]) {BC the;return 0; }加了虛函數(shù)后,the對象的內(nèi)存結(jié)構(gòu)如下:
B::base offset A
B::m_nDataB
C::base offset A
C::m_nDataC
0x00000000
BC::vtable
A::m_nDataA?
紅色地方同理,派生類的fun多次覆蓋父類的。最后為BC::vtable。
我們繼續(xù)變形如下:
class A { public:A(){m_nDataA = 1;}virtual void fun(){}virtual void funA(){}int m_nDataA; };class B :virtual public A { public:B(){m_nDataB = 2;}virtual void fun(){}virtual void funB(){}int m_nDataB; };class C :virtual public A { public:C(){m_nDatac = 3;}virtual void fun(){}virtual void funC(){}int m_nDatac; };class BC :public B, public C { public:BC(){m_nDataBC = 4;}virtual void fun(){}virtual void funBC(){}int m_nDataBC; };int main(int argc, char* argv[]) {BC the;return 0; }the對象的內(nèi)存結(jié)構(gòu)如下:
B::vtable
B::base offset A?
B::m_nDataB
C::vtable
C::base offset A
C::m_nDataC
BC::m_nDataBC
0x00000000
A::vtable
A::m_nDataA
B::vtable中表末尾存放著BC::funBC, 而BC::fun則覆蓋到A::vtable中.
BC::funBC我們知道。即使不是虛繼承。也會自動(dòng)填充到按定義順序首基類的虛表的末尾。
而B是定義的首繼承基類,而B::fun中又覆蓋掉了虛基類A的虛表的A::fun,由于B和C虛繼承于A。
所以B和C不能同時(shí)都在虛基類A中虛表末尾加上各自的虛函數(shù),所以只能自己建張表.
然而BC又是以B定義順序的基類.也不是虛繼承。就把BC::funBC直接填充到B::vtable末尾.
到此為止,我們分析了幾乎大部分虛繼承的內(nèi)存結(jié)構(gòu)。在看到內(nèi)存的時(shí)候。大家是否能還原出代碼呢?
當(dāng)然了。還有很多更復(fù)雜的結(jié)構(gòu)。只要掌握了最基本的原理。無非就是組合使用了!
轉(zhuǎn)載于:https://www.cnblogs.com/ziolo/archive/2013/05/07/3066022.html
總結(jié)
以上是生活随笔為你收集整理的菱形继承,多继承,虚继承、虚表的内存结构全面剖析(逆向分析基础)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [转]Cocos2d-x观察者模式
- 下一篇: 博客收集