逆向基础(三)--- IDA中的虚函数

这片文章(C++中的多态及实现原理(虚函数))阐述了多态的使用以及利用虚函数实现多态的原理,主要是从正向侧说明。我们在逆向过程中也会经常遇到虚函数的使用,在逆向中,在IDA中,虚函数又是什么样子的呢?

本文实例代码:

/*
C++多态测试实例
*/

#include <string>
#include <iostream>
using namespace std;

//基类
class Base {
public:
    virtual void vfunc1()
    {
        printf("Base::vfunc1\r\n");
    }
    virtual void vfunc2()
    {
        printf("Base::vfunc2\r\n");
    }
    virtual void vfunc3()
    {
        printf("Base::vfunc3\r\n");
    }
    virtual void vfunc4()
    {
        printf("Base::vfunc4\r\n");
    }
    void func1()
    {
        printf("Base::func1\r\n");
    }
    void func2()
    {
        printf("Base::func2\r\n");
    }
public:
    int m_data1, m_data2;
};
//无虚函数的基类
class BaseWithoutV {
public:
     void vfunc1()
    {
        printf("BaseWithoutV::vfunc1\r\n");
    }
     void vfunc2()
    {
        printf("BaseWithoutV::vfunc2\r\n");
    }
     void vfunc3()
    {
        printf("BaseWithoutV::vfunc3\r\n");
    }
     void vfunc4()
    {
        printf("BaseWithoutV::vfunc4\r\n");
    }
    void func1()
    {
        printf("BaseWithoutV::func1\r\n");
    }
    void func2()
    {
        printf("BaseWithoutV::func2\r\n");
    }
private:
    int m_data1, m_data2;
};

//派生类A
class A: public Base {
public:
    virtual void vfunc1()
    {
        printf("A::vfunc1\r\n");
    }
    void func1(int a)
    {
        printf("A::func1 with %d\r\n",a);
    }
    void func1()
    {
        printf("A::func1\r\n");
    }
private:
    int m_data3;
};
//派生类B
class B : public Base {
public:
    virtual void vfunc1()
    {
        printf("B::vfunc1\r\n");
    }
    virtual void vfunc3()
    {
        printf("B::vfunc3\r\n");
    }
    void func2()
    {
        printf("B::func2\r\n");
    }
private:
    int m_data1, m_data4;
};


int main()
{
    //动态联编调用,实现动态多态
    A object_a;          
    B object_b;      

    Base* p = &object_a;    //隐式向下转换基类指针p 指向类A对象object_a
    p->vfunc1();           //基类指针指向对象A,调用A类虚函数vfunc1

    p = &object_b;          //隐式向下转换基类指针p 指向类B对象object_b
    p->vfunc3();           //基类指针指向对象B,调用B类虚函数vfunc3

    system("pause");
}

IDA中虚函数的构造函数

我们知道编译器处理虚函数的方法是:给每一个对象添加一个隐藏的成员—虚函数表指针,该指针指向一个虚函数表。编译器必须要保证虚函数表的指针存在于对象实例中最前面的位置。
来看实例中派生类B的构造,从反汇编可以看出构造过程,派生类B的构造首先进行基类构造,然后才进行派生类B的构造。
有关函数调用,参数传递,引用等可以参考:逆向基础(二)— 函数调用过程完整分析,这里就不注解了。

text:00411830 ; B *__thiscall B::B(B *__hidden this)
.text:00411830 ??0B@@QAE@XZ    proc near               ; CODE XREF: B::B(void)↑j
.text:00411830
.text:00411830 var_CC          = byte ptr -0CCh
.text:00411830 var_8           = dword ptr -8
.text:00411830
.text:00411830                 push    ebp
.text:00411831                 mov     ebp, esp
.text:00411833                 sub     esp, 0CCh
.text:00411839                 push    ebx
.text:0041183A                 push    esi
.text:0041183B                 push    edi
.text:0041183C                 push    ecx
.text:0041183D                 lea     edi, [ebp+var_CC]
.text:00411843                 mov     ecx, 33h
.text:00411848                 mov     eax, 0CCCCCCCCh
.text:0041184D                 rep stosd
.text:0041184F                 pop     ecx
.text:00411850                 mov     [ebp+var_8], ecx
.text:00411853                 mov     ecx, [ebp+var_8] ; this
.text:00411856                 call    j_??0Base@@QAE@XZ ; 基类构造
.text:0041185B                 mov     eax, [ebp+var_8]
.text:0041185E                 mov     dword ptr [eax], offset ??_7B@@6B@ ; ClassB的虚函数表覆盖了上面存放的基类虚函数表
.text:00411864                 mov     eax, [ebp+var_8]
.text:00411867                 pop     edi
.text:00411868                 pop     esi
.text:00411869                 pop     ebx
.text:0041186A                 add     esp, 0CCh
.text:00411870                 cmp     ebp, esp
.text:00411872                 call    j___RTC_CheckEsp
.text:00411877                 mov     esp, ebp
.text:00411879                 pop     ebp
.text:0041187A                 retn

ClassB的虚函数表如下:
.rdata:00417BC4 ; const B::`vftable’
.rdata:00417BC4 ??7B@@6B@ dd offset j?vfunc1@B@@UAEXXZ
.rdata:00417BC4 ; DATA XREF: B::B(void)+2E↑o
.rdata:00417BC4 ; B::vfunc1(void)
.rdata:00417BC8 dd offset j_?vfunc2@Base@@UAEXXZ ; Base::vfunc2(void)
.rdata:00417BCC dd offset j_?vfunc3@B@@UAEXXZ ; B::vfunc3(void)
.rdata:00417BD0 dd offset j_?vfunc4@Base@@UAEXXZ ; Base::vfunc4(void)
构造完成,this指针内存情况如下图
在这里插入图片描述

IDA中虚函数的调用

截取mian中的关键反汇编代码,调用过程详见注释说明

.text:00E61DAE                 mov     eax, ___security_cookie
.text:00E61DB3                 xor     eax, ebp
.text:00E61DB5                 mov     [ebp+var_4], eax
.text:00E61DB8                 mov     ecx, offset unk_E6C027
.text:00E61DBD                 call    j_@__CheckForDebuggerJustMyCode@4 ; __CheckForDebuggerJustMyCode(x)
.text:00E61DC2                 lea     ecx, [ebp+var_18] ; ClassA的this,thiscall 使用ecx传输this指针
.text:00E61DC5                 call    j_??0A@@QAE@XZ  ; ClassA的构造函数
.text:00E61DCA                 lea     ecx, [ebp+var_34] ; ClassB的this
.text:00E61DCD                 call    j_??0B@@QAE@XZ  ; ClassB的构造函数
.text:00E61DD2                 lea     eax, [ebp+var_18] ; ClassA的this
.text:00E61DD5                 mov     [ebp+var_40], eax
.text:00E61DD8                 mov     eax, [ebp+var_40]
.text:00E61DDB                 mov     edx, [eax]      ; 从ClassA的this中获取虚函数表存放到edx中
.text:00E61DDD                 mov     esi, esp
.text:00E61DDF                 mov     ecx, [ebp+var_40]
.text:00E61DE2                 mov     eax, [edx]      ; 从ClassA虚函数表中索引虚函数vfunc1()
.text:00E61DE4                 call    eax             ; 虚函数调用 A::vfunc1()
.text:00E61DE6                 cmp     esi, esp
.text:00E61DE8                 call    j___RTC_CheckEsp
.text:00E61DED                 lea     eax, [ebp+var_34] ; ClassB的this
.text:00E61DF0                 mov     [ebp+var_40], eax
.text:00E61DF3                 mov     eax, [ebp+var_40]
.text:00E61DF6                 mov     edx, [eax]      ; 从ClassB的this中获取虚函数表存放到edx中
.text:00E61DF8                 mov     esi, esp
.text:00E61DFA                 mov     ecx, [ebp+var_40]
.text:00E61DFD                 mov     eax, [edx+8]    ; 从ClassB虚函数表中索引虚函数vfunc3(),注意+8对应第三个虚函数
.text:00E61E00                 call    eax             ; 虚函数调用 B::vfunc3()
.text:00E61E02                 cmp     esi, esp
.text:00E61E04                 call    j___RTC_CheckEsp

IDA中整个调用流程如下图:
虚函数的调用过程
ClassB中的虚函数调用同ClassA,需要注意的是这里
ClassB虚函数调用当派生类中如果没有重写虚函数时,这时候调用没有重写的虚函数,直接调用该派生类继承的基类对应的虚函数。如下图ClassB中没有重写vfunc2(),在ClassB的虚函数表中存放的是基类vfunc2()。
未重写的虚函数

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值