这一部分专门复盘 C++ 对象在二进制里的样子。源码里的类、继承、多态、虚函数,进入反汇编后会变成对象地址、成员偏移、虚表指针、间接调用和构造析构序列。

1、内容范围

小节 内容 复盘点
16.7 - 16.9 对象模型与 thiscall 对象存储、成员偏移、this 指针
17.0 - 17.3 对象传参返回 调用约定、类对象返回、编译器优化
17.4 - 17.9 构造析构与拷贝 构造触发、堆对象、全局对象、深拷贝
18.0 - 18.2 构造限制与常成员 默认参数、静态成员、const 成员函数
18.3 - 18.7 继承对象模型 单继承、多继承、菱形继承
18.8 - 19.4 多态与虚表 虚函数指针、虚表、多态调用、纯虚对象
19.5 - 19.6 模板使用与特化 模板实例化、特化和代码拷贝

2、对象模型

普通 C++ 对象通常只保存成员变量;成员函数在代码区。调用成员函数时会传入 this 指针。

1
2
3
4
5
对象内存:
[成员变量 1][成员变量 2][...]

成员函数:
代码区中的普通函数 + 隐式 this 参数

3、thiscall 与成员偏移

x86 thiscall 中,ECX 常保存 this。成员访问可能表现为:

1
mov eax, [ecx+4]

还原思路:

  1. ecx 很可能是对象地址。
  2. +4 是成员偏移。
  3. 访问宽度决定成员大小。

4、静态成员

静态成员不属于单个对象,通常像全局变量一样保存。静态成员函数没有 this 指针。

5、对象传参和返回

小对象可能按值传递;大对象或带复杂构造的对象可能通过隐藏指针传递/返回。优化等级不同,表现也不同。

场景 逆向观察
引用参数 传对象地址
指针参数 传对象地址
值参数 可能复制对象
返回对象 可能使用隐藏返回指针

6、构造函数与析构函数

构造函数负责建立对象有效状态,析构函数负责释放资源。

逆向观察点:

现象 说明
初始化成员 给对象偏移写入初值
设置虚表指针 有虚函数的对象常见
调用基类构造 继承对象先构造基类
析构顺序相反 先析构派生类,再析构基类

7、继承布局

单继承通常是基类子对象在前,派生类成员在后。多继承会出现多个基类子对象,指针转换可能需要调整地址。

继承类型 复盘重点
单继承 基类字段 + 派生字段
多继承 多个基类区域和多个虚表可能出现
菱形继承 重复基类或虚继承布局

8、虚表与多态

有虚函数的类通常含有虚表指针。虚表里保存虚函数地址。

1
对象 -> vptr -> vtable -> virtual function address

动态绑定常表现为:

1
2
mov eax, [ecx]      ; 取虚表指针
call dword ptr [eax+offset]

9、纯虚函数与抽象类

纯虚函数让类成为抽象类。逆向时可能看到纯虚函数占位、运行库 purecall 相关行为。

10、模板与特化

模板是编译期生成代码。不同类型实例化后,可能产生多份相似函数。特化会让某些类型走独立实现。

11、学习检查清单

检查项 状态
能说明对象成员和成员函数分别在哪里 待复盘
能根据 this + offset 推测成员访问 待复盘
能识别构造函数中设置虚表指针的行为 待复盘
能画出单继承和多继承的大致对象布局 待复盘
能解释虚表间接调用和多态的关系 待复盘