[背景]
虚函数表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
编译器应该保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
无继承时虚函数表
1 编写demo: 2 #include <iostream> 3 using namespace std; 4 5 class base_class 6 { 7 private: 8 int m_base; 9 public: 10 virtual void v_func1() 11 { 12 cout << "This is base_class's v_func1()" << endl; 13 } 14 virtual void v_func2() 15 { 16 cout << "This is base_class's v_func2()" << endl; 17 } 18 virtual void v_func3() 19 { 20 cout << "This is base_class's v_func3()" << endl; 21 } 22 };
OD载入逆向分析
构造函数
我们查看一下虚表指针
内存中应该为:
虚表单一继承:
1 改写demo 2 class base_class 3 { 4 public: 5 virtual void v_func1() 6 { 7 cout << "This is base_class's v_func1()" << endl; 8 } 9 virtual void v_func2() 10 { 11 cout << "This is base_class's v_func2()" << endl; 12 } 13 virtual void v_func3() 14 { 15 cout << "This is base_class's v_func3()" << endl; 16 } 17 }; 18 class dev_class : public base_class 19 { 20 public: 21 virtual void v_func4() 22 { 23 cout << "This is dev_class's v_func4()" << endl; 24 } 25 virtual void v_func5() 26 { 27 cout << "This is dev_class's v_func5()" << endl; 28 } 29 };
构造函数逆向如下
基类构造函数改写指针
此时虚表
在派生类中又改写了虚函数指针
此时虚表
在内存中的布局应该为:
在改写虚表指针的时候,按照父类-子类的顺序存放在虚表中
重写父类虚函数继承
1 我们改写demo: 2 class base_class 3 { 4 public: 5 virtual void v_func1() 6 { 7 cout << "This is base_class's v_func1()" << endl; 8 } 9 virtual void v_func2() 10 { 11 cout << "This is base_class's v_func2()" << endl; 12 } 13 virtual void v_func3() 14 { 15 cout << "This is base_class's v_func3()" << endl; 16 } 17 }; 18 class dev_class : public base_class 19 { 20 public: 21 virtual void v_func3() 22 { 23 cout << "This is dev_class's v_func4()" << endl; 24 } 25 virtual void v_func4() 26 { 27 cout << "This is dev_class's v_func5()" << endl; 28 } 29 };
OD载入分析构造函数
我们按照上述方法打印虚表
在构造基类的时候
在派生类修改虚表指针后
可以很清楚的发现,在第三个虚函数地址被派生类修改
内存中布局应该是这样
多重继承下的虚函数表_子类没有改写父类
1 我们改写demo 2 3 class base_class_A 4 { 5 public: 6 virtual void v_func1() 7 { 8 cout << "This is base_class_A's v_func1()" << endl; 9 } 10 virtual void v_func2() 11 { 12 cout << "This is base_class_A's v_func2()" << endl; 13 } 14 15 }; 16 class base_class_B 17 { 18 public: 19 virtual void v_func3() 20 { 21 cout << "This is base_class_B's v_func1()" << endl; 22 } 23 virtual void v_func4() 24 { 25 cout << "This is base_class_B's v_func2()" << endl; 26 } 27 }; 28 class dev_class : public base_class_A,base_class_B 29 { 30 public: 31 virtual void v_func5() 32 { 33 cout << "This is dev_class`s v_func" << endl; 34 } 35 36 };
OD载入分析构造函数
Base_a 虚表
Base_b
Dev:
修改虚表指针
和
通过分析我们可以发现当多重继承中会存在多张虚表
内存中的布局应该为:
多重继承下的虚函数表_子类改写父类
1 我们改写demo 2 class base_class_A 3 { 4 public: 5 virtual void v_func1() 6 { 7 cout << "This is base_class_A's v_func1()" << endl; 8 } 9 virtual void v_func2() 10 { 11 cout << "This is base_class_A's v_func2()" << endl; 12 } 13 14 }; 15 class base_class_B 16 { 17 public: 18 virtual void v_func3() 19 { 20 cout << "This is base_class_B's v_func1()" << endl; 21 } 22 virtual void v_func4() 23 { 24 cout << "This is base_class_B's v_func2()" << endl; 25 } 26 }; 27 class dev_class : public base_class_A,base_class_B 28 { 29 public: 30 virtual void v_func1() 31 { 32 cout << "This is dev_class`s v_func1" << endl; 33 } 34 virtual void v_func3() 35 { 36 cout << "This is dev_class`s v_func3" << endl; 37 } 38 virtual void v_fun5() 39 { 40 cout << "This is dev_class`s v_func5" << endl; 41 } 42 43 };
虚表为
内存中的布局为
我们稍微修改下我们的demo
加入成员变量:
1 class root 2 { 3 private: 4 int m_r1; 5 int m_r2; 6 public: 7 root() 8 { 9 m_r1 = 1; 10 m_r2 = 2; 11 } 12 ~root(){}; 13 virtual void v_funr() 14 { 15 cout << "This is root" << endl; 16 } 17 18 19 }; 20 class base_class_A : public root 21 { 22 private: 23 int m_a; 24 25 public: 26 base_class_A() 27 { 28 m_a = 3; 29 } 30 ~base_class_A(){}; 31 virtual void v_func1() 32 { 33 cout << "This is base_class_A's v_func1()" << endl; 34 } 35 virtual void v_func2() 36 { 37 cout << "This is base_class_A's v_func2()" << endl; 38 } 39 40 }; 41 class base_class_B : public root 42 { 43 private: 44 int m_b ; 45 46 public: 47 base_class_B() 48 { 49 m_b = 4; 50 } 51 ~base_class_B(){}; 52 void v_func3() 53 { 54 cout << "This is base_class_B's v_func1()" << endl; 55 } 56 void v_func4() 57 { 58 cout << "This is base_class_B's v_func2()" << endl; 59 } 60 }; 61 62 class dev_class : public base_class_A,base_class_B 63 { 64 private: 65 int m_a; 66 int m_b; 67 int m_c; 68 public: 69 dev_class(); 70 ~dev_class(){}; 71 virtual void v_func1() 72 { 73 cout << "This is dev_class`s v_func1" << endl; 74 } 75 virtual void v_func3() 76 { 77 cout << "This is dev_class`s v_func3" << endl; 78 } 79 virtual void v_fun5() 80 { 81 cout << "This is dev_class`s v_func5" << endl; 82 } 83 84 }; 85 86 dev_class :: dev_class():m_a(1),m_b(2),m_c(3) 87 { 88 89 }
加入成员变量
我们看下最开始的基类root的构造
虚表为
虚拟多重继承
1 Demo: 2 class root 3 { 4 private: 5 int m_r1; 6 int m_r2; 7 public: 8 root() 9 { 10 m_r1 = 1; 11 m_r2 = 2; 12 } 13 ~root(){}; 14 virtual void v_funr() 15 { 16 cout << "This is root" << endl; 17 } 18 19 20 }; 21 class base_class : virtual public root 22 { 23 private: 24 int m_a; 25 int m_b; 26 27 public: 28 base_class() 29 { 30 m_a = 3; 31 m_b = 4; 32 } 33 ~base_class(){}; 34 virtual void v_funr() 35 { 36 cout << "This is base_class_A's v_funcr()" << endl; 37 } 38 virtual void v_func1() 39 { 40 cout << "This is base_class_A's v_func1()" << endl; 41 } 42 virtual void v_func2() 43 { 44 cout << "This is base_class_A's v_func2()" << endl; 45 } 46 47 }; 48 49 class dev_class :virtual public base_class 50 { 51 private: 52 int m_a; 53 int m_b; 54 int m_c; 55 public: 56 dev_class(); 57 ~dev_class(){}; 58 virtual void v_funr() 59 { 60 cout << "This is dev_class's v_funcr()" << endl; 61 } 62 virtual void v_func1() 63 { 64 cout << "This is dev_class`s v_func1" << endl; 65 } 66 virtual void v_func3() 67 { 68 cout << "This is dev_class`s v_func3" << endl; 69 } 70 virtual void v_fun5() 71 { 72 cout << "This is dev_class`s v_func5" << endl; 73 } 74 75 }; 76 dev_class :: dev_class():m_a(1),m_b(2),m_c(3) 77 { 78 79 }
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也有这样的结构
我们现在总结在内存中,虚拟继承结构如下:
结论:
在分析虚函数,当存在多重继承(虚拟继承中有虚函数)情况下,虚表的结构会发生变化,将会多出一个偏转表,通过对偏移地址的操作进而去访问和改写父类虚表指针。而其在内存中的结构也与普通继承有些不同(考虑跟编译器有关!)。