C++逆向分析——多态和虚表

发布时间 2023-04-09 10:54:30作者: bonelee

虚表

上一章了解了多态,那么我们来了解一下多态在C++中是如何实现的。

了解本质,那就通过反汇编代码去看就行了,首先我们看下非多态的情况下的反汇编代码:

images/download/attachments/12714553/image2021-4-15_23-14-19.png

然后再来看下多态情况下的反汇编代码:

images/download/attachments/12714553/image2021-4-15_23-18-22.png

很明显这里多态的情况下会根据edx间接调用,而非多态则会直接调用。

那么我们来看下间接调用的流程是什么:

  1. ebp+8地址对应的值给到eax(ebp+8 也就是函数的参数 → 当前参数指针【父类指针】)

  2. eax地址对应的值给到edx(eax相当于当前对象的第一个成员)

  3. 调用edx地址对应的值,也就是子类对象的Print函数

但是这里很奇怪,第一个成员为什么就能是Print函数呢?跟我们之前理解的4个字节的参数完全不一样。

那么编译器到底是做了什么工作,才能根据我们传入的对象来进行间接调用的呢?这是因为虚表

只要有虚函数,不论多少个,对象的数据宽度就会比其原来多出4个字节,这四个字节我们称之为虚表。

images/download/attachments/12714553/image2021-4-15_23-54-33.png

images/download/attachments/12714553/image2021-4-15_23-54-26.png

那么虚表在哪呢?可以通过VC6来寻找虚标,先创建对象然后下断点运行查看,如下图中,可以很清晰的看见对象t除了继承Person父类的Age、Sex以及本身的Level成员外,还有一个__vfptr,上面有一个地址就是0x00422024,那这个地址就是虚表,这个表里面存储的就是函数的地址:

images/download/attachments/12714553/image2021-4-16_0-1-56.png

我们可以调出内存窗口查看一下:

images/download/attachments/12714553/image2021-4-16_0-6-6.png

这个存储的地址就是0x00401037,这时候切到反汇编代码就然后Ctrl+G输入跟进这个地址:

images/download/attachments/12714553/image2021-4-16_0-8-14.png

那这个地址就是Teacher的成员函数Print的地址。

虚表的结构:虚表中存储的都是函数地址,每个地址占用4个字节,有几个虚函数,则就有几个地址。

子类没有重写时,虚表中则只有父类自己的成员函数地址,反之,当子类重写虚函数时候,虚表中则存在父类自己的成员函数地址与子类重写的成员函数地址。

 

纯虚函数

 

之前学习过虚函数,也提到了纯虚函数,虽然纯虚函数语法很简单的,但是其比较难理解,所以在有一定的面向对象的基础时候再来学习会比较容易理解一些。

纯虚函数语法:

  1. 将成员函数声明为 virtual

  2. 该函数没有函数体,最后跟=0

class Base {
public:
virtual int Plus() = 0;
}

语法不过多的阐述,之前也有写过;接下来我们要了解一个新的概念:抽象类。

抽象类有这几种特征:

  1. 含有纯虚函数的类,称之为抽象类

  2. 抽象类也可以包含普通的函数;

  3. 抽象类不能实例化(创建对象)。

那么问题来了,抽象类有什么意义呢?我们可以把抽象类看作是对子类的一种约束,或者认为其(抽象类)就是定义一种标准。

比如:淘宝,有很多店铺,虽然每个店铺卖的东西都不一样,但是他们同样都可以下单、评论、购物车,也就是说他们都遵守了这种标准规则;也就是说你可以把淘宝当作一个抽象类,其有很多成员:购物车、评论、商品展示区...但是他都没有定义,而是交给开淘宝店的人(子类)去根据标准规则定义。

images/download/attachments/12714553/image2021-4-17_16-20-55.png

而如果不按照这种标准呢来,那么假如要统计所有的数据就会非常麻烦,不便于管理。