这片文章(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中没有重写vfunc2(),在ClassB的虚函数表中存放的是基类vfunc2()。