阅读目录
虚函数逆向分析
发布于:2016-7-6 12:21 | 123002次阅读 作者: 管理员 | 原作者: TP | 来自: 原创
[背景] 虚函数表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。 编译器应该保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
无继承时虚函数表 编写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也有这样的结构 我们现在总结在内存中,虚拟继承结构如下:
结论: 在分析虚函数,当存在多重继承(虚拟继承中有虚函数)情况下,虚表的结构会发生变化,将会多出一个偏转表,通过对偏移地址的操作进而去访问和改写父类虚表指针。而其在内存中的结构也与普通继承有些不同(考虑跟编译器有关!)。 |

最新评论
查看全部评论(1)
发表评论