C++ 程序怎么去逆向找虚表
虚表(vtable)的编译后形态
在 C++ 中,当一个类包含虚函数时,编译器会做两件事:
- 为该类生成一个虚表。这个虚表本质上是一个函数指针数组。数组中的每个元素都指向该类中一个虚函数的实际地址
- 为该类的每个对象(实例)在内存布局的开头添加一个隐藏的虚表指针(vptr)。这个指针指向该类的虚表
因此,逆向寻找虚表的过程,就是找到这个隐藏的 vptr
,并顺藤摸瓜找到它指向的虚表
逆向寻找虚表的三种主要方法
方法一:寻找虚表指针(vptr)的初始化
这是最直接也最常用的方法。虚表指针通常在对象的构造函数中被初始化
- 定位构造函数:在 C++ 程序中,当你使用
new
关键字创建一个对象时,编译器会调用该类的构造函数。在反汇编代码中,你会看到对new
操作的封装,然后是对构造函数的调用 - 查找虚表指针的赋值:在构造函数的开头,通常会有类似
mov [ecx], offset class_vtable
或mov [this], offset class_vtable
的指令(取决于调用约定和寄存器)this
指针(通常在ecx
或rcx
寄存器中)指向新创建的对象offset class_vtable
是虚表的地址,这是一个常量,通常由链接器确定- 这条指令的含义是:将虚表的地址存入对象的第一个成员变量中,这个变量就是
vptr
- 识别虚表:一旦你找到了虚表的地址,你就可以跳转到这个地址,IDA Pro 或 Ghidra 通常会将其识别为数据段中的一个指针数组
方法二:从虚函数的调用处反推
如果你无法直接找到构造函数,可以从虚函数的调用点入手。虚函数的调用通常是通过 vptr
进行的间接调用
- 识别间接调用:寻找类似
call [eax+offset]
或call [vptr]
的指令eax
通常包含this
指针,指向对象实例offset
是一个数字,通常是vptr
在对象内存布局中的偏移量(在单继承情况下通常是0
)- 这条指令的含义是:从
eax
指向的内存位置(即vptr
)获取一个地址,然后再加上一个偏移量,最终跳转到那个地址执行代码
- 分析偏移量:通过观察偏移量,你可以判断这是虚表中的第几个虚函数。例如,
call [eax+8]
意味着调用虚表中的第二个函数(因为每个函数指针通常是 4 或 8 字节) - 反推虚表:找到
vptr
的地址,然后跳转到该地址。你可以向上或向下遍历这个指针数组,来识别其他的虚函数
方法三:利用 IDA Pro 的自动化识别功能
IDA Pro 和 Ghidra 这样的高级反汇编器拥有强大的自动化分析能力,可以极大地简化寻找虚表的过程
- 启用 C++ RTTI 分析:在 IDA 的
Options -> General -> IDA
窗口中,确保 C++ 的RTTI (Run-Time Type Information) 分析选项已启用。这能帮助 IDA 识别类和虚表结构 - 函数识别:让 IDA 自动分析程序,它通常会尝试识别标准库中的虚表
- 数据段搜索:在 IDA 的数据段(通常是
.data
或.rdata
)中搜索,寻找指针数组。如果一个数组中的元素都是函数地址,并且这些函数之间有逻辑关联,那它很可能就是一个虚表。IDA 通常会把这些识别出来的虚表标记为vftable
或类似的名字