---
name: re-cpp-abi
type: atomic
description: >
  现代 C++ 二进制逆向：RTTI/异常/虚表恢复、ABI 识别、mangling 解码。
  触发词：C++逆向、RTTI、虚表恢复、异常处理、C++ ABI、mangling、C++反编译。
capabilities: [decompilation]
---

# 现代 C++ 逆向（RTTI / 异常 / 虚表）

## 何时使用 / 何时不用

- 用：RTTI/异常表密集的二进制、反编译结果混乱的 C++ 目标（类层次/虚调用/异常流无法直接读出）
- 不用：C 代码或纯汇编（走 [[re-binary-core]] 通用路径）；混淆主导的目标（先 [[re-deobfuscate]]）

## 工具准备

### readelf / llvm-objdump（节表与异常表）

- Linux: `apt install binutils llvm` / `dnf install binutils llvm` / `pacman -S binutils llvm`
- macOS: `brew install llvm`（binutils 部分 macOS 自带）
- Windows: WSL 或 llvm 预编译
- 验证: `readelf --version`、`llvm-objdump --version`

### c++filt / undname（mangling 解码）

- Linux/macOS: `c++filt`（binutils 自带）；Windows: `undname`（VS 工具链）
- 验证: `echo '_ZN3foo3barEv' | c++filt`（输出 `foo::bar()`）

### Ghidra / IDA（反编译底座，脚本化 RTTI 遍历）

- 安装与验证见 [[re-ghidra]] / [[re-ida]] 工具准备

### gdb（异常断点，可选）

- 安装与验证见 [[re-gdb]]

## 操作步骤

按顺序执行，每步产物存档（路径 + sha256，见 [[re-triage]]）。

1. **ABI 识别**：
   ```sh
   readelf -s sample | grep -E '_ZN|_ZTV|_ZTI' | head    # Itanium（GCC/Clang）
   strings sample | grep -E '^\?\?_' | head              # MSVC
   ```
   - Itanium 特征：`_ZN`（函数）、`_ZTV`（虚表）、`_ZTI`（RTTI 类型信息）
   - MSVC 特征：`??_7`（vftable）、`??_R`（RTTI）（strip 后 ??_ 在符号表而非字符串区——补查导入表 __CxxFrameHandler / msvcp 特征辅助判断）
   - 识别错则后续全部偏——先确认再继续

2. **RTTI 重建（Itanium）**：
   ```sh
   readelf -s sample | grep _ZTI | head
   # _ZTI<类名> 指向 typeinfo：起始为 vptr（指向 _ZTVN10__cxxabiv1... 类型信息虚表），
   # 下一 pointer-sized 槽为 __type_name；64 位通常 +0/+8、32 位 +0/+4
   # 解析前先定 ELF class / 指针宽度；__si_class_type_info / __vmi_class_type_info 同样按目标 ABI 对齐与字段宽度解析
   ```
   - 结构：`typeinfo` → `__class_type_info` 派生链 → 每个类的完整继承路径
   - 脚本化：Ghidra/IDA 遍历 _ZTI 引用，重建类继承图（父子关系表）
   - 产出：类名 → 继承链映射（写入会话 symbols_known，见 [[re-analyze/analysis-contract]]）
   - MSVC：`??_R0<类名>` TypeDescriptor 符号 + `_RTTICompleteObjectLocator`（COL）遍历重建继承图

3. **虚表恢复**：
   ```sh
   readelf -s sample | grep _ZTV | head
   ```
   - `_ZTV<类名>` 指向 vtable 起点（虚函数指针数组）；vtable 前缀两槽位（32 位 8 字节 / 64 位 16 字节）：offset to top + typeinfo 指针（Itanium ABI）
   - 定位 vtable 后：每个槽位的函数地址 → 调用点反推虚方法名（结合步骤 2 的继承图）
   - 虚调用（`call *reg`）无法静态定名 → 用调用点上下文（参数/返回值使用）缩小候选

4. **异常处理表**：
   ```sh
   readelf -S sample | grep -E 'eh_frame|gcc_except'   # ELF：.eh_frame（readelf 不解析 PE，PE 走下方命令）
   llvm-readobj --coff-unwind-info sample.exe            # PE：.pdata/.xdata（或 objdump -h / pefile）
   ```
   - PE：`.pdata` 的 RUNTIME_FUNCTION（Begin/End/UnwindInfo）→ `.xdata` 展开数据 → 异常处理器（__CxxFrameHandler3）
   - ELF：`.eh_frame` 的 FDE/CIE → 展开规则与 LSDA（.gcc_except_table）→ 异常处理函数
   - 用途：恢复被异常路径打断的控制流、定位析构/清理逻辑（catch 块）

5. **模板/lambda 识别**：
   - 模板：符号含 `<...>` 参数（Itanium mangling 中展开为长串）；实例化爆炸时按调用模式聚类
   - lambda：closure type 是真实的匿名 class type；普通调用场景常不发射独立 RTTI，缺 `_ZTI` 既不能排除也不能反推 lambda（typeid 等 ODR-use 时可有对应 RTTI）。识别优先用 Itanium 的 `_ZZ<作用域>ENK...` / `operator()` mangling、捕获成员布局与调用点，MSVC 的 `<lambda_...>`
   - 输出：疑似模板实例化/lambda 的函数清单 + 调用点

6. **mangling 解码（批量）**：
   ```sh
   readelf -s sample | grep -E '_ZN|_ZTV|_ZTI' | awk '{print $8}' | c++filt | head -20
   ```
   - MSVC: `undname` 或在线等价工具
   - 解码结果写入符号表（供 [[re-ghidra]] / [[re-ida]] 重命名）

## 跨域联合

- [[re-binary-core]] 网关：本技能归属（re-binary-core 选择树已挂载）
- [[re-ghidra]] / [[re-ida]]：反编译底座与脚本化
- [[re-deobfuscate]]：混淆与 ABI 分析衔接
- [[re-analyze/analysis-contract]]：类继承图/符号表按数据契约传递
- [[re-analyze/rerouting]]：RTTI/异常表特征触发本技能（A 表已挂）

## 常见坑与陷阱

- **ABI 误判导致全部解析失败**：现象——用 Itanium 结构解析 MSVC 目标（或反之）全盘错位；原因——识别步骤跳过；对策——先做步骤 1，mangling 特征双查
- **模板展开导致符号爆炸**：现象——readelf 输出几万行 `_Z...`；原因——模板实例化；对策——按调用模式聚类、过滤标准库符号（libstdc++/STL 前缀）
- **lambda 缺 RTTI 被当成无类型**：现象——类继承图缺节点，误以为 lambda 不是真实类型；原因——closure type 是真实的匿名 class type，只是普通调用场景常不发射独立 typeinfo，缺 `_ZTI` 既不能排除也不能反推；对策——按 `_ZZ<作用域>ENK...` / `operator()` mangling、捕获成员布局与调用点识别，不硬找 RTTI（typeid 等 ODR-use 时可另有 RTTI）
- **异常表版本差异**：现象——.xdata 解析错位；原因——MSVC 异常处理版本（__CxxFrameHandler3 等）不同；对策——按导入函数（__CxxFrameHandler）确认版本再解析
- **虚调用无法静态定名**：现象——`call *reg` 全是间接调用；原因——虚分派；对策——结合 vtable 槽位与调用点证据缩小候选，不猜
