全面剖析逆向還原代碼的內存結構
// 聲明:以下代碼均在Win32_Sp3 VC6.0_DEBUG版中調試通過..
當然,由于水平有限,必定錯漏百出!所以,希望耽誤您的時間,懇求您的指點。在這里萬分感謝!
首先,我們定義如下類:
- 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;
- }
類的構造順序:先基類-->在成員對象-->在派生類(自己)
所以the對象的構造過程如下:
按照繼承定義時寫的順序:
1、基類A構造:虛表賦值,成員數據
2、基類B構造:虛表賦值,成員數據
3、派生類AB構造:虛表覆蓋,成員數據
內存中結構如下圖:
在做如下修改:
- 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對象來說,它的內存結構的內存結構還是不會改變,但是虛表的內容會改變,改變后的虛表如下:
1、A::Vtable如下:
&AB::fun &A::fun1 AB::fun3
2、B::Vtable如下:
&AB::fun &B::fun2
總結一下:先按繼承聲明順序依次構造虛表,如果子類有虛函數,并且不同名,則填寫到聲明順序首位的基類虛表中的末尾項。
我們從淺到深慢慢的剖析虛繼承的內存結構,首先看源碼如下:
- 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;
- }
假設沒有virtual虛繼承關鍵字,the對象在內存中的結構如下:
A::m_nDataA
B::m_nDataB
現在我們的源碼中有virtual繼承關鍵字,那么內存結構必然會有區別,那么內存結構是怎么樣的呢?如下:
B::base Offset m_nDataA // A::m_nDataA數據的偏移
B::m_nDataB
A::m_nDataA
編譯器為什么要這么做呢?這個偏移值是什么?這么做的意義又何在?
首先,這么做是為了只存在于一份虛基類數據。后面會講解。
B::base Offset 偏移的值,一般為全局數據區中。編譯器為了和虛表區別。這個指針指向的地址的值一般為:0x00000000, 或者某些特殊值
而在他后面的4個字節中。才是真正數據的偏移地址
為什么取這兩個值?為什么不直接寫偏移呢?
編譯器為了在內存中只產生一份基類數據,當然就必須得寫偏移值,可是又為了和虛表區分。所以只能取特殊值作為區分。(當然這里僅個人猜想,不作參考)
繼續看源碼:
- 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;
- }
現在多了虛表的加入。內存結構有了大的變化
the對象的內存結構如下:
B::base Offset A // B的父類A的偏移
B::m_nDataB
0x00000000 // 虛基類的非虛基類的分隔符
B::Virtual
A::m_nDataA
劃紅線的地方,產生覆蓋,我們慢慢剖析編譯器構造的過程。
the對象初始化空間如下:
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
先是虛基類的構造。內存結構如下:
1、***步
B::base Offset A // 填入A類的偏移
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
2、第二步
B::base Offset A // 由此處指向內容向下4個字節為B的父類A的偏移。取出內容偏移地址后。當前地址 + 偏移地址 == 填寫A類虛表的地址
0xCCCCCCCC
0xCCCCCCCC
A::virtual // A類的虛表
0xCCCCCCCC
3、第三步
B::base Offset A
0xCCCCCCCC
0xCCCCCCCC
A::virtual
A::m_nDataA
4、第四步(程序流程返回到派生類B構造函數)
B::base Offset A
0xCCCCCCCC
0x00000000 // 填充全0,作為虛基類和非基類的分隔符
A::virtual
A::m_nDataA
5、第五步(虛表賦值)
A::Offset A
0xCCCCCCCC
0x00000000
B::virtual // 由于派生類的有寫fun虛函數。構成覆蓋關系。所以覆蓋A的虛表
A::m_nDataA
6、第六步
B::base Offset A
B::m_nDataB // B::m_nDataB
0x00000000
B::virtual
A::m_nDataA
我們繼續看源碼:
- 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;
- }
加入了虛函數,構成多個虛表的the對象內存結構如下:
B::Vtable
B::base Offset A
B::m_nDataB
0x00000000
A::Vtable
A::m_nDataA
我們繼續慢慢剖析內存結構。the對象初始化內存空間如下:
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
0xCCCCCCCC
1、***步
0xCCCCCCCC
B::base offset A // B的虛基類A的偏移地址. 規律找出來了。不用看偏移基本也可以推測出以下上個區域
0xCCCCCCCC
0xCCCCCCCC // 這里應該是虛基類和非虛基類的分隔符。
0xCCCCCCCC // 后面的步驟會填充為A的虛表
0xCCCCCCCC // 后面的步驟會填充為A的數據成員m_nDataA
結果會是我們推測的這樣嗎?這是構造完虛基類A的情況。
果然和我們猜想的一樣.到這里了。你肯定會問。為什么不在是對象的首地址開始填充偏移地址了。
這里要搞清楚的是。現在派生類有了自己虛函數Fun2(). 并且和父類不同名。所以必須單獨建立一張虛表了。于是編譯器就這樣安排內存結構了.
繼續往下剖析:
由于重復的操作,省略...... 我們直接來看第五步
5、第五步(派生類B的構造函數)
B::virtual
B::base offset A
0xCCCCCCCC
0x00000000 // 虛基類和非虛基類的分隔符
A::Virtual
A::m_nDataA
這里紅色標記的地方產生了派生類虛函數的覆蓋,虛表中的結構如下:
B::fun // 覆蓋掉了 A::fun
A::fun1
并且,產生一個B虛表,虛表中的結構如下:
B::fun2
6、第六步
B::virtual
B::base offset A
B::m_nDataB // B的成員數據
0x00000000
A::Virtual
A::m_nDataA
好了,現在有了前面的講解,我們來剖析下較為復雜菱形繼承的內存結構,源碼如下:
- 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;
- }
由于是虛繼承,所以虛基類只會產生一份拷貝.內存結構必然如下:
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;
- }
加了虛函數后,the對象的內存結構如下:
B::base offset A
B::m_nDataB
C::base offset A
C::m_nDataC
0x00000000
BC::vtable
A::m_nDataA
紅色地方同理,派生類的fun多次覆蓋父類的。***為BC::vtable。
我們繼續變形如下:
- 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對象的內存結構如下:
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我們知道。即使不是虛繼承。也會自動填充到按定義順序首基類的虛表的末尾。
而B是定義的首繼承基類,而B::fun中又覆蓋掉了虛基類A的虛表的A::fun,由于B和C虛繼承于A。
所以B和C不能同時都在虛基類A中虛表末尾加上各自的虛函數,所以只能自己建張表.
然而BC又是以B定義順序的基類.也不是虛繼承。就把BC::funBC直接填充到B::vtable末尾.
到此為止,我們分析了幾乎大部分虛繼承的內存結構。在看到內存的時候。大家是否能還原出代碼呢?
當然了。還有很多更復雜的結構。只要掌握了最基本的原理。無非就是組合使用了!
原文鏈接:http://www.cnblogs.com/ziolo/archive/2013/05/07/3066022.html