从逆向分析角度看C++虚函数

来源:互联网 发布:微谱数据库免费入口 编辑:程序博客网 时间:2024/05/22 00:39

谈到虚函数,我想很多朋友都应该知道虚函数表指针VPTR和虚函数表VTABLE,如果不清楚的朋友,建议先看看侯捷先生翻译的《深度探索C++对象模型》:)

刚开始的时候,我仅仅知道虚函数的多态机制是通过VPTR和VTABLE操控的,完全地相信书上所描述的,并没有亲自去证明过,或许是因为那时候我还没有接触到逆向分析吧:)

 

提几个问题:

1.      VPTR的大小如何确定?

2.      VPTR在类实例中的偏移值是多少?

3.      VPTR是如何索引到需要调用的函数的?

4.      VTABLE中函数的顺序是如何确定的?

 

了解上述问题的朋友,可以关闭这个窗口了:)


考虑如下代码:

#include <cstdio>class Base{public:int i;char c;double d;Base(){i  = 4;c  = 'A';d  = 2.0;}virtual void Virtual_Func_A(){printf("Virtual_Func_A()\n");}virtual void Virtual_Func_B(){printf("Virtual_Func_B()\n");}};int main(void){Base* b = new Base;b->Virtual_Func_A();b->Virtual_Func_A();return 0;}

分析工具:VC 6.0

分析过程:

在解答这个问题前,读者有必要了解一下“数据对齐”的概念,如果不了解的读者,可以参考下:)

http://blog.csdn.net/yeweiouyang/article/details/8636458


注意,Inter cpu 采用的是小端法

VPTR对程序员来说是隐形的,在Win32平台下,不考虑数据对齐的情况,VPTR占用4个字节,用于保存VTABLE的地址

 考虑如下程序:

#include <cstdio>class Base{public:virtual void Common_Func(){printf("Base::Common_Func()\n");}virtual void Base_Func(){printf("Base::Func()\n");}};class Derived : public Base{public:virtual void Common_Func(){printf("Derived::Common_Func()\n");}virtual void Derived_Func(){printf("Derived::Func()\n");}};int main(void){Base* b = new Base;b->Common_Func();b->Base_Func();Derived* d = new Derived;d->Common_Func();d->Base_Func();d->Derived_Func();return 0;}

分析工具:IDA Pro

静态反汇编:

.text:00401000    push    esi.text:00401001    push    4               ; 为 Base::VPTR 申请 4 个字节的栈空间.text:00401003    call    ??2@YAPAXI@Z    ; operator new(uint).text:00401008    add     esp, 4.text:0040100B    test    eax, eax.text:0040100D    jz      short loc_401019.text:0040100F    mov     dword ptr [eax], offset Base_VPTR ; *b 的堆空间放入 Base::VPTR.text:00401015    mov     esi, eax.text:00401017    jmp     short loc_40101B

对应的offset Base_VPTR:

.rdata:004060BC Base_VPTR       dd offset Base_Common_Func ; DATA XREF: _main+Fo.rdata:004060C0                 dd offset Base_Func.rdata:004060C4                 align 8

不难发现Base_VPTR正是VTABLE的首地址,即Base::VPTR所指向之处

由这一句:

.text:0040100F    mov    dword ptr [eax], offset Base_VPTR ;

不难发现,VPTR存放的位置是Base类实例b的开始处,即偏移值为0


.text:00401019.text:00401019 loc_401019:                              ; CODE XREF: _main+Dj.text:00401019    xor     esi, esi.text:0040101B.text:0040101B loc_40101B:                              ; CODE XREF: _main+17j.text:0040101B    mov     eax, [esi]                    ; eax = Base::VTABLE.text:0040101D    mov     ecx, esi.text:0040101F    call    dword ptr [eax]               ; call Base::Common_Func().text:00401021    mov     edx, [esi]                    ; edx = Base::VTABLE.text:00401023    mov     ecx, esi.text:00401025    call    dword ptr [edx+4]             ; call Base::Base_Func().text:00401028    push    4                             ; 为 Derived::VPTR 申请 4 个字节的栈空间.text:0040102A    call    ??2@YAPAXI@Z                  ; operator new(uint).text:0040102F    add     esp, 4.text:00401032    test    eax, eax.text:00401034    jz      short loc_401040.text:00401036    mov     dword ptr [eax], offset Derived_VPTR ; *d 的堆空间放入 Derived_VPTR.text:0040103C    mov     esi, eax.text:0040103E    jmp     short loc_401042.text:00401040 ; ---------------------------------------------------------------------------.text:00401040.text:00401040 loc_401040:                              ; CODE XREF: _main+34j.text:00401040    xor     esi, esi.text:00401042.text:00401042 loc_401042:                              ; CODE XREF: _main+3Ej.text:00401042    mov     eax, [esi]                    ; eax = Derived::VTABLE.text:00401044    mov     ecx, esi.text:00401046    call    dword ptr [eax]               ; call Derived::Common_Func().text:00401048    mov     edx, [esi]                    ; edx = Derived::VTABLE.text:0040104A    mov     ecx, esi.text:0040104C    call    dword ptr [edx+4]             ; call Base_Func(),Base_Func()是从Base类中继承而来的.text:0040104F    mov     eax, [esi]                    ; eax = Derived::VTABLE.text:00401051    mov     ecx, esi.text:00401053    call    dword ptr [eax+8]             ; call Derived::Derived_Func().text:00401056    xor     eax, eax.text:00401058    pop     esi.text:00401059    retn.text:00401059 _main           endp

对应的Derived_VPTR:

.rdata:004060B0 Derived_VPTR    dd offset Derived_Common_Func ; DATA XREF: _main+36o……(IDA 并没有将所有函数指针显示出来)

经过分析,Base和Derived的VTABLE结构大致如下:


在VTABLE中保存有函数的地址(即函数指针),而非函数本身或函数名,在Win32平台下,函数指针占用4个字节,当通过类实例调用虚函数时,VPTR就会在VTABLE中进行索引,若找到对应的虚函数,就会取出函数指针并进行调用

原创粉丝点击