0%

C++虚函数探究

对于c++函数的虚函数的学习

基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <cstdlib>
#include <iostream>
struct Mammal {
Mammal() { std::cout << "Mammal::Mammal\n"; }
virtual ~Mammal() { std::cout << "Mammal::~Mammal\n"; };
virtual void run() = 0;
virtual void walk() = 0;
virtual void move() { walk(); }
};
struct Cat : Mammal {
Cat() { std::cout << "Cat::Cat\n"; }
virtual ~Cat() { std::cout << "Cat::~Cat\n"; }
virtual void run() { std::cout << "Cat::run\n"; }
virtual void walk() { std::cout << "Cat::walk\n"; }
};
struct Dog : Mammal {
Dog() { std::cout << "Dog::Dog\n"; }
virtual ~Dog() { std::cout << "Dog::~Dog\n"; }
virtual void run() { std::cout << "Dog::run\n"; }
virtual void walk() { std::cout << "Dog::walk\n"; }
};
int main() {
Mammal * m;
if (rand() % 2) {
m = new Cat();
} else {
m = new Dog();
}
m->walk();
delete m;
}

g++编译g++ -m32 -fno-rtti -O1 file.cpp

然后用strip命令去掉所有符号信息 strip -s --strip-all a.out

对比前后文件大小

逆向部分

IDA 载入后

完全没法分析,下面我们开始分析虚函数表

C++虚函数基础:对于每种有虚函数的类,编译器将一个被称为vtable的函数指针表插入生成的二进制文件。这种类型的每个实例对象将多出一个被称为vptr的额外成员指向正确的vtable。用来以正确值初始化这个指针的代码将被添加到构造函数里

首先我们根据已知的信息找到虚表的位置


由于我们有源码这里我们知道有三个类,根据这些函数的内容,我们确定这三个分别是哺乳动物,猫,狗的虚表vtable

但是我们知道cat和dog明明只有四个虚函数,为何这是五个呢,跟踪进去后我们发现前面两个都是析构函数,查资料知道第一个只会破坏对象的成员,第二个将删除对象的已分配内存
,我们跟踪进去也发现第二个函数多一个delete删除内存的操作

最终修改如下

但是重新对main函数进行反编译仍然不可读

这就需要定义并应用structure了
下面我们开始
先定义每个类的vptr额外成员

而后我们定义各自的虚表

而后很关键一步:为每个vptr成员设置类型
具体操作和在伪代码界面设置变量类型一样
以Dog类为例子

后重新对main函数进行反编译,并更改v0类型为Cat *

这样一看简单多了

如果更改类型为 Mammal * ,那么

很明显会出异常,而终止的

但是很奇怪的是我们在编写代码的时候明明是定义的Mammal * 类型,这是因为我们定义的是编译时类型(即静态类型),但是v0的动态类型(或运行时类型)才能决定在一个虚拟函数调用里哪个函数将被调用。

over!!!

参考文章 :
逆向C++虚函数(一)