C++逆向学习(四) 类
字数 1522 2025-08-05 08:18:36
C++逆向工程:类结构分析详解
1. 类的基本结构分析
1.1 this指针机制
在C++逆向分析中,识别this指针是理解类成员函数的关键:
- Linux环境下(g++编译):this指针通过rdi寄存器传递
- Windows环境下(MSVC编译):this指针通过rcx寄存器传递
识别特征:
- 如果一个函数在rcx/rdi寄存器未初始化前就被使用,很可能是类的成员函数
- 成员函数第一个参数通常是this指针
1.2 类成员变量布局
类成员变量在内存中按照声明顺序排列,考虑对齐规则:
class base {
public:
int a; // 4字节
double b; // 8字节
// 总大小:16字节(考虑对齐)
};
内存布局:
+---+---+---+---+---+---+---+---+
| a (int) | padding |
+---+---+---+---+---+---+---+---+
| b (double) |
+---+---+---+---+---+---+---+---+
2. 构造函数与析构函数分析
2.1 构造函数特征
构造函数的关键识别特征:
-
虚表指针初始化:
- 构造函数首先将虚表地址赋值给类实例的首字段
- 这是识别构造函数的最重要标志
-
初始化顺序:
- 基类构造函数先于派生类构造函数执行
- 成员变量按照声明顺序初始化
2.2 析构函数特征
析构函数的关键识别特征:
-
虚表指针重置:
- 析构函数也会重新赋值虚表指针
- 确保在析构过程中调用虚函数时行为正确
-
调用顺序:
- 派生类析构函数先于基类析构函数执行
- 与构造函数顺序相反
3. 虚函数机制分析
3.1 虚表结构
虚表(vtable)是虚函数调用的核心:
-
虚表指针:
- 位于类实例的首地址
- 指向虚函数表
-
虚表内容:
- 按声明顺序存放虚函数指针
- 虚表前有typeinfo信息(RTTI)
// 获取虚表指针示例
int** vtable = *(int***)object_ptr;
3.2 虚函数调用过程
虚函数调用在汇编层面的表现:
- 通过对象指针获取虚表指针
- 从虚表中获取目标函数地址
- 调用该函数,传递this指针
; 示例x86_64汇编
mov rax, [rdi] ; 获取虚表指针
mov rax, [rax] ; 获取第一个虚函数地址
call rax ; 调用虚函数
4. 特殊构造场景分析
4.1 全局/静态对象构造
全局和静态对象的构造由编译器生成的代理函数处理:
-
构造时机:
- 在main函数执行前完成构造
- 通过
initterm系列函数调用构造代理
-
识别方法:
- 查找
atexit调用点(析构注册) - 构造代理函数通常在附近
- 查找
4.2 构造/析构代理函数
-
构造代理函数:
- 调用实际构造函数
- 注册析构函数到
atexit
-
析构代理函数:
- 通过
atexit注册 - 在程序退出时调用
- 通过
5. 继承关系分析
5.1 单继承
简单继承的内存布局:
- 基类成员在前
- 派生类成员在后
- 只有一个虚表指针
5.2 多重继承
多重继承的复杂情况:
class Derived : public Base1, public Base2 {
// ...
};
内存布局:
- Base1部分(包含虚表指针)
- Base2部分(包含虚表指针)
- Derived新增成员
5.3 虚继承(菱形继承)
虚继承的特殊内存布局:
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
关键特征:
- 虚基类(A)的数据被放在公共位置
- 派生类(B/C)保存指向虚基类的偏移量
- 避免了数据冗余
逆向识别要点:
- 查找额外的偏移量字段
- 注意非直接访问基类成员的情况
6. RTTI(运行时类型识别)分析
RTTI相关数据结构:
-
typeinfo:
- 位于虚表前一个位置
- 包含类型名称、继承关系等信息
-
RTTICompleteObjectLocator:
- 通过虚表指针-1的位置访问
- 包含完整的类型信息
// 获取RTTI信息示例
int** vtable = *(int***)object_ptr;
RTTICompleteObjectLocator* rtti = *(RTTICompleteObjectLocator**)(vtable[-1]);
7. 逆向分析技巧总结
-
识别类结构的线索:
- 虚表指针初始化 → 构造函数
- 虚表指针重置 → 析构函数
- 特定寄存器作为第一个参数 → 成员函数
-
工具使用建议:
- 在IDA中添加类/结构体定义
- 利用交叉引用追踪虚表使用
- 关注
atexit调用点找到全局对象
-
调试技巧:
- 对
atexit下断点定位全局对象构造 - 跟踪虚表指针变化理解对象生命周期
- 对
8. 推荐学习资源
-
《深度探索C++对象模型》- Stanley B. Lippman
- 详细讲解C++对象内存布局的历史和设计考量
-
《逆向工程核心原理》- 李承远
- 包含C++逆向分析的实用技巧
-
IDA Pro官方文档
- 学习如何有效使用IDA进行C++逆向分析