九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

C++中的虚函数调用原理的反汇编实例分析(1)


C++中的虚函数调用原理的反汇编实例分析(1)

write by 九天雁翎(JTianLing) -- www.jtianling.com

具体的原理我并不想多讲,最好的办法是看《inside C++ Object》一书,我只是通过实际的反汇编代码来检验一下。。。。

其实仅仅是最近老在看反汇编的东西,这应该也属于逆向分析初步的知识吧,起码看到汇编代码能知道你看到的到底是什么

 

测试1

 1 #include <stdio.h>
 2 #include <string.h>
 3
 4 class CTestThisPointer
 5 {
 6 public:
 7     CTestThisPointer(int ai):mi(ai) { }
 8     virtual int Add(int ai)
 9     {
10         mi += ai;
11         return mi;
12     }
13     
14 private:
15     int mi;
16 };
17
18
19 int main()
20 {
21     CTestThisPointer loTest(10);
22
23     printf("%d",loTest.Add(5));
24     return 0;
25 }
26

VC6默认优化release编译

反汇编代码如下:

main函数

.text:00401000 ; Attributes: bp-based frame

.text:00401000

.text:00401000 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401000 _main           proc near               ; CODE XREF: _mainCRTStartup+AFp

.text:00401000

.text:00401000 var_8           = byte ptr -8

.text:00401000 argc            = dword ptr  8

.text:00401000 argv            = dword ptr  0Ch

.text:00401000 envp            = dword ptr  10h

.text:00401000

.text:00401000                 push    ebp

.text:00401001                 mov     ebp, esp

.text:00401003                 sub     esp, 8

.text:00401006                 push    0Ah

.text:00401008                 lea     ecx, [ebp+var_8]

.text:0040100B                 call    CTestThisPointer__CTestThisPointer

.text:00401010                 push    5

.text:00401012                 lea     ecx, [ebp+var_8]

.text:00401015                 call    CTestThisPointer__Add ; 呵呵,此处因为是通过对象访问的,所以其实根本没有用到

.text:00401015                                         ; 虚表,我疏忽了........

.text:0040101A                 push    eax

.text:0040101B                 push    offset aD       ; "%d"

.text:00401020                 call    _printf

.text:00401025                 add     esp, 8

.text:00401028                 xor     eax, eax

.text:0040102A                 mov     esp, ebp

.text:0040102C                 pop     ebp

.text:0040102D                 retn

.text:0040102D _main           endp

 

 

构造函数:

.text:00401030 CTestThisPointer__CTestThisPointer proc near ; CODE XREF: _main+Bp

.text:00401030

.text:00401030 this            = dword ptr -4

.text:00401030 arg_0           = dword ptr  8

.text:00401030

.text:00401030                 push    ebp

.text:00401031                 mov     ebp, esp

.text:00401033                 push    ecx

.text:00401034                 mov     [ebp+this], ecx ; 以下可以看到,此临时变量主要用于存储this指针,所以

.text:00401034                                         ; 我直接将其改名了

.text:00401037                 mov     eax, [ebp+this]

.text:0040103A                 mov     ecx, [ebp+arg_0]

.text:0040103D                 mov     [eax+4], ecx    ; 将构造函数的参数保存在this+4的位置上

.text:00401040                 mov     edx, [ebp+this]

.text:00401043                 mov     dword ptr [edx], offset const CTestThisPointer::`vftable' ;

.text:00401043                                         ; 此处可以看出在VC中虚表其实是放在一个类的最开始位置的,

.text:00401043                                         ; 这点的解释可以参考《inside C++ Ojbect》一书

.text:00401049                 mov     eax, [ebp+this] ; 此句用途不明,返回this?外部也没有使用此eax寄存器

.text:0040104C                 mov     esp, ebp

.text:0040104E                 pop     ebp

.text:0040104F                 retn    4               ; 可以看到在这里C++的函数默认是__stdcall调用约定的

.text:0040104F CTestThisPointer__CTestThisPointer endp

 

CTestThisPointer虚表所在位置只读代码段片段

.rdata:004070DC const CTestThisPointer::`vftable' db 60h,10h,40h,0

.rdata:004070DC                                         ; DATA XREF: CTestThisPointer__CTestThisPointer+13o

.rdata:004070DC                                         ; 因为只有唯一的函数Add,此处是Add函数的地址,经检验

.rdata:004070DC                                         ; 401060的确是Add函数的地址

.rdata:004070DC                                         ; 有个疑问留待进行更多测试,那就是此虚表中没有运行时

.rdata:004070DC                                         ; 类型识别需要的标志(RTTI),有两种可能,一是VC6没有

.rdata:004070DC                                         ; 实现此功能,二是因为此处不需要,所以优化掉了

 

Add函数:

.text:00401060 ; 可以看到此地址的确与虚表中的地址一致

.text:00401060 ; Attributes: bp-based frame

.text:00401060

.text:00401060 CTestThisPointer__Add proc near         ; CODE XREF: _main+15p

.text:00401060

.text:00401060 var_4           = dword ptr -4

.text:00401060 arg_0           = dword ptr  8

.text:00401060

.text:00401060                 push    ebp

.text:00401061                 mov     ebp, esp

.text:00401063                 push    ecx

.text:00401064                 mov     [ebp+var_4], ecx

.text:00401067                 mov     eax, [ebp+var_4]

.text:0040106A                 mov     ecx, [eax+4]

.text:0040106D                 add     ecx, [ebp+arg_0]

.text:00401070                 mov     edx, [ebp+var_4]

.text:00401073                 mov     [edx+4], ecx

.text:00401076                 mov     eax, [ebp+var_4]

.text:00401079                 mov     eax, [eax+4]

.text:0040107C                 mov     esp, ebp

.text:0040107E                 pop     ebp

.text:0040107F                 retn    4

.text:0040107F CTestThisPointer__Add endp

 

测试2

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <typeinfo.h>
 4
 5 class CTestThisPointer
 6 {
 7 public:
 8     CTestThisPointer(int ai):mi(ai) { }
 9     virtual int Add(int ai)
10     {
11         mi += ai;
12         return mi;
13     }
14     
15 private:
16     int mi;
17 };
18
19
20 int main()
21 {
22     CTestThisPointer loTest(10);
23     CTestThisPointer *lp = &loTest;
24
25     printf("%d/n",lp->Add(5));
26     // Test If VC6 implement RTTI
27     printf("%s/n",typeid(lp).name());
28     return 0;
29 }
30

 

反汇编:

主函数:

.text:00401000     ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401000     _main   proc near                       ; CODE XREF: _mainCRTStartup+AFp

.text:00401000

.text:00401000     thisTemp2= dword ptr -0Ch

.text:00401000     thisTemp= byte ptr -8

.text:00401000     argc    = dword ptr  8

.text:00401000     argv    = dword ptr  0Ch

.text:00401000     envp    = dword ptr  10h

.text:00401000

.text:00401000 000         push    ebp

.text:00401001 004         mov     ebp, esp

.text:00401003 004         sub     esp, 0Ch                ; Integer Subtraction

.text:00401006 010         push    0Ah

.text:00401008 014         lea     ecx, [ebp+thisTemp]     ; Load Effective Address

.text:0040100B 014         call    CTestThisPointer__CTestThisPointer ; Call Procedure

.text:00401010 010         lea     eax, [ebp+thisTemp]     ; Load Effective Address

.text:00401013 010         mov     [ebp+thisTemp2], eax

.text:00401016 010         push    5

.text:00401018 014         mov     ecx, [ebp+thisTemp2]

.text:0040101B 014         mov     edx, [ecx]

.text:0040101D 014         mov     ecx, [ebp+thisTemp2]

.text:00401020 014         call    dword ptr [edx]         ; 直接调用了this指针所在位置的函数,即vtbl中唯一的函数

.text:00401020                                             ; Add,可能这也是将虚表放在整个C++对象第一个位置的原因

.text:00401020                                             ; 吧,因为这样虚函数的调用寻址最短

.text:00401022 010         push    eax

.text:00401023 014         push    offset aD               ; "%d/n"

.text:00401028 018         call    _printf                 ; Call Procedure

.text:0040102D 018         add     esp, 8                  ; Add

.text:00401030 010         mov     ecx, offset CTestThisPointer * `RTTI Type Descriptor' ; 数据段上编译器已经存在的type_info对象。。。。。

.text:00401035 010         call    type_info::name(void)   ; 此函数实现不深究了,不过咋一看就觉得效率奇低,竟然

.text:00401035                                             ; 还需要分配释放内存,malloc,free,strcpy.........晕掉了

.text:00401035                                             ; 主要实现功能的函数是系统_unDName

.text:0040103A 010         push    eax

.text:0040103B 014         push    offset aS_0             ; "%s/n"

.text:00401040 018         call    _printf                 ; Call Procedure

.text:00401045 018         add     esp, 8                  ; Add

.text:00401048 010         xor     eax, eax                ; Logical Exclusive OR

.text:0040104A 010         mov     esp, ebp

.text:0040104C 004         pop     ebp

.text:0040104D 000         retn                            ; Return Near from Procedure

.text:0040104D     _main   endp

 

其他内容没有变!这代表在VC6RTTI的实现不是通过在虚表中加内容的方式实现的(《inside C++ Object》只提到这种实现方式),并且实际中分析代码,感觉是开始就为要识别类型的类加了一个特定的type_info类,然后主要是由__unDName这个系统函数实现了类型的鉴别。

__unDName函数嘛,调用了

.text:004015A4 080         call    Replicator::Replicator(void) ; Call Procedure

.text:004015A9 080         lea     ecx, [ebp+var_3C]       ; Load Effective Address

.text:004015AC 080         call    Replicator::Replicator(void) ; Call Procedure

.text:004015B1 080         mov     eax, [ebp+arg_4]

.text:004015B4 080         lea     ecx, [ebp+var_78]       ; Load Effective Address

.text:004015B7 080         mov     char const * const UnDecorator::name, eax

.text:004015BC 080         mov     char const * const UnDecorator::gName, eax

.text:004015C1 080         mov     eax, [ebp+arg_8]

.text:004015C4 080         mov     char * (*UnDecorator::m_pGetParameter)(long), esi

.text:004015CA 080         mov     int UnDecorator::maxStringLength, eax

.text:004015CF 080         mov     eax, [ebp+arg_0]

.text:004015D2 080         mov     char * UnDecorator::outputString, eax

.text:004015D7 080         lea     eax, [ebp+var_3C]       ; Load Effective Address

.text:004015DA 080         mov     Replicator * UnDecorator::pZNameList, eax

.text:004015DF 080         lea     eax, [ebp+var_78]       ; Load Effective Address

.text:004015E2 080         mov     Replicator * UnDecorator::pArgList, eax

.text:004015E7 080         movzx   eax, [ebp+arg_14]       ; Move with Zero-Extend

.text:004015EB 080         mov     ulong UnDecorator::disableFlags, eax

.text:004015F0 080         call    UnDecorator::operator char *(void) ; Call Procedure

.text:004015F5 080         mov     ecx, offset dword_40F920

.text:004015FA 080         mov     esi, eax

.text:004015FC 080         call    HeapManager::Destructor(void) ; Call Procedure

.text:00401601 080         mov     eax, esi

 

这么多类和函数。。。。。。。。。这些已经超过windows API的范围了,估计是属于VC6编译器内部实现的类了,呵呵,因为Windows内部实现不是主要用C吗?上面用了那么多类,应该不是Windows XP的代码吧。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

分类:  汇编和反汇编 
标签:  C++  虚函数 

Posted By 九天雁翎 at 九天雁翎的博客 on 2009年06月14日

前一篇: C++中的成员函数调用原理及this指针的传递方式 后一篇: C++中的虚函数调用原理的反汇编实例分析(2)