虚函数逆向分析

[背景]

       虚函数表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

      编译器应该保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

 

 

无继承时虚函数表

 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也有这样的结构

我们现在总结在内存中,虚拟继承结构如下:

 

结论:

在分析虚函数,当存在多重继承(虚拟继承中有虚函数)情况下,虚表的结构会发生变化,将会多出一个偏转表,通过对偏移地址的操作进而去访问和改写父类虚表指针。而其在内存中的结构也与普通继承有些不同(考虑跟编译器有关!)。

 

*转载请注明来自游戏安全实验室(GSLAB.QQ.COM)

原文地址:https://www.cnblogs.com/nothx/p/8537334.html