阶段三:进阶编程
📅 创建日期: 2025-01-XX
⏱️ 预计学时: 6-8 周
🎯 学习目标: 掌握 C++ 面向对象编程、x64 汇编、保护研究基础
📋 前置要求: [[阶段二:初级逆向]]
🔗 返回: [[逆向与驱动开发学习路径(详细版)]]
📚 本阶段内容概览
- [[#3.1 C++编程|3.1 C++编程]]
- [[#3.1.1 面向对象编程|面向对象编程]]
- [[#3.1.2 虚表机制|虚表机制]]
- [[#3.1.3 STL容器逆向|STL容器逆向]]
- [[#3.2 x64汇编|3.2 x64汇编]]
- [[#3.2.1 x64寄存器|x64寄存器]]
- [[#3.2.2 x64调用约定|x64调用约定]]
- [[#3.2.3 x64特有指令|x64特有指令]]
- [[#3.3 保护研究入门|3.3 保护研究入门]]
3.1 C++编程
3.1.1 面向对象编程
📝 学习笔记
类与对象的内存布局
// 简单类的内存布局
class SimpleClass {
public:
int a; // offset: 0
char b; // offset: 4
// padding: 3 bytes
double c; // offset: 8
};
// sizeof(SimpleClass) = 16 (考虑内存对齐)
// 带虚函数的类
class VirtualClass {
public:
virtual void func1() {} // 虚表指针在最前
virtual void func2() {}
int member; // offset: 8 (x64)
};
// sizeof(VirtualClass) = 16 (x64: 8字节vptr + 4字节int + 4字节padding)
继承的内存布局
class Base {
public:
int baseValue; // offset: 0
virtual void baseFunc() {}
};
class Derived : public Base {
public:
int derivedValue; // offset: 基类大小之后
virtual void derivedFunc() {}
};
// 多重继承
class A { int a; virtual void funcA() {} };
class B { int b; virtual void funcB() {} };
class C : public A, public B {
int c;
// C有两个虚表指针!
};
[!important] 关键概念
- 单继承: 只有一个虚表指针,派生类虚函数追加到基类虚表
- 多重继承: 每个有虚函数的基类都有自己的虚表指针
- 虚继承: 使用虚基类表(vbtable)解决菱形继承问题
3.1.2 虚表机制
📝 学习笔记
虚表结构深度分析
class Animal {
public:
virtual void speak() { cout << "Animal" << endl; }
virtual void move() { cout << "Moving" << endl; }
int age;
};
class Dog : public Animal {
public:
virtual void speak() override { cout << "Woof!" << endl; }
virtual void fetch() { cout << "Fetching" << endl; } // 新虚函数
int breed;
};
内存布局图解
Dog对象内存布局 (x64):
+------------------+
| vptr (8 bytes) | --> Dog虚表
+------------------+
| age (4 bytes) |
+------------------+
| breed (4 bytes) |
+------------------+
Dog虚表:
+------------------+
| &Dog::speak | --> 重写的函数
+------------------+
| &Animal::move | --> 继承的函数
+------------------+
| &Dog::fetch | --> 新增的虚函数
+------------------+
虚表在逆向中的识别
; 虚函数调用的汇编特征 (x64)
mov rax, [rcx] ; rcx = this指针, [rcx] = 虚表指针
call qword ptr [rax+8] ; 调用虚表第二个函数 (move)
; 构造函数中初始化虚表
lea rax, [Dog_vtable]
mov [rcx], rax ; 设置虚表指针
[!tip] 逆向技巧
- 在IDA中查找
lea rax, offset xxx后紧跟mov [rcx], rax的模式- 虚表通常位于
.rdata段- 虚表第一个条目前可能有RTTI信息(如果启用)
RTTI 结构 (Run-Time Type Information)
// 编译器生成的RTTI结构
struct type_info {
void* vfptr; // type_info的虚表
char* _M_d_name; // mangled name
// ...
};
// 可通过 typeid() 访问
const type_info& ti = typeid(Dog);
cout << ti.name() << endl; // 输出: class Dog
3.1.3 STL容器逆向
📝 学习笔记
std::vector 内部结构
// MSVC实现 (简化)
template<class T>
class vector {
T* _Myfirst; // 指向第一个元素
T* _Mylast; // 指向最后一个元素之后
T* _Myend; // 指向分配内存的末尾
};
// 计算方式
size() = _Mylast - _Myfirst
capacity() = _Myend - _Myfirst
vector 在内存中的识别
; vector访问元素
mov rax, [rcx] ; _Myfirst
mov rdx, [rcx+8] ; _Mylast
mov r8, [rcx+16] ; _Myend
; 计算size
sub rdx, rax
sar rdx, 2 ; 除以元素大小(这里是4字节int)
; vector[i]访问
mov rax, [rcx] ; _Myfirst
mov eax, [rax+rdi*4] ; 元素访问, rdi=索引
std::string 内部结构 (MSVC)
// MSVC的SSO (Small String Optimization)
class string {
union {
char _Buf[16]; // 短字符串直接存储
char* _Ptr; // 长字符串堆指针
};
size_t _Mysize; // 字符串长度
size_t _Myres; // 容量
};
// 判断逻辑: 如果 _Myres < 16, 使用_Buf; 否则使用_Ptr
std::map 红黑树结构
// map节点结构
struct _Tree_node {
_Tree_node* _Left;
_Tree_node* _Parent;
_Tree_node* _Right;
char _Color; // 红或黑
bool _Isnil;
pair<Key, Value> _Myval;
};
// 遍历特征:中序遍历递归
[!important] STL 逆向要点
容器 关键特征 识别方法 vector 三指针结构 连续的3个相邻指针 string SSO判断 检查size与16的比较 map/set 红黑树节点 Left/Parent/Right指针模式 list 双向链表 prev/next指针对 unordered_map 哈希桶 桶数组 + 链表结构
3.2 x64汇编
3.2.1 x64寄存器
📝 学习笔记
通用寄存器扩展
64位 32位 16位 8位高 8位低
RAX EAX AX AH AL
RBX EBX BX BH BL
RCX ECX CX CH CL
RDX EDX DX DH DL
RSI ESI SI - SIL
RDI EDI DI - DIL
RBP EBP BP - BPL
RSP ESP SP - SPL
R8 R8D R8W - R8B
R9 R9D R9W - R9B
R10 R10D R10W - R10B
R11 R11D R11W - R11B
R12 R12D R12W - R12B
R13 R13D R13W - R13B
R14 R14D R14W - R14B
R15 R15D R15W - R15B
[!warning] 重要注意事项
- 写入32位寄存器(如EAX)会自动清零高32位
- 写入16位或8位寄存器不会清零高位
- R8-R15是x64新增寄存器
特殊用途寄存器
| 寄存器 | 用途 |
|---|---|
| RSP | 栈指针 |
| RBP | 帧指针(可选) |
| RIP | 指令指针 |
| RFLAGS | 标志寄存器 |
| FS/GS | 段寄存器(TEB/TLS访问) |
Windows x64 TEB 访问
; 获取TEB
mov rax, gs:[0x30] ; TEB指针
; 获取PEB
mov rax, gs:[0x60] ; PEB指针
; 获取当前线程ID
mov eax, gs:[0x48]
; 获取当前进程ID
mov eax, gs:[0x40]
3.2.2 x64调用约定
📝 学习笔记
Windows x64 调用约定 (Microsoft x64)
参数传递:
- 整数/指针: RCX, RDX, R8, R9 (前4个)
- 浮点数: XMM0, XMM1, XMM2, XMM3 (前4个)
- 超过4个参数: 栈传递
返回值:
- 整数/指针: RAX
- 浮点数: XMM0
- 大结构体: 调用者分配空间,RCX传递指针
栈对齐:
- 调用前RSP必须16字节对齐
- 调用者分配32字节"影子空间"(shadow space)
影子空间示例
; 调用 func(a, b, c, d, e)
sub rsp, 40h ; 32字节影子空间 + 8字节第5参数 + 8字节对齐
mov [rsp+20h], r9 ; 可选:保存寄存器参数
mov [rsp+28h], param5 ; 第5个参数
mov r9, d
mov r8, c
mov rdx, b
mov rcx, a
call func
add rsp, 40h
Linux x64 调用约定 (System V AMD64 ABI)
参数传递:
- 整数/指针: RDI, RSI, RDX, RCX, R8, R9 (前6个)
- 浮点数: XMM0-XMM7 (前8个)
- 超过的参数: 栈传递
返回值:
- 整数: RAX, RDX(大返回值)
- 浮点: XMM0, XMM1
注意: 没有影子空间要求
[!important] 对比表
特性 Windows x64 Linux x64 整数参数 RCX,RDX,R8,R9 RDI,RSI,RDX,RCX,R8,R9 影子空间 必需(32字节) 不需要 红区 无 128字节 调用者保存 RAX,RCX,RDX,R8-R11 RAX,RCX,RDX,RSI,RDI,R8-R11 被调用者保存 RBX,RBP,RDI,RSI,R12-R15 RBX,RBP,R12-R15
3.2.3 x64特有指令
📝 学习笔记
RIP 相对寻址
; x86 全局变量访问
mov eax, [global_var] ; 绝对地址
; x64 RIP相对寻址
mov eax, [rip+offset] ; 相对于下一条指令的偏移
lea rax, [rip+offset] ; 获取地址
; IDA显示形式
mov eax, cs:global_var ; IDA会计算出实际地址
新增 / 增强指令
; MOVSXD - 带符号扩展从32位到64位
movsxd rax, dword ptr [rcx]
; LEA增强 - 可用于复杂计算
lea rax, [rcx+rdx*8+10h] ; rax = rcx + rdx*8 + 16
; 条件移动(x86已有但x64更常用)
cmovz rax, rbx ; if ZF==1, rax=rbx
cmovnz rax, rbx ; if ZF==0, rax=rbx
; SYSCALL (替代INT 2E/SYSENTER)
mov r10, rcx ; 系统调用号
syscall
; CMPXCHG16B - 128位原子比较交换
lock cmpxchg16b [rdi]
SSE/AVX 指令
; 128位操作
movaps xmm0, [rax] ; 对齐的128位加载
movups xmm0, [rax] ; 非对齐的128位加载
; 浮点运算
addsd xmm0, xmm1 ; 双精度加法
mulss xmm0, xmm1 ; 单精度乘法
; 向量化字符串操作
pcmpeqb xmm0, xmm1 ; 16字节并行比较
pmovmskb eax, xmm0 ; 获取比较结果掩码
3.3 保护研究入门
📝 学习笔记
常见软件保护技术
| 保护类型 | 描述 | 识别特征 |
|---|---|---|
| 反调试 | 检测调试器 | IsDebuggerPresent, NtQueryInformationProcess |
| 代码混淆 | 增加分析难度 | 控制流平坦化、虚假分支 |
| 加壳 | 运行时解压 | 入口点异常、节区熵值高 |
| 虚拟机保护 | 字节码解释执行 | dispatch循环、handler表 |
| 完整性校验 | 检测修改 | CRC/Hash计算 |
反调试技术示例
// 方法1: IsDebuggerPresent
if (IsDebuggerPresent()) {
// 检测到调试器
}
// 方法2: PEB.BeingDebugged
bool IsDebugged() {
PPEB peb = (PPEB)__readgsqword(0x60);
return peb->BeingDebugged;
}
// 方法3: NtGlobalFlag检查
bool CheckNtGlobalFlag() {
PPEB peb = (PPEB)__readgsqword(0x60);
DWORD flag = *(DWORD*)((BYTE*)peb + 0xBC); // x64 offset
return (flag & 0x70) != 0; // FLG_HEAP_*标志
}
// 方法4: 时间检测
DWORD start = GetTickCount();
// 敏感代码
DWORD end = GetTickCount();
if (end - start > 1000) {
// 可能被调试
}
绕过反调试
// 方法1: 直接patch IsDebuggerPresent返回值
// 在函数入口处: xor eax, eax; ret
// 方法2: 修改PEB.BeingDebugged
PPEB peb = (PPEB)__readgsqword(0x60);
peb->BeingDebugged = FALSE;
// 方法3: 使用x64dbg的ScyllaHide插件
// 或手动Hook相关API
加壳识别与脱壳
常见壳识别:
- UPX: 节名 UPX0, UPX1
- VMProtect: .vmp段, 高熵值代码
- Themida: 复杂的虚拟机结构
脱壳基本流程:
1. 运行到OEP (Original Entry Point)
2. dump内存
3. 修复IAT (Import Address Table)
4. 重建PE头
[!tip] 工具推荐
- DIE (Detect It Easy): 壳识别
- x64dbg + Scylla: 脱壳dump
- UPX:
upx -d直接脱壳- Import Reconstructor: IAT修复
✅ 阶段检查点
C++编程检查
- [ ] 理解类的内存布局和对齐规则
- [ ] 掌握虚表结构和虚函数调用机制
- [ ] 能在IDA中识别虚表和构造函数
- [ ] 理解多重继承和虚继承的内存布局
- [ ] 能识别STL容器的内存结构(vector, string, map)
x64汇编检查
- [ ] 熟悉R8-R15新寄存器
- [ ] 掌握Windows x64调用约定
- [ ] 理解影子空间的作用
- [ ] 能阅读RIP相对寻址代码
- [ ] 了解Linux与Windows调用约定的区别
保护研究检查
- [ ] 了解常见反调试技术
- [ ] 能识别和绕过基本反调试
- [ ] 了解加壳原理和脱壳流程
- [ ] 使用DIE等工具识别保护类型
📖 学习资料
| 资源类型 | 名称 | 说明 |
|---|---|---|
| 📕 书籍 | C++ Primer (第5版) | C++经典教材 |
| 📕 书籍 | Inside the C++ Object Model | 深入理解C++对象模型 |
| 📕 书籍 | 逆向工程核心原理 | 韩国作者,涵盖保护技术 |
| 🌐 网站 | godbolt.org | 在线查看编译器输出 |
| 🌐 网站 | cppinsights.io | 查看编译器对C++的处理 |
| 📹 视频 | CppCon会议视频 | C++深入讲解 |
🔗 导航
← [[阶段二:初级逆向]] | [[逆向与驱动开发学习路径(详细版)| 返回主目录]] | [[阶段四:Windows 开发]] →