逆向工程与驱动开发综合学习路径
📅 创建日期:2025-12-03
🎯 目标:从编程基础到高级逆向工程师 + 驱动开发专家
⏱️ 学习周期:18-30 个月
📝 文档版本:v2.0(详细版)
📑 目录
阶段一:编程基础
⏱️ 总时长:2-3 个月
🎯 目标:打下坚实的编程和汇编基础
1.1 C语言 ⭐⭐⭐⭐⭐
🎯 目标:掌握 C 语言核心概念,为后续所有学习打基础
⏱️ 时长:4-6 周
1.1.1 基础语法
学习小点
[ ] 变量与数据类型(int, char, float, double, long long)
[ ] 有符号与无符号类型的区别及范围
[ ] 类型转换(隐式转换、显式转换)
[ ] sizeof 运算符与数据类型大小
[ ] 运算符及优先级
[ ] printf/scanf 格式化输入输出
[ ] 条件语句(if-else, switch-case)
[ ] 循环语句(for, while, do-while)
[ ] break、continue 与 goto
代码示例
// 数据类型大小
printf("int: %zu bytes\n", sizeof(int)); // 4
printf("char: %zu bytes\n", sizeof(char)); // 1
printf("long long: %zu bytes\n", sizeof(long long)); // 8
printf("pointer: %zu bytes\n", sizeof(void*)); // 4(x86) / 8(x64)
// 有符号与无符号
unsigned int u = 0xFFFFFFFF; // 4294967295
int s = 0xFFFFFFFF; // -1 (补码表示)
// 类型转换陷阱
int a = 5, b = 2;
float result1 = a / b; // 2.0 (整数除法后转float)
float result2 = (float)a / b; // 2.5 (正确)
📚 学习资料
1.1.2 指针与内存管理(最重要!)
学习小点
[ ] 指针的概念(内存地址、解引用、取地址运算符)
[ ] 指针变量的声明与初始化
[ ] 指针运算(加减、比较、偏移计算)
[ ] 数组与指针的关系
[ ] 指针数组 vs 数组指针
[ ]
int *arr[10]- 指针数组[ ]
int (*arr)[10]- 数组指针
[ ] 多级指针(二级指针、三级指针)
[ ] 函数指针
[ ] 定义与声明
[ ] 作为参数传递(回调函数)
[ ] 函数指针数组
[ ] void 指针与类型转换
[ ] const 与指针的四种组合
[ ]
const int *p- 指向常量的指针[ ]
int * const p- 常量指针[ ]
const int * const p- 指向常量的常量指针
[ ] 动态内存分配
[ ] malloc/calloc/realloc/free
[ ] 内存泄漏检测
[ ] 野指针与悬空指针避免
代码示例
// 基本指针操作
int value = 100;
int *p = &value; // p存储value的地址
printf("地址: %p\n", p);
printf("值: %d\n", *p); // 解引用获取值
*p = 200; // 通过指针修改值
// 指针运算(逆向分析中非常常见)
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // 数组名就是首元素地址
printf("%d\n", *ptr); // 10
printf("%d\n", *(ptr + 2)); // 30,偏移2个int(8字节)
printf("%d\n", ptr[2]); // 30,等价写法
// 多级指针(用于修改指针本身)
int x = 10;
int *p1 = &x;
int **pp = &p1; // 指针的指针
printf("%d\n", **pp); // 10
// 函数指针(逆向分析核心!虚表就是函数指针数组)
typedef int (*Operation)(int, int); // 定义类型别名
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
Operation op = add; // 函数指针赋值
printf("%d\n", op(3, 5)); // 8
// 函数指针数组(模拟虚表)
Operation operations[4] = {add, sub, mul, div_func};
for (int i = 0; i < 4; i++) {
printf("%d\n", operations[i](10, 5));
}
// 回调函数
void process(int arr[], int size, int (*callback)(int)) {
for (int i = 0; i < size; i++)
arr[i] = callback(arr[i]);
}
// 动态内存管理
int size = 100;
int *buffer = (int*)malloc(size * sizeof(int));
if (buffer == NULL) {
printf("内存分配失败\n");
return;
}
memset(buffer, 0, size * sizeof(int)); // 初始化为0
// 使用buffer...
free(buffer);
buffer = NULL; // 避免悬空指针!
📚 学习资料
1.1.3 结构体与联合体
学习小点
[ ] 结构体定义与初始化
[ ] 结构体成员访问(. 和 -> 运算符)
[ ] 结构体内存对齐原理(非常重要!)
[ ] 对齐规则
[ ] 计算结构体大小
[ ]
#pragma pack的使用
[ ]
offsetof宏计算成员偏移[ ] 嵌套结构体
[ ] 自引用结构体(链表节点)
[ ] 位域(bit field)定义与使用
[ ] 联合体(union)
[ ] 与结构体的区别
[ ] 内存共享特性
[ ] 类型双关(type punning)应用
[ ] typedef 的使用(Windows 风格)
代码示例
// Windows风格结构体定义
typedef struct _MY_STRUCT {
int field1; // +0x00, 4字节
char field2; // +0x04, 1字节
// 3字节padding // +0x05-0x07
void *field3; // +0x08, 8字节(x64)
} MY_STRUCT, *PMY_STRUCT;
// 计算偏移(逆向分析必备)
#include <stddef.h>
printf("field1 offset: %zu\n", offsetof(MY_STRUCT, field1)); // 0
printf("field2 offset: %zu\n", offsetof(MY_STRUCT, field2)); // 4
printf("field3 offset: %zu\n", offsetof(MY_STRUCT, field3)); // 8
printf("struct size: %zu\n", sizeof(MY_STRUCT)); // 16
// 修改对齐方式
#pragma pack(push, 1) // 1字节对齐
typedef struct _PACKED_STRUCT {
int field1; // +0x00
char field2; // +0x04
void *field3; // +0x05
} PACKED_STRUCT;
#pragma pack(pop)
printf("packed size: %zu\n", sizeof(PACKED_STRUCT)); // 13
// 联合体(类型转换技巧)
typedef union _VALUE {
int i;
float f;
char bytes[4];
struct {
unsigned short low;
unsigned short high;
};
} VALUE;
VALUE v;
v.i = 0x12345678;
printf("整数: 0x%X\n", v.i);
printf("低16位: 0x%X\n", v.low); // 0x5678
printf("高16位: 0x%X\n", v.high); // 0x1234
printf("字节: %02X %02X %02X %02X\n",
v.bytes[0], v.bytes[1], v.bytes[2], v.bytes[3]);
// 位域(用于标志位)
typedef struct _FLAGS {
unsigned int readable : 1; // 1位
unsigned int writable : 1; // 1位
unsigned int executable : 1; // 1位
unsigned int reserved : 29; // 29位
} FLAGS;
FLAGS f = {0};
f.readable = 1;
f.executable = 1;
printf("flags value: %u\n", *(unsigned int*)&f); // 5 (101b)
📚 学习资料
1.1.4 位运算
学习小点
[ ] 位运算符
[ ] AND(&)、OR(|)、XOR(^)、NOT(~)
[ ] 左移(<<)、右移(>>)
[ ] 设置位、清除位、翻转位、检查位
[ ] 位掩码的创建与使用
[ ] 常见位运算技巧
[ ] 大小端字节序理解与检测
代码示例
unsigned int flags = 0;
// 设置第n位
#define SET_BIT(x, n) ((x) |= (1 << (n)))
// 清除第n位
#define CLEAR_BIT(x, n) ((x) &= ~(1 << (n)))
// 翻转第n位
#define TOGGLE_BIT(x, n) ((x) ^= (1 << (n)))
// 检查第n位
#define CHECK_BIT(x, n) (((x) >> (n)) & 1)
SET_BIT(flags, 3); // 设置第3位
printf("after set: 0x%X\n", flags); // 0x8
CLEAR_BIT(flags, 3); // 清除第3位
printf("after clear: 0x%X\n", flags); // 0x0
// 常用技巧
int x = 12; // 1100b
x & (x - 1); // 清除最低位的1 -> 8 (1000b)
x & (-x); // 获取最低位的1 -> 4 (0100b)
x ^ x; // 清零
~0; // 全1
// 字节序检测
int test = 1;
if (*(char*)&test == 1)
printf("Little Endian (x86/x64)\n");
else
printf("Big Endian\n");
// 字节交换
unsigned int value = 0x12345678;
unsigned int swapped = ((value >> 24) & 0xFF) |
((value >> 8) & 0xFF00) |
((value << 8) & 0xFF0000) |
((value << 24) & 0xFF000000);
printf("swapped: 0x%X\n", swapped); // 0x78563412
📚 学习资料
1.1.5 文件操作
学习小点
[ ] 文件打开模式(r, w, a, rb, wb, ab, r+, w+)
[ ] fopen/fclose 文件打开关闭
[ ] fread/fwrite 二进制读写
[ ] fgets/fputs/fprintf 文本读写
[ ] fseek/ftell/rewind 文件定位
[ ] feof/ferror 错误检测
[ ] 二进制文件分析基础
代码示例
// 读取二进制文件(PE分析基础)
FILE* fp = fopen("test.exe", "rb");
if (!fp) {
perror("打开文件失败");
return;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long fileSize = ftell(fp);
fseek(fp, 0, SEEK_SET); // 或 rewind(fp);
// 分配内存并读取
unsigned char* buffer = (unsigned char*)malloc(fileSize);
size_t bytesRead = fread(buffer, 1, fileSize, fp);
fclose(fp);
// 检查PE签名
if (buffer[0] == 'M' && buffer[1] == 'Z') {
printf("有效的PE文件\n");
// 获取PE头偏移
int peOffset = *(int*)(buffer + 0x3C);
printf("PE头偏移: 0x%X\n", peOffset);
// 检查PE签名
if (*(unsigned int*)(buffer + peOffset) == 0x00004550) {
printf("PE签名有效\n");
}
}
free(buffer);
📚 学习资料
✅ C语言阶段检查清单
[ ] 能熟练使用基本语法编写程序
[ ] 完全理解指针的概念和操作
[ ] 能正确计算结构体大小和成员偏移
[ ] 熟练使用位运算
[ ] 能正确管理动态内存
[ ] 能使用函数指针实现回调
[ ] 能读写二进制文件
1.2 数据结构 ⭐⭐⭐⭐
🎯 目标:掌握常用数据结构,能在内存中识别它们
⏱️ 时长:2-3 周
1.2.1 链表
学习小点
[ ] 单链表
[ ] 创建节点
[ ] 头插法 / 尾插法
[ ] 删除节点
[ ] 遍历链表
[ ] 反转链表
[ ] 双链表
[ ] 循环链表
[ ] Windows 内核风格链表(LIST_ENTRY)
[ ] 链表在内存中的特征识别
代码示例
// 单链表节点
typedef struct _NODE {
int data;
struct _NODE* next;
} NODE, *PNODE;
// 创建节点
NODE* CreateNode(int data) {
NODE* node = (NODE*)malloc(sizeof(NODE));
node->data = data;
node->next = NULL;
return node;
}
// 尾插法
void AppendNode(NODE** head, int data) {
NODE* newNode = CreateNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
NODE* current = *head;
while (current->next != NULL)
current = current->next;
current->next = newNode;
}
// 遍历
void PrintList(NODE* head) {
while (head != NULL) {
printf("%d -> ", head->data);
head = head->next;
}
printf("NULL\n");
}
// ========== Windows内核风格链表(驱动开发必备)==========
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink; // 前向指针
struct _LIST_ENTRY *Blink; // 后向指针
} LIST_ENTRY, *PLIST_ENTRY;
// 包含LIST_ENTRY的数据结构
typedef struct _MY_DATA {
int id;
char name[32];
LIST_ENTRY ListEntry; // 嵌入链表节点
} MY_DATA, *PMY_DATA;
// 通过LIST_ENTRY获取包含它的结构(重要!)
#define CONTAINING_RECORD(address, type, field) \
((type *)((char*)(address) - (unsigned long long)(&((type *)0)->field)))
// 使用示例
void ProcessList(PLIST_ENTRY listHead) {
PLIST_ENTRY entry = listHead->Flink;
while (entry != listHead) {
PMY_DATA data = CONTAINING_RECORD(entry, MY_DATA, ListEntry);
printf("ID: %d, Name: %s\n", data->id, data->name);
entry = entry->Flink;
}
}
📚 学习资料
1.2.2 栈和队列
学习小点
[ ] 栈的概念(LIFO)
[ ] 栈的实现(数组、链表)
[ ] 栈的应用
[ ] 函数调用栈(重要!)
[ ] 表达式求值
[ ] 括号匹配
[ ] 队列的概念(FIFO)
[ ] 队列的实现
[ ] 环形队列
代码示例
// 栈实现
#define STACK_SIZE 100
typedef struct {
int data[STACK_SIZE];
int top;
} Stack;
void StackInit(Stack* s) { s->top = -1; }
int StackEmpty(Stack* s) { return s->top == -1; }
int StackFull(Stack* s) { return s->top == STACK_SIZE - 1; }
void Push(Stack* s, int value) {
if (!StackFull(s))
s->data[++s->top] = value;
}
int Pop(Stack* s) {
if (!StackEmpty(s))
return s->data[s->top--];
return -1;
}
/*
函数调用栈示意(逆向分析核心概念)
调用 func(1, 2) 时的栈布局:
高地址
+-------------------+
| 参数2 (2) | [EBP+0x0C]
+-------------------+
| 参数1 (1) | [EBP+0x08]
+-------------------+
| 返回地址 | [EBP+0x04] <- CALL指令压入
+-------------------+
| 旧EBP | [EBP+0x00] <- 函数序言保存
+-------------------+ <- EBP 指向这里
| 局部变量1 | [EBP-0x04]
+-------------------+
| 局部变量2 | [EBP-0x08]
+-------------------+
低地址 <- ESP 指向这里
*/
📚 学习资料
1.2.3 树
学习小点
[ ] 二叉树基础概念
[ ] 二叉树的遍历(前序、中序、后序、层序)
[ ] 二叉搜索树(BST)
[ ] 平衡二叉树(AVL)了解
[ ] 红黑树(了解原理,Windows 内核使用)
[ ] 树在内存中的识别
代码示例
typedef struct _TREE_NODE {
int data;
struct _TREE_NODE* left;
struct _TREE_NODE* right;
} TREE_NODE, *PTREE_NODE;
// 创建节点
TREE_NODE* CreateTreeNode(int data) {
TREE_NODE* node = (TREE_NODE*)malloc(sizeof(TREE_NODE));
node->data = data;
node->left = node->right = NULL;
return node;
}
// 中序遍历(BST排序输出)
void InorderTraversal(TREE_NODE* root) {
if (root == NULL) return;
InorderTraversal(root->left);
printf("%d ", root->data);
InorderTraversal(root->right);
}
// BST插入
TREE_NODE* BSTInsert(TREE_NODE* root, int data) {
if (root == NULL) return CreateTreeNode(data);
if (data < root->data)
root->left = BSTInsert(root->left, data);
else
root->right = BSTInsert(root->right, data);
return root;
}
📚 学习资料
1.2.4 哈希表
学习小点
[ ] 哈希函数设计原则
[ ] 冲突处理方法
[ ] 链地址法(拉链法)
[ ] 开放寻址法
[ ] 哈希表在逆向中的识别特征
[ ] 常见哈希算法(DJB2, FNV, CRC32)
代码示例
#define TABLE_SIZE 256
typedef struct _HASH_NODE {
char* key;
void* value;
struct _HASH_NODE* next;
} HASH_NODE;
typedef struct _HASH_TABLE {
HASH_NODE* buckets[TABLE_SIZE];
int count;
} HASH_TABLE;
// DJB2哈希算法
unsigned int djb2_hash(const char* str) {
unsigned int hash = 5381;
int c;
while ((c = *str++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash % TABLE_SIZE;
}
// 插入
void HashInsert(HASH_TABLE* table, const char* key, void* value) {
unsigned int index = djb2_hash(key);
HASH_NODE* node = (HASH_NODE*)malloc(sizeof(HASH_NODE));
node->key = strdup(key);
node->value = value;
node->next = table->buckets[index];
table->buckets[index] = node;
table->count++;
}
// 查找
void* HashFind(HASH_TABLE* table, const char* key) {
unsigned int index = djb2_hash(key);
HASH_NODE* node = table->buckets[index];
while (node) {
if (strcmp(node->key, key) == 0)
return node->value;
node = node->next;
}
return NULL;
}
📚 学习资料
✅ 数据结构阶段检查清单
[ ] 能实现单链表的增删改查
[ ] 理解 Windows 内核链表 LIST_ENTRY
[ ] 理解函数调用栈的结构
[ ] 能实现基本的树遍历
[ ] 理解哈希表原理
1.3 x86汇编 ⭐⭐⭐⭐
🎯 目标:掌握 32 位汇编基础,能读懂简单反汇编
⏱️ 时长:3-4 周
1.3.1 寄存器
学习小点
[ ] 通用寄存器
[ ] EAX(累加器,函数返回值)
[ ] EBX(基址寄存器)
[ ] ECX(计数器,this 指针)
[ ] EDX(数据寄存器,除法高位)
[ ] 索引寄存器(ESI, EDI)
[ ] 栈寄存器
[ ] ESP(栈顶指针)
[ ] EBP(栈帧基址)
[ ] 指令指针(EIP)
[ ] 标志寄存器(EFLAGS)
[ ] ZF(零标志)
[ ] SF(符号标志)
[ ] CF(进位标志)
[ ] OF(溢出标志)
[ ] 段寄存器(CS, DS, SS, ES, FS, GS)
[ ] 寄存器分割(AL, AH, AX, EAX)
代码示例
; 32位通用寄存器及用途
; EAX - 累加器,函数返回值存这里
; EBX - 基址寄存器,通用
; ECX - 计数器,循环计数,C++中存this指针
; EDX - 数据寄存器,除法存高位,I/O端口操作
; 寄存器分割
; EAX (32位) = AX (低16位)
; AX = AH (高8位) + AL (低8位)
MOV EAX, 0x12345678
; 此时:
; EAX = 0x12345678
; AX = 0x5678
; AH = 0x56
; AL = 0x78
; 标志寄存器常用位
; ZF (Zero Flag) - 运算结果为0时置1
; SF (Sign Flag) - 运算结果为负时置1
; CF (Carry Flag) - 无符号运算进位/借位时置1
; OF (Overflow Flag) - 有符号运算溢出时置1
; 示例
XOR EAX, EAX ; EAX=0, ZF=1
SUB EAX, 1 ; EAX=0xFFFFFFFF, SF=1, CF=1
📚 学习资料
1.3.2 基本指令
学习小点
[ ] 数据传送指令
[ ] MOV(数据传送)
[ ] LEA(加载有效地址)
[ ] XCHG(交换)
[ ] PUSH/POP(栈操作)
[ ] MOVZX/MOVSX(零扩展 / 符号扩展)
[ ] 算术运算指令
[ ] ADD/SUB(加减)
[ ] INC/DEC(自增自减)
[ ] MUL/IMUL(无符号 / 有符号乘法)
[ ] DIV/IDIV(无符号 / 有符号除法)
[ ] NEG(取负)
[ ] 逻辑运算指令
[ ] AND/OR/XOR/NOT
[ ] TEST(测试位)
[ ] 移位指令
[ ] SHL/SHR(逻辑移位)
[ ] SAL/SAR(算术移位)
[ ] ROL/ROR(循环移位)
[ ] 比较与跳转
[ ] CMP(比较)
[ ] TEST(按位测试)
[ ] JMP(无条件跳转)
[ ] JE/JNE, JG/JL, JA/JB 等(条件跳转)
[ ] 函数调用
[ ] CALL(调用)
[ ] RET(返回)
[ ] LEAVE(清理栈帧)
代码示例
; ========== 数据传送 ==========
MOV EAX, 100 ; EAX = 100
MOV EBX, EAX ; EBX = EAX
MOV ECX, [0x401000] ; ECX = 内存[0x401000]的值
MOV [EBX], EAX ; 内存[EBX] = EAX
LEA EAX, [EBX+ECX*4+8] ; EAX = EBX + ECX*4 + 8 (只计算地址,不访问内存)
; LEA常用于计算地址或简单算术
XCHG EAX, EBX ; 交换EAX和EBX
; 栈操作
PUSH EAX ; ESP -= 4; [ESP] = EAX
POP EBX ; EBX = [ESP]; ESP += 4
PUSHAD ; 压入所有通用寄存器
POPAD ; 弹出所有通用寄存器
; 扩展
MOVZX EAX, BYTE PTR [ESI] ; 零扩展:AL = [ESI], 高位清0
MOVSX EAX, BYTE PTR [ESI] ; 符号扩展:保持符号
; ========== 算术运算 ==========
ADD EAX, 10 ; EAX += 10
SUB EAX, 5 ; EAX -= 5
INC ECX ; ECX++
DEC ECX ; ECX--
; 乘法
MUL EBX ; EDX:EAX = EAX * EBX (无符号)
IMUL EBX ; EDX:EAX = EAX * EBX (有符号)
IMUL EAX, EBX, 5 ; EAX = EBX * 5 (三操作数形式)
; 除法
; 被除数在EDX:EAX中,除数在操作数中
XOR EDX, EDX ; 清零EDX (无符号除法前必须)
DIV ECX ; EAX = EDX:EAX / ECX, EDX = 余数
; ========== 逻辑运算 ==========
AND EAX, 0xFF ; 保留低8位,清除高位
OR EAX, 0x80000000 ; 设置最高位
XOR EAX, EAX ; 清零(比MOV EAX, 0更快)
NOT EAX ; 按位取反
TEST EAX, EAX ; 检查EAX是否为0,设置ZF,不修改EAX
; TEST常用于检查位或判断是否为0
; ========== 移位 ==========
SHL EAX, 2 ; EAX <<= 2 (相当于 *4)
SHR EAX, 1 ; EAX >>= 1 (无符号,相当于 /2)
SAR EAX, 1 ; EAX >>= 1 (有符号,保持符号位)
; ========== 比较与跳转 ==========
CMP EAX, 10 ; 比较EAX和10,设置标志位
JE equal_label ; 相等跳转 (ZF=1)
JNE not_equal_label ; 不等跳转 (ZF=0)
JG greater_label ; 大于跳转 (有符号)
JL less_label ; 小于跳转 (有符号)
JA above_label ; 大于跳转 (无符号)
JB below_label ; 小于跳转 (无符号)
JGE greater_equal ; 大于等于 (有符号)
JLE less_equal ; 小于等于 (有符号)
; 循环
MOV ECX, 10
loop_start:
; 循环体
LOOP loop_start ; ECX--; if ECX != 0 goto loop_start
; ========== 函数调用 ==========
CALL my_function ; 等价于: PUSH EIP+5; JMP my_function
; ...
my_function:
PUSH EBP ; 保存旧栈帧
MOV EBP, ESP ; 建立新栈帧
SUB ESP, 0x20 ; 分配局部变量空间
; 函数体...
; 参数访问: [EBP+8], [EBP+C], ...
; 局部变量: [EBP-4], [EBP-8], ...
MOV ESP, EBP ; 恢复ESP
POP EBP ; 恢复EBP
RET ; 等价于: POP EIP
; 或使用LEAVE
LEAVE ; 等价于: MOV ESP, EBP; POP EBP
RET
📚 学习资料
1.3.3 调用约定
学习小点
[ ] __cdecl(C 语言默认)
[ ] 参数从右到左入栈
[ ] 调用者清理栈
[ ] __stdcall(Windows API 标准)
[ ] 参数从右到左入栈
[ ] 被调用者清理栈
[ ] __fastcall
[ ] 前两个参数用 ECX, EDX
[ ] 其余参数入栈
[ ] __thiscall(C++ 成员函数)
[ ] this 指针在 ECX 中
[ ] 栈帧结构理解
[ ] 函数序言和尾声识别
代码示例
; ========== __cdecl (C语言默认) ==========
; 特点:调用者清理栈
; 调用 int func(int a, int b, int c)
PUSH 3 ; 参数c
PUSH 2 ; 参数b
PUSH 1 ; 参数a
CALL func
ADD ESP, 12 ; 调用者清理3个参数 (3*4=12字节)
; 返回值在EAX
; ========== __stdcall (Windows API) ==========
; 特点:被调用者清理栈
; 调用 int __stdcall func(int a, int b, int c)
PUSH 3
PUSH 2
PUSH 1
CALL func ; 函数内部 RET 12
; 函数定义示例
func_stdcall:
PUSH EBP
MOV EBP, ESP
; 函数体
MOV EAX, [EBP+8] ; 参数a
ADD EAX, [EBP+C] ; + 参数b
ADD EAX, [EBP+10] ; + 参数c
POP EBP
RET 12 ; 返回并清理12字节参数
; ========== __fastcall ==========
; 特点:前两个参数用寄存器
; 调用 int __fastcall func(int a, int b, int c, int d)
PUSH d ; 第4个参数入栈
PUSH c ; 第3个参数入栈
MOV EDX, b ; 第2个参数
MOV ECX, a ; 第1个参数
CALL func
; ========== __thiscall (C++成员函数) ==========
; 特点:this指针在ECX
; 调用 obj->method(int a, int b)
PUSH b
PUSH a
MOV ECX, obj ; this指针
CALL method
; ========== 标准栈帧结构 ==========
; 调用 func(1, 2) 后的栈:
;
; +------------------+ 高地址
; | 参数2 (2) | [EBP+0x0C]
; +------------------+
; | 参数1 (1) | [EBP+0x08]
; +------------------+
; | 返回地址 | [EBP+0x04]
; +------------------+
; | 旧EBP | [EBP] <- 当前EBP指向
; +------------------+
; | 局部变量1 | [EBP-0x04]
; +------------------+
; | 局部变量2 | [EBP-0x08]
; +------------------+ 低地址 <- ESP指向
; 典型函数序言
PUSH EBP ; 保存调用者的栈帧
MOV EBP, ESP ; 建立新栈帧
SUB ESP, N ; 分配N字节局部变量
PUSH EBX ; 保存被调用者保存寄存器
PUSH ESI
PUSH EDI
; 典型函数尾声
POP EDI ; 恢复寄存器
POP ESI
POP EBX
MOV ESP, EBP ; 释放局部变量
POP EBP ; 恢复调用者栈帧
RET ; 返回
📚 学习资料
1.3.4 常见汇编模式识别
学习小点
[ ] 变量访问模式
[ ] 全局变量:直接地址
[] 局部变量:[EBP-X]
[] 函数参数:[EBP+X]
[ ] 数组访问模式
[ ] 结构体访问模式
[ ] if-else 模式
[ ] switch-case 模式(跳转表)
[ ] for/while 循环模式
[ ] 函数调用模式
代码示例
; ========== 变量访问模式 ==========
; 全局变量 - 直接地址访问
MOV EAX, [0x401000] ; 读取全局变量
MOV [0x401000], EAX ; 写入全局变量
; 局部变量 - 栈上负偏移
MOV EAX, [EBP-4] ; 第1个局部变量
MOV EBX, [EBP-8] ; 第2个局部变量
; 函数参数 - 栈上正偏移
MOV EAX, [EBP+8] ; 第1个参数
MOV EBX, [EBP+C] ; 第2个参数
; ========== 数组访问模式 ==========
; arr[i] 访问
; 基址 + 索引 * 元素大小
MOV EAX, [EBX+ECX*4] ; int arr[],EBX=基址,ECX=索引
MOV AL, [ESI+EDI] ; char arr[],ESI=基址,EDI=索引
; 固定索引
MOV EAX, [EBX+20] ; arr[5],假设int数组
; ========== 结构体访问模式 ==========
; obj->field 或 (*ptr).field
; 基址 + 固定偏移
MOV EAX, [ESI+0] ; 第1个成员
MOV EBX, [ESI+4] ; 第2个成员
MOV ECX, [ESI+10h] ; 偏移0x10的成员
; ========== if-else 模式 ==========
; C代码: if (a > 10) { ... } else { ... }
CMP EAX, 10
JLE else_branch ; 小于等于则跳到else
; if 分支代码
JMP end_if
else_branch:
; else 分支代码
end_if:
; ========== switch-case 模式 ==========
; 小范围连续case - 跳转表
CMP EAX, 5 ; 检查是否超出范围
JA default_case ; 超出则跳到default
JMP DWORD PTR [jump_table+EAX*4] ; 跳转表
jump_table:
DD case_0
DD case_1
DD case_2
DD case_3
DD case_4
DD case_5
; ========== for循环模式 ==========
; C代码: for (int i = 0; i < 10; i++) { ... }
XOR ECX, ECX ; i = 0
loop_start:
CMP ECX, 10 ; i < 10?
JGE loop_end ; 不满足则退出
; 循环体
INC ECX ; i++
JMP loop_start
loop_end:
; 或使用LOOP指令
MOV ECX, 10 ; 循环次数
loop_body:
; 循环体
LOOP loop_body ; ECX--; if ECX!=0 goto loop_body
; ========== while循环模式 ==========
; C代码: while (condition) { ... }
while_start:
; 检查条件
TEST EAX, EAX
JZ while_end ; 条件不满足则退出
; 循环体
JMP while_start
while_end:
📚 学习资料
✅ x86汇编阶段检查清单
[ ] 认识所有 x86 寄存器及用途
[ ] 能读懂基本汇编指令
[ ] 理解三种主要调用约定
[ ] 能识别函数序言和尾声
[ ] 能识别 if-else、循环等控制结构
[ ] 能识别数组和结构体访问模式
[ ] 能使用 IDA 或 x64dbg 查看反汇编
阶段二:初级逆向
⏱️ 总时长:1-2 个月
🎯 目标:掌握逆向工具使用,能分析简单程序
2.1 逆向工具的使用 ⭐⭐⭐⭐⭐
🎯 目标:熟练使用 IDA、x64dbg、Cheat Engine
⏱️ 时长:2-3 周
2.1.1 IDA Pro
学习小点
[ ] IDA 界面熟悉
[ ] 反汇编视图(Text View / Graph View)
[ ] 十六进制视图
[ ] 结构体视图
[ ] 字符串窗口
[ ] 导入 / 导出表
[ ] 交叉引用窗口
[ ] 基本操作
[ ] 打开文件与分析
[ ] 导航与跳转
[ ] 重命名函数和变量
[ ] 添加注释
[ ] 创建结构体
[ ] 修改数据类型
[ ] 反编译器使用(F5)
[ ] 查看伪代码
[ ] 修改变量类型
[ ] 同步反汇编与伪代码
[ ] 搜索功能
[ ] 字符串搜索
[ ] 二进制搜索
[ ] 立即数搜索
[ ] 脚本与插件
[ ] IDAPython 基础
[ ] 常用插件
快捷键速查表
IDA实战练习
练习1:分析简单CrackMe
1. 打开CrackMe程序
2. 找到main函数
3. 定位密码校验逻辑
4. 分析校验算法
5. 找出正确密码
练习2:分析API调用
1. 查看导入表
2. 找到MessageBoxA的调用
3. 通过交叉引用定位调用位置
4. 分析调用参数
📚 学习资料
2.1.2 x64dbg
学习小点
[ ] 界面熟悉
[ ] CPU 窗口(反汇编、寄存器、栈、内存)
[ ] 日志窗口
[ ] 断点窗口
[ ] 内存映射
[ ] 调用栈
[ ] 线程列表
[ ] 基本调试操作
[ ] 附加进程 / 打开程序
[ ] 运行、暂停、重启
[ ] 单步步入(F7)、单步步过(F8)
[ ] 运行到光标(F4)
[ ] 运行到返回(Ctrl+F9)
[ ] 断点使用
[ ] 软件断点(INT3)
[ ] 硬件断点(执行 / 读 / 写)
[ ] 内存断点
[ ] 条件断点
[ ] 内存操作
[ ] 查看内存
[ ] 修改内存
[ ] 搜索内存
[ ] 内存映射查看
[ ] 补丁与修改
[ ] 汇编修改指令
[ ] NOP 填充
[ ] 导出补丁
[ ] 脚本功能
[ ] 命令行使用
[ ] 脚本编写基础
快捷键速查表
x64dbg实战练习
练习1:动态分析程序流程
1. 在main函数入口下断点
2. 单步执行观察流程
3. 观察寄存器和栈变化
4. 找到关键判断点
练习2:修改程序行为
1. 找到跳转指令
2. 使用NOP或修改跳转
3. 观察程序行为变化
4. 保存补丁
练习3:分析加密算法
1. 在输入函数后下断点
2. 观察输入数据如何被处理
3. 使用硬件断点跟踪数据流
4. 还原加密逻辑
📚 学习资料
2.1.3 Cheat Engine
学习小点
[ ] 基本功能
[ ] 附加进程
[ ] 数值搜索(精确值、变动值)
[ ] 数据类型选择
[ ] 修改数值
[ ] 进阶搜索
[ ] 未知初始值搜索
[ ] 增加 / 减少搜索
[ ] 变动 / 未变搜索
[ ] 范围搜索
[ ] 指针扫描
[ ] 找到动态地址
[ ] 扫描指针路径
[ ] 构建指针链
[ ] 代码注入
[ ] 找到修改代码的位置
[ ] 使用 "找出是什么访问了这个地址"
[ ] 使用 "找出是什么写入了这个地址"
[ ] 代码注入器基础
[ ] 内置教程
[ ] 完成 CE Tutorial(必做!)
CE操作流程
基本数值搜索流程:
1. 打开CE,附加目标进程
2. 选择正确的数据类型(4字节/浮点等)
3. 输入当前数值,首次扫描
4. 在游戏中改变数值
5. 输入新数值,再次扫描
6. 重复直到找到唯一地址
7. 修改数值或锁定
指针扫描流程:
1. 找到动态地址
2. 右键 -> 指针扫描
3. 设置扫描参数(通常Max Level 5-7)
4. 重启游戏验证
5. 比较结果,找出稳定指针路径
找代码注入点:
1. 找到目标地址
2. 右键 -> "找出是什么写入了这个地址"
3. 在游戏中触发写入
4. 查看写入的代码位置
5. 分析代码逻辑
📚 学习资料
2.1.4 其他实用工具
学习小点
[ ] PE 分析工具
[ ] CFF Explorer
[ ] PE-bear
[] DIE (Detect It Easy)
[ ] 十六进制编辑器
[ ] HxD
[ ] 010 Editor
[ ] 资源查看器
[ ] Resource Hacker
[ ] 进程工具
[ ] Process Monitor
[ ] Process Explorer
[ ] API Monitor
[ ] 网络分析
[ ] Wireshark
[ ] Fiddler
工具用途说明
CFF Explorer / PE-bear:
- 查看PE文件结构
- 分析导入导出表
- 修改PE头信息
DIE (Detect It Easy):
- 检测加壳类型
- 识别编译器
- 检测保护方式
Process Monitor:
- 监控文件操作
- 监控注册表操作
- 监控网络操作
API Monitor:
- 监控API调用
- 查看参数和返回值
- 分析程序行为
📚 学习资料
✅ 逆向工具阶段检查清单
[ ] 能用 IDA 打开程序并分析函数
[ ] 会使用 IDA 的交叉引用功能
[ ] 能用 x64dbg 进行动态调试
[ ] 会设置各种类型的断点
[ ] 能用 CE 搜索和修改游戏数值
[ ] 会用 CE 进行指针扫描
[ ] 能识别程序是否加壳
2.2 C语言逆向 ⭐⭐⭐⭐
🎯 目标:能将反汇编代码还原为 C 代码
⏱️ 时长:2-3 周
2.2.1 变量识别
学习小点
[ ] 全局变量识别
[ ] 直接地址访问特征
[ ] .data/.bss 段分布
[ ] 局部变量识别
[] [EBP-X] 模式
[] [ESP+X] 模式
[ ] 函数参数识别
[] [EBP+X] 模式
[ ] 根据调用约定分析
[ ] 数组识别
[ ] 基址 + 索引 * 大小 模式
[ ] 连续内存访问
[ ] 结构体识别
[ ] 基址 + 固定偏移 模式
[ ] 多个字段访问
汇编特征与C代码对照
; ========== 全局变量 ==========
; 汇编特征:直接地址访问
MOV EAX, DWORD PTR DS:[0x00404000]
MOV DWORD PTR DS:[0x00404000], 100
; 对应C代码:
int g_value; // 地址 0x00404000
g_value = 100;
; ========== 局部变量 ==========
; 汇编特征:[EBP-X] 或 [ESP+X] 访问
MOV DWORD PTR SS:[EBP-4], 10 ; 第1个局部变量
MOV DWORD PTR SS:[EBP-8], 20 ; 第2个局部变量
; 对应C代码:
int local1 = 10;
int local2 = 20;
; ========== 函数参数 ==========
; 汇编特征:[EBP+X] 访问,X >= 8
MOV EAX, DWORD PTR SS:[EBP+8] ; 第1个参数
MOV EBX, DWORD PTR SS:[EBP+C] ; 第2个参数
; 对应C代码:
void func(int param1, int param2) {
// param1 = [EBP+8]
// param2 = [EBP+C]
}
; ========== 数组访问 ==========
; 汇编特征:基址 + 索引 * 元素大小
MOV EAX, DWORD PTR DS:[ECX*4+0x00404000] ; arr[i]
MOV EAX, DWORD PTR DS:[ESI+EDI*4] ; arr[i],ESI为基址
; 对应C代码:
int arr[100]; // 基址 0x00404000
x = arr[i]; // ECX = i
; ========== 结构体访问 ==========
; 汇编特征:基址 + 固定偏移
MOV EAX, DWORD PTR DS:[ESI] ; obj->field1 (offset 0)
MOV EBX, DWORD PTR DS:[ESI+4] ; obj->field2 (offset 4)
MOV ECX, DWORD PTR DS:[ESI+10] ; obj->field3 (offset 0x10)
; 对应C代码:
struct MyStruct {
int field1; // +0x00
int field2; // +0x04
// padding
int field3; // +0x10
};
MyStruct* obj;
x = obj->field1;
y = obj->field2;
z = obj->field3;
📚 学习资料
2.2.2 控制流识别
学习小点
[ ] if-else 识别
[ ] CMP + 条件跳转模式
[ ] 分支结构
[ ] switch-case 识别
[ ] 跳转表模式
[ ] if-else 链模式
[ ] for 循环识别
[ ] 初始化 - 条件 - 递增结构
[ ] while 循环识别
[ ] 条件在开头
[ ] do-while 循环识别
[ ] 条件在结尾
汇编特征与C代码对照
; ========== if-else ==========
; 汇编模式:
CMP DWORD PTR SS:[EBP-4], 10 ; if (x > 10)
JLE short loc_401020 ; 不满足则跳过if块
; if 块代码
MOV EAX, 1
JMP short loc_401030
loc_401020: ; else 块
MOV EAX, 0
loc_401030:
; 对应C代码:
if (x > 10) {
return 1;
} else {
return 0;
}
; ========== if-else if-else ==========
CMP EAX, 1
JNE loc_case2
; case 1
JMP loc_end
loc_case2:
CMP EAX, 2
JNE loc_default
; case 2
JMP loc_end
loc_default:
; default
loc_end:
; 对应C代码:
if (x == 1) {
// case 1
} else if (x == 2) {
// case 2
} else {
// default
}
; ========== switch-case (跳转表) ==========
CMP EAX, 4 ; 检查范围
JA loc_default ; > 4 则default
JMP DWORD PTR DS:[EAX*4+jump_table]
jump_table:
DD loc_case0
DD loc_case1
DD loc_case2
DD loc_case3
DD loc_case4
; 对应C代码:
switch (x) {
case 0: /* ... */ break;
case 1: /* ... */ break;
case 2: /* ... */ break;
case 3: /* ... */ break;
case 4: /* ... */ break;
default: /* ... */
}
; ========== for 循环 ==========
; 特征:初始化 -> 条件检查(在开头) -> 循环体 -> 递增 -> 跳回条件检查
XOR ECX, ECX ; i = 0
JMP short loc_cond_check
loc_loop_body:
; 循环体
; ...
INC ECX ; i++
loc_cond_check:
CMP ECX, 10 ; i < 10
JL short loc_loop_body
; 对应C代码:
for (int i = 0; i < 10; i++) {
// 循环体
}
; ========== while 循环 ==========
; 特征:条件检查在开头
loc_while_check:
CMP DWORD PTR SS:[EBP-4], 0
JE short loc_while_end ; 不满足则退出
; 循环体
DEC DWORD PTR SS:[EBP-4]
JMP short loc_while_check
loc_while_end:
; 对应C代码:
while (x != 0) {
// 循环体
x--;
}
; ========== do-while 循环 ==========
; 特征:条件检查在结尾
loc_do_body:
; 循环体
DEC DWORD PTR SS:[EBP-4]
CMP DWORD PTR SS:[EBP-4], 0
JNE short loc_do_body ; 满足则继续
; 对应C代码:
do {
// 循环体
x--;
} while (x != 0);
📚 学习资料
2.2.3 函数识别
学习小点
[ ] 函数边界识别
[ ] 序言特征
[ ] 尾声特征
[ ] 参数数量判断
[ ] 根据调用约定
[ ] 栈清理大小
[ ] 返回值识别
[ ] EAX 保存返回值
[ ] 结构体返回特殊处理
[ ] 调用约定识别
[ ] cdecl/stdcall/fastcall 区分
汇编特征
; ========== 函数序言 ==========
PUSH EBP ; 保存旧栈帧
MOV EBP, ESP ; 建立新栈帧
SUB ESP, X ; 分配局部变量空间
PUSH EBX ; 保存被调用者保存寄存器
PUSH ESI
PUSH EDI
; 有时会有:
AND ESP, 0xFFFFFFF0 ; 栈对齐
; ========== 函数尾声 ==========
POP EDI ; 恢复寄存器
POP ESI
POP EBX
MOV ESP, EBP ; 或 ADD ESP, X
POP EBP
RET ; __cdecl
; 或
RET X ; __stdcall,X = 参数大小
; ========== 调用约定识别 ==========
; __cdecl 特征:
PUSH arg3
PUSH arg2
PUSH arg1
CALL func
ADD ESP, 0xC ; 调用者清理12字节
; __stdcall 特征:
PUSH arg3
PUSH arg2
PUSH arg1
CALL func ; 调用者不清理
; func内部:
RET 0xC ; 被调用者清理
; __fastcall 特征:
PUSH arg4
PUSH arg3
MOV EDX, arg2 ; 第2个参数用EDX
MOV ECX, arg1 ; 第1个参数用ECX
CALL func
; ========== 参数数量判断 ==========
; 方法1:查看栈清理大小
ADD ESP, 0x10 ; 0x10 / 4 = 4个参数
; 方法2:查看RET指令
RET 0x14 ; 0x14 / 4 = 5个参数
; 方法3:查看函数内部参数访问
MOV EAX, [EBP+8] ; 参数1
MOV EBX, [EBP+C] ; 参数2
MOV ECX, [EBP+10] ; 参数3
; 最高偏移 (0x10 - 8) / 4 + 1 = 3个参数
📚 学习资料
2.2.4 常用算法识别
学习小点
[ ] 字符串操作
[ ] strlen
[ ] strcpy/memcpy
[ ] strcmp
[ ] 简单加密
[ ] XOR 加密
[ ] 凯撒密码
[ ] Base64
[ ] 哈希算法特征
[ ] CRC32
[ ] MD5
[ ] SHA 系列
汇编特征
; ========== strlen ==========
XOR ECX, ECX ; len = 0
loc_loop:
CMP BYTE PTR [EAX+ECX], 0 ; str[len] == 0?
JE short loc_end
INC ECX ; len++
JMP short loc_loop
loc_end:
; ECX = 字符串长度
; ========== strcpy/memcpy ==========
; REP MOVSB/MOVSW/MOVSD 特征
MOV ESI, src ; 源
MOV EDI, dst ; 目标
MOV ECX, len ; 长度
REP MOVSB ; 按字节复制
; ========== strcmp ==========
loc_cmp:
MOV AL, [ESI]
MOV BL, [EDI]
CMP AL, BL
JNE short loc_diff
TEST AL, AL ; 检查结束
JE short loc_equal
INC ESI
INC EDI
JMP short loc_cmp
; ========== XOR加密 ==========
MOV ECX, len
MOV ESI, data
loc_xor:
XOR BYTE PTR [ESI], key ; data[i] ^= key
INC ESI
LOOP loc_xor
; ========== Base64特征 ==========
; 查找特征字符串
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
; 查找特征操作
SHR x, 2 ; >> 2
AND x, 0x3F ; & 0x3F (63)
; ========== CRC32特征 ==========
; 查找多项式常数
0xEDB88320 ; 反射多项式
0x04C11DB7 ; 正向多项式
; 查找预计算表(256项)
📚 学习资料
✅ C语言逆向阶段检查清单
[ ] 能识别各种变量类型
[ ] 能识别 if-else 结构
[ ] 能识别各种循环结构
[ ] 能识别 switch-case
[ ] 能判断函数参数数量
[ ] 能识别调用约定
[ ] 能识别常见字符串操作
[ ] 能识别简单加密算法
阶段三:进阶编程
⏱️ 总时长:2-3 个月
🎯 目标:掌握 C++ 逆向特征和 64 位汇编
3.1 C++ ⭐⭐⭐⭐⭐
🎯 目标:掌握 C++ 面向对象,深入理解虚表机制(逆向核心)
⏱️ 时长:4-6 周
3.1.1 类与对象
学习小点
[ ] 类的定义与实例化
[ ] 访问修饰符(public, private, protected)
[ ] 构造函数与析构函数
[ ] 成员变量与成员函数
[ ] this 指针
[ ] 静态成员
[ ] 类的内存布局
代码示例
class Player {
public:
int m_health; // +0x00
int m_mana; // +0x04
float m_posX; // +0x08
float m_posY; // +0x0C
Player() : m_health(100), m_mana(50), m_posX(0), m_posY(0) {}
void TakeDamage(int damage) {
m_health -= damage;
if (m_health < 0) m_health = 0;
}
int GetHealth() const { return m_health; }
};
// 内存布局 (无虚函数):
// +0x00: m_health (4 bytes)
// +0x04: m_mana (4 bytes)
// +0x08: m_posX (4 bytes)
// +0x0C: m_posY (4 bytes)
// Total: 16 bytes
// this指针在汇编中 (x86 __thiscall)
// ECX = this pointer
C++成员函数调用的汇编特征
; C++调用: player->TakeDamage(10)
; x86 __thiscall:
PUSH 10 ; 参数
MOV ECX, player ; this指针放ECX
CALL Player::TakeDamage
; 在TakeDamage函数内部:
Player::TakeDamage:
PUSH EBP
MOV EBP, ESP
; ECX = this
MOV EAX, [ECX] ; this->m_health
SUB EAX, [EBP+8] ; -= damage
MOV [ECX], EAX ; 写回
; ...
📚 学习资料
3.1.2 虚函数与虚表(逆向核心!)
学习小点
[ ] 虚函数概念
[ ] 多态实现原理
[ ] 虚表(vtable)结构
[ ] 虚表指针(vfptr)位置
[ ] 单继承虚表布局
[ ] 多继承虚表布局
[ ] 虚析构函数
[ ] 纯虚函数与抽象类
虚表机制详解
class Animal {
public:
int m_age; // +0x08 (x64)
virtual void Speak() { printf("...\n"); } // vtable[0]
virtual void Move() { printf("move\n"); } // vtable[1]
};
class Dog : public Animal {
public:
int m_breed; // +0x0C
virtual void Speak() override { printf("Woof!\n"); } // 覆盖
virtual void Fetch() { printf("fetch\n"); } // 新增
};
// Animal对象内存布局 (x64):
// +0x00: vfptr -> Animal::vtable
// +0x08: m_age
// Size: 16 bytes (对齐)
// Dog对象内存布局 (x64):
// +0x00: vfptr -> Dog::vtable
// +0x08: m_age (继承自Animal)
// +0x0C: m_breed
// Size: 16 bytes
// Animal的虚表:
// vtable[0] = Animal::Speak
// vtable[1] = Animal::Move
// Dog的虚表:
// vtable[0] = Dog::Speak (覆盖了Animal::Speak)
// vtable[1] = Animal::Move (继承)
// vtable[2] = Dog::Fetch (新增)
虚函数调用的汇编特征
; C++代码: animal->Speak()
; 其中 animal 是 Animal* 类型,可能指向 Dog 对象
; x64汇编:
MOV RAX, [RCX] ; RAX = *this = vfptr (虚表指针)
MOV RAX, [RAX] ; RAX = vtable[0] = Speak函数地址
CALL RAX ; 调用虚函数
; x86汇编:
MOV EAX, [ECX] ; EAX = vfptr
MOV EAX, [EAX] ; EAX = vtable[0]
CALL EAX
; 调用第2个虚函数 (Move):
MOV RAX, [RCX] ; RAX = vfptr
MOV RAX, [RAX+8] ; RAX = vtable[1] (偏移8字节,x64指针大小)
CALL RAX
; 特征识别:
; 1. 首先读取对象的第一个成员 (vfptr)
; 2. 然后从该地址读取函数指针
; 3. 间接调用 (CALL EAX/RAX)
多继承虚表布局
class A {
public:
int a;
virtual void funcA() {}
};
class B {
public:
int b;
virtual void funcB() {}
};
class C : public A, public B {
public:
int c;
virtual void funcA() override {}
virtual void funcB() override {}
virtual void funcC() {}
};
// C对象内存布局 (x64):
// +0x00: vfptr_A -> C的A部分虚表
// +0x08: a
// +0x10: vfptr_B -> C的B部分虚表
// +0x18: b
// +0x1C: c
// C的A部分虚表:
// [0] = C::funcA
// [1] = C::funcC
// C的B部分虚表:
// [0] = thunk -> C::funcB (需要调整this指针)
📚 学习资料
3.1.3 STL容器
学习小点
[ ] vector 内存布局
[ ] string 内存布局(SSO 优化)
[ ] list 内存布局
[ ] map 内存布局
[ ] 逆向中识别 STL 容器
STL容器内存布局
// ========== std::vector ==========
// MSVC实现 (x64):
// +0x00: _Myfirst (指向首元素)
// +0x08: _Mylast (指向最后元素之后)
// +0x10: _Myend (指向分配内存末尾)
// Size: 24 bytes
std::vector<int> vec = {1, 2, 3, 4, 5};
// vec._Myfirst -> [1, 2, 3, 4, 5, ?, ?...]
// ^ ^ ^
// _Myfirst _Mylast _Myend
// 获取size: (_Mylast - _Myfirst) / sizeof(element)
// 获取capacity: (_Myend - _Myfirst) / sizeof(element)
// ========== std::string ==========
// MSVC实现 (x64) - SSO优化:
// 短字符串 (<=15字符):
// +0x00: 内联buffer[16] (直接存储字符串)
// +0x10: _Mysize (长度)
// +0x18: _Myres (容量,SSO时为15)
// 长字符串:
// +0x00: _Bx._Ptr (指向堆上的字符串)
// +0x08: (padding)
// +0x10: _Mysize
// +0x18: _Myres (>=16表示使用堆)
// 判断是否SSO: _Myres < 16
// ========== std::map ==========
// 红黑树实现:
// map对象:
// +0x00: _Myhead (指向头节点)
// +0x08: _Mysize (元素数量)
// 树节点:
// +0x00: _Left (左子节点)
// +0x08: _Parent (父节点)
// +0x10: _Right (右子节点)
// +0x18: _Color (红/黑)
// +0x19: _Isnil (是否为nil节点)
// +0x20: _Myval (键值对)
📚 学习资料
3.1.4 C++逆向特征总结
识别特征
1. 类对象识别:
- 对象首地址通常有vfptr(如果有虚函数)
- 成员访问使用 [this+offset] 模式
- 构造函数初始化vfptr
2. 虚函数调用特征:
- MOV REG, [this] ; 获取vfptr
- MOV REG, [REG+offset] ; 获取函数指针
- CALL REG ; 间接调用
3. 构造函数特征:
- 初始化vfptr
- 调用父类构造函数
- 初始化成员变量
4. 析构函数特征:
- 反向调用(先子类后父类)
- 将vfptr设为当前类的虚表
- 释放资源
5. this指针传递:
- x86: ECX (__thiscall)
- x64: RCX (第一个参数)
📚 学习资料
✅ C++阶段检查清单
[ ] 理解类的内存布局
[ ] 完全理解虚表机制
[ ] 能在反汇编中识别虚函数调用
[ ] 了解 STL 容器内存布局
[ ] 能识别构造函数和析构函数
[ ] 能分析 C++ 程序的类结构
3.2 x64汇编 ⭐⭐⭐⭐
🎯 目标:掌握 64 位汇编和 Windows x64 调用约定
⏱️ 时长:2-3 周
3.2.1 x64寄存器
学习小点
[ ] 64 位通用寄存器(RAX-RDX, RSI, RDI, RBP, RSP)
[ ] 新增寄存器(R8-R15)
[ ] 寄存器分割(RAX, EAX, AX, AL)
[ ] 特殊寄存器(RIP, RFLAGS)
寄存器详解
; x64通用寄存器 (16个)
; 原有寄存器的64位扩展:
RAX, RBX, RCX, RDX ; 数据寄存器
RSI, RDI ; 源/目标索引
RBP, RSP ; 栈帧/栈顶
RIP ; 指令指针
; 新增寄存器:
R8, R9, R10, R11 ; 通用
R12, R13, R14, R15 ; 通用(被调用者保存)
; 寄存器分割(以RAX为例):
; RAX (64位) = 0x1122334455667788
; EAX (低32位) = 0x55667788
; AX (低16位) = 0x7788
; AL (低8位) = 0x88
; AH (次低8位) = 0x77
; R8-R15的分割:
; R8 (64位)
; R8D (低32位)
; R8W (低16位)
; R8B (低8位)
; 注意:写入32位寄存器会清零高32位!
MOV EAX, 1 ; RAX = 0x0000000000000001
; 但写入8/16位不会影响高位
MOV AL, 1 ; 只修改AL,RAX高56位不变
📚 学习资料
3.2.2 x64 Windows调用约定(重要!)
学习小点
[ ] 参数传递规则(前 4 个用寄存器)
[ ] 寄存器分配(RCX, RDX, R8, R9)
[ ] 浮点参数(XMM0-XMM3)
[ ] Shadow Space(32 字节预留空间)
[ ] 栈对齐要求(16 字节)
[ ] 返回值规则
[ ] 易失性 / 非易失性寄存器
调用约定详解
; ========== x64 Windows调用约定 ==========
; 参数传递:
; 参数1: RCX (或XMM0如果是浮点)
; 参数2: RDX (或XMM1)
; 参数3: R8 (或XMM2)
; 参数4: R9 (或XMM3)
; 参数5+: 栈传递 [RSP+0x28], [RSP+0x30], ...
; Shadow Space (32字节):
; 调用者必须为前4个参数预留栈空间
; 即使参数用寄存器传递,也必须预留
; [RSP+0x00]: 返回地址
; [RSP+0x08]: 参数1的home space
; [RSP+0x10]: 参数2的home space
; [RSP+0x18]: 参数3的home space
; [RSP+0x20]: 参数4的home space
; [RSP+0x28]: 参数5 (如果有)
; 函数调用示例:func(1, 2, 3, 4, 5, 6)
SUB RSP, 0x38 ; 分配栈空间 (shadow 32 + 2参数 16 + 对齐)
MOV DWORD PTR [RSP+0x30], 6 ; 参数6
MOV DWORD PTR [RSP+0x28], 5 ; 参数5
MOV R9D, 4 ; 参数4
MOV R8D, 3 ; 参数3
MOV EDX, 2 ; 参数2
MOV ECX, 1 ; 参数1
CALL func
ADD RSP, 0x38
; 栈必须16字节对齐:
; CALL前RSP必须是16的倍数+8(因为CALL会压入8字节返回地址)
; 易失性寄存器(调用者保存,函数可能修改):
; RAX, RCX, RDX, R8, R9, R10, R11
; XMM0-XMM5
; 非易失性寄存器(被调用者保存,函数必须保存/恢复):
; RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15
; XMM6-XMM15
; 返回值:
; 整数/指针: RAX
; 浮点: XMM0
; 小结构体: RAX (如果<=8字节)
; 大结构体: 隐藏参数传递地址
函数序言/尾声
; 典型x64函数序言:
MyFunction:
; 可能有:
MOV [RSP+0x08], RCX ; 保存参数到shadow space
MOV [RSP+0x10], RDX
PUSH RBP ; 保存非易失性寄存器
PUSH RBX
PUSH RSI
PUSH RDI
PUSH R12
PUSH R13
PUSH R14
PUSH R15
SUB RSP, 0x?? ; 分配局部变量空间
LEA RBP, [RSP+0x??] ; 建立栈帧(可选)
; 函数体...
; 典型x64函数尾声:
LEA RSP, [RBP-0x??] ; 或 ADD RSP, 0x??
POP R15
POP R14
POP R13
POP R12
POP RDI
POP RSI
POP RBX
POP RBP
RET
📚 学习资料
3.2.3 x64特有指令
学习小点
[ ] RIP 相对寻址
[ ] MOVSXD(符号扩展到 64 位)
[ ] 新增条件移动指令
[ ] 64 位立即数加载
代码示例
; ========== RIP相对寻址 ==========
; x64默认使用RIP相对寻址访问全局变量
; 更紧凑,位置无关
; 访问全局变量:
MOV RAX, [RIP+offset] ; RIP相对
; IDA中显示为:
MOV RAX, cs:g_variable
; vs x86:
MOV EAX, [0x00401000] ; 绝对地址
; ========== 64位立即数 ==========
; MOV指令可以加载64位立即数到寄存器
MOV RAX, 0x123456789ABCDEF0 ; 10字节指令
; 但不能直接移动64位立即数到内存
; 需要先加载到寄存器
MOV RAX, 0x123456789ABCDEF0
MOV [RBX], RAX
; ========== MOVSXD (符号扩展) ==========
; 32位有符号数扩展到64位
MOVSXD RAX, EBX ; RAX = (int64_t)(int32_t)EBX
MOVSXD RAX, DWORD PTR [RCX] ; 从内存读取
; ========== LEA的64位用法 ==========
; 计算复杂地址/算术
LEA RAX, [RBX+RCX*8+0x100] ; RAX = RBX + RCX*8 + 0x100
; 获取RIP相对地址
LEA RAX, [RIP+offset] ; 获取全局变量/函数地址
📚 学习资料
✅ x64汇编阶段检查清单
[ ] 认识所有 x64 寄存器
[ ] 完全理解 x64 调用约定
[ ] 理解 Shadow Space 概念
[ ] 理解栈对齐要求
[ ] 能识别 x64 函数序言 / 尾声
[ ] 理解 RIP 相对寻址
3.3 PR(保护研究)⭐⭐⭐⭐
🎯 目标:了解反调试、加壳脱壳等保护技术
⏱️ 时长:2-3 周
3.3.1 反调试技术
学习小点
[ ] IsDebuggerPresent
[ ] PEB.BeingDebugged
[ ] NtQueryInformationProcess
[ ] CheckRemoteDebuggerPresent
[ ] 时间检测
[ ] 硬件断点检测
[ ] SEH 检测
反调试代码示例
// ========== IsDebuggerPresent ==========
if (IsDebuggerPresent()) {
// 检测到调试器
ExitProcess(0);
}
// ========== PEB.BeingDebugged ==========
// 直接读取PEB
#ifdef _WIN64
PPEB pPeb = (PPEB)__readgsqword(0x60);
#else
PPEB pPeb = (PPEB)__readfsdword(0x30);
#endif
if (pPeb->BeingDebugged) {
ExitProcess(0);
}
// ========== NtQueryInformationProcess ==========
typedef NTSTATUS (NTAPI *pNtQueryInformationProcess)(
HANDLE, UINT, PVOID, ULONG, PULONG);
pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
// ProcessDebugPort (0x07)
DWORD debugPort = 0;
NtQIP(GetCurrentProcess(), 7, &debugPort, sizeof(debugPort), NULL);
if (debugPort != 0) {
ExitProcess(0);
}
// ProcessDebugObjectHandle (0x1E)
HANDLE debugObject = 0;
NtQIP(GetCurrentProcess(), 0x1E, &debugObject, sizeof(debugObject), NULL);
if (debugObject != 0) {
ExitProcess(0);
}
// ========== 时间检测 ==========
DWORD t1 = GetTickCount();
// 代码块
DWORD t2 = GetTickCount();
if (t2 - t1 > 100) { // 正常执行应该很快
ExitProcess(0); // 可能被单步调试
}
// ========== RDTSC时间检测 ==========
unsigned __int64 t1 = __rdtsc();
// 代码块
unsigned __int64 t2 = __rdtsc();
if (t2 - t1 > 1000000) {
ExitProcess(0);
}
// ========== 硬件断点检测 ==========
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ctx);
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) {
ExitProcess(0);
}
绕过反调试
绕过方法:
1. IsDebuggerPresent绕过:
- 修改PEB.BeingDebugged为0
- Hook IsDebuggerPresent返回FALSE
- NOP掉检测代码
2. NtQueryInformationProcess绕过:
- Hook函数返回伪造结果
- 使用x64dbg的ScyllaHide插件
3. 时间检测绕过:
- Hook GetTickCount等时间函数
- 修改RDTSC指令返回值
4. 通用方法:
- 使用ScyllaHide插件(推荐)
- 使用TitanHide驱动
📚 学习资料
3.3.2 加壳与脱壳
学习小点
[ ] 壳的原理与分类
[ ] 常见壳的识别
[ ] 手动脱壳流程
[ ] 自动脱壳工具
壳的分类
压缩壳:
- UPX (最简单,入门首选)
- ASPack
- PECompact
保护壳:
- Themida/WinLicense
- VMProtect
- Enigma Protector
虚拟机壳:
- VMProtect
- Code Virtualizer
手动脱壳流程
通用脱壳步骤:
1. 识别壳类型
- 使用DIE (Detect It Easy)
- 使用ExeInfo PE
2. 找OEP (原始入口点)
- ESP定律
- 单步跟踪
- 内存断点
3. Dump内存
- 使用x64dbg的Scylla插件
- 或OllyDumpEx
4. 修复IAT (导入表)
- 使用Scylla修复
- 手动修复
ESP定律脱壳步骤:
1. 运行程序,在入口点暂停
2. 观察ESP值 (通常 ESP = 原始ESP - 4)
3. 在ESP处设置硬件访问断点
4. F9运行,断在OEP附近
5. 单步找到真正的OEP
6. Dump并修复
UPX脱壳示例:
1. 直接用命令行: upx -d packed.exe
2. 或手动:
- pushad 后设置ESP硬件断点
- 断下来后找 popad + jmp OEP
📚 学习资料
✅ PR阶段检查清单
[ ] 了解常见反调试技术
[ ] 会使用 ScyllaHide 绕过反调试
[ ] 能识别常见壳
[ ] 会手动脱 UPX 壳
[ ] 理解脱壳的基本流程
[ ] 会使用 Scylla 修复 IAT
阶段四:Windows开发
⏱️ 总时长:2-3 个月
🎯 目标:掌握 Windows 核心 API 和应用程序开发
4.1 Win32 API ⭐⭐⭐⭐⭐
🎯 目标:掌握 Windows 核心 API,为后续逆向和驱动开发打基础
⏱️ 时长:4-6 周
4.1.1 进程与线程
学习小点
[ ] 进程概念与创建
[ ] CreateProcess
[ ] 进程句柄与 ID
[ ] 进程终止
[ ] 线程概念与创建
[ ] CreateThread
[ ] 线程同步
[ ] 线程本地存储(TLS)
[ ] 进程 / 线程枚举
[ ] CreateToolhelp32Snapshot
[ ] Process32First/Next
[ ] Thread32First/Next
代码示例
#include <windows.h>
#include <tlhelp32.h>
// ========== 创建进程 ==========
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
BOOL success = CreateProcessA(
NULL, // 应用程序名
"notepad.exe", // 命令行
NULL, // 进程安全属性
NULL, // 线程安全属性
FALSE, // 是否继承句柄
0, // 创建标志
NULL, // 环境变量
NULL, // 当前目录
&si, // 启动信息
&pi // 进程信息
);
if (success) {
printf("进程ID: %d\n", pi.dwProcessId);
printf("线程ID: %d\n", pi.dwThreadId);
// 等待进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
// 关闭句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
// ========== 创建线程 ==========
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
int* pValue = (int*)lpParam;
printf("线程参数: %d\n", *pValue);
return 0;
}
int param = 100;
HANDLE hThread = CreateThread(
NULL, // 安全属性
0, // 栈大小(0=默认)
ThreadFunc, // 线程函数
¶m, // 参数
0, // 创建标志
NULL // 线程ID
);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
// ========== 枚举进程 ==========
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe = { sizeof(pe) };
if (Process32First(hSnapshot, &pe)) {
do {
printf("PID: %5d Name: %s\n", pe.th32ProcessID, pe.szExeFile);
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
// ========== 根据名称查找进程ID ==========
DWORD GetProcessIdByName(const char* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe = { sizeof(pe) };
DWORD pid = 0;
if (Process32First(hSnapshot, &pe)) {
do {
if (_stricmp(pe.szExeFile, processName) == 0) {
pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return pid;
}
📚 学习资料
4.1.2 内存操作(逆向核心!)
学习小点
[ ] 进程内存操作
[ ] OpenProcess
[ ] ReadProcessMemory
[ ] WriteProcessMemory
[ ] VirtualAllocEx
[ ] VirtualProtectEx
[ ] VirtualFreeEx
[ ] 内存属性
[ ] PAGE_EXECUTE_READWRITE
[ ] MEM_COMMIT / MEM_RESERVE
[ ] 模块枚举
[ ] EnumProcessModules
[ ] GetModuleInformation
代码示例
#include <windows.h>
#include <psapi.h>
// ========== 读写其他进程内存 ==========
DWORD pid = 1234; // 目标进程ID
HANDLE hProcess = OpenProcess(
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,
FALSE,
pid
);
if (hProcess) {
// 读取内存
int value;
SIZE_T bytesRead;
LPVOID address = (LPVOID)0x12345678;
if (ReadProcessMemory(hProcess, address, &value, sizeof(value), &bytesRead)) {
printf("读取值: %d\n", value);
}
// 写入内存
int newValue = 999;
SIZE_T bytesWritten;
// 可能需要先修改内存保护
DWORD oldProtect;
VirtualProtectEx(hProcess, address, sizeof(newValue),
PAGE_EXECUTE_READWRITE, &oldProtect);
if (WriteProcessMemory(hProcess, address, &newValue, sizeof(newValue), &bytesWritten)) {
printf("写入成功\n");
}
// 恢复保护
VirtualProtectEx(hProcess, address, sizeof(newValue), oldProtect, &oldProtect);
CloseHandle(hProcess);
}
// ========== 在目标进程分配内存 ==========
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
LPVOID remoteMem = VirtualAllocEx(
hProcess,
NULL, // 让系统选择地址
4096, // 大小
MEM_COMMIT | MEM_RESERVE, // 分配类型
PAGE_EXECUTE_READWRITE // 保护属性
);
if (remoteMem) {
printf("分配的远程内存: %p\n", remoteMem);
// 写入数据
char shellcode[] = { /* ... */ };
WriteProcessMemory(hProcess, remoteMem, shellcode, sizeof(shellcode), NULL);
// 使用完后释放
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
}
// ========== 枚举模块 ==========
HMODULE hMods[1024];
DWORD cbNeeded;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
int moduleCount = cbNeeded / sizeof(HMODULE);
for (int i = 0; i < moduleCount; i++) {
char modName[MAX_PATH];
MODULEINFO modInfo;
GetModuleFileNameExA(hProcess, hMods[i], modName, sizeof(modName));
GetModuleInformation(hProcess, hMods[i], &modInfo, sizeof(modInfo));
printf("模块: %s\n", modName);
printf(" 基址: %p\n", modInfo.lpBaseOfDll);
printf(" 大小: 0x%X\n", modInfo.SizeOfImage);
}
}
// ========== 获取模块基址 ==========
DWORD_PTR GetModuleBaseAddress(DWORD pid, const char* moduleName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
MODULEENTRY32 me = { sizeof(me) };
DWORD_PTR baseAddr = 0;
if (Module32First(hSnapshot, &me)) {
do {
if (_stricmp(me.szModule, moduleName) == 0) {
baseAddr = (DWORD_PTR)me.modBaseAddr;
break;
}
} while (Module32Next(hSnapshot, &me));
}
CloseHandle(hSnapshot);
return baseAddr;
}
📚 学习资料
4.1.3 DLL操作
学习小点
[ ] DLL 加载与卸载
[ ] LoadLibrary / LoadLibraryEx
[ ] FreeLibrary
[ ] GetProcAddress
[ ] DLL 编写
[ ] DllMain 入口
[ ] 导出函数
[ ] DLL 注入基础
[ ] GetModuleHandle
代码示例
// ========== 加载和使用DLL ==========
HMODULE hDll = LoadLibraryA("user32.dll");
if (hDll) {
// 获取函数地址
typedef int (WINAPI *MessageBoxA_t)(HWND, LPCSTR, LPCSTR, UINT);
MessageBoxA_t pMessageBoxA = (MessageBoxA_t)GetProcAddress(hDll, "MessageBoxA");
if (pMessageBoxA) {
pMessageBoxA(NULL, "Hello", "Title", MB_OK);
}
FreeLibrary(hDll);
}
// ========== 编写DLL ==========
// mydll.cpp
#include <windows.h>
// 导出函数
extern "C" __declspec(dllexport) int Add(int a, int b) {
return a + b;
}
extern "C" __declspec(dllexport) void ShowMessage(const char* msg) {
MessageBoxA(NULL, msg, "MyDLL", MB_OK);
}
// DLL入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
switch (reason) {
case DLL_PROCESS_ATTACH:
// DLL被加载时执行
// 常用于注入后的初始化
break;
case DLL_PROCESS_DETACH:
// DLL被卸载时执行
break;
case DLL_THREAD_ATTACH:
// 新线程创建时
break;
case DLL_THREAD_DETACH:
// 线程退出时
break;
}
return TRUE;
}
// ========== .def文件导出 ==========
// mydll.def
LIBRARY "mydll"
EXPORTS
Add @1
ShowMessage @2
📚 学习资料
4.1.4 文件与注册表
学习小点
[ ] 文件操作
[ ] CreateFile
[ ] ReadFile / WriteFile
[ ] SetFilePointer
[ ] 文件映射(CreateFileMapping, MapViewOfFile)
[ ] 注册表操作
[ ] RegOpenKeyEx
[ ] RegQueryValueEx
[ ] RegSetValueEx
[ ] RegCreateKeyEx
代码示例
// ========== 文件操作 ==========
HANDLE hFile = CreateFileA(
"test.bin",
GENERIC_READ | GENERIC_WRITE,
0, // 不共享
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile != INVALID_HANDLE_VALUE) {
// 写入数据
char data[] = "Hello World";
DWORD bytesWritten;
WriteFile(hFile, data, sizeof(data), &bytesWritten, NULL);
// 移动文件指针到开头
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
// 读取数据
char buffer[100];
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
CloseHandle(hFile);
}
// ========== 文件映射(分析大文件利器)==========
HANDLE hFile = CreateFileA("large.exe", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
LPVOID pFileBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (pFileBase) {
// 直接访问文件内容
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBase;
if (pDos->e_magic == IMAGE_DOS_SIGNATURE) {
printf("有效的PE文件\n");
}
UnmapViewOfFile(pFileBase);
}
CloseHandle(hMapping);
CloseHandle(hFile);
// ========== 注册表操作 ==========
HKEY hKey;
LONG result = RegOpenKeyExA(
HKEY_CURRENT_USER,
"Software\\MyApp",
0,
KEY_READ | KEY_WRITE,
&hKey
);
if (result == ERROR_SUCCESS) {
// 读取值
char value[256];
DWORD valueSize = sizeof(value);
DWORD type;
RegQueryValueExA(hKey, "Setting1", NULL, &type, (LPBYTE)value, &valueSize);
// 写入值
const char* newValue = "NewData";
RegSetValueExA(hKey, "Setting1", 0, REG_SZ,
(const BYTE*)newValue, strlen(newValue) + 1);
RegCloseKey(hKey);
}
📚 学习资料
4.1.5 窗口与消息
学习小点
[ ] 窗口创建流程
[ ] RegisterClass
[ ] CreateWindow
[ ] ShowWindow
[ ] 消息循环
[ ] 消息处理
[ ] WndProc 回调
[ ] 常见消息(WM_CREATE, WM_DESTROY 等)
[ ] 窗口查找
[ ] FindWindow
[ ] EnumWindows
代码示例
// ========== 创建窗口 ==========
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE:
// 窗口创建时
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
// 注册窗口类
WNDCLASSA wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "MyWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClassA(&wc);
// 创建窗口
HWND hWnd = CreateWindowA(
"MyWindowClass",
"My Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, hInstance, NULL
);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// ========== 查找窗口 ==========
HWND hWnd = FindWindowA("Notepad", NULL); // 查找记事本
if (hWnd) {
// 获取进程ID
DWORD pid;
GetWindowThreadProcessId(hWnd, &pid);
printf("记事本PID: %d\n", pid);
// 发送消息
SendMessage(hWnd, WM_CLOSE, 0, 0);
}
// ========== 枚举所有窗口 ==========
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) {
char title[256];
GetWindowTextA(hWnd, title, sizeof(title));
if (strlen(title) > 0) {
printf("HWND: %p Title: %s\n", hWnd, title);
}
return TRUE; // 继续枚举
}
EnumWindows(EnumWindowsProc, 0);
📚 学习资料
✅ Win32 API阶段检查清单
[ ] 能创建进程和线程
[ ] 能枚举系统进程
[ ] 能读写其他进程内存
[ ] 能加载 DLL 并调用函数
[ ] 能编写简单的 DLL
[ ] 理解文件映射
[ ] 能操作注册表
[ ] 能创建窗口程序
[ ] 能查找和枚举窗口
4.2 MFC ⭐⭐⭐
🎯 目标:了解 MFC 框架,能逆向 MFC 程序
⏱️ 时长:1-2 周
4.2.1 MFC基础
学习小点
[ ] MFC 框架结构
[ ] CWinApp 应用程序类
[ ] CFrameWnd 框架窗口
[ ] CView 视图类
[ ] CDocument 文档类
[ ] 消息映射机制
[ ] BEGIN_MESSAGE_MAP / END_MESSAGE_MAP
[ ] ON_COMMAND
[ ] ON_MESSAGE
[ ] MFC 程序识别特征
MFC程序结构
// ========== MFC应用程序基本结构 ==========
// 应用程序类
class CMyApp : public CWinApp {
public:
virtual BOOL InitInstance();
};
// 主框架窗口
class CMainFrame : public CFrameWnd {
DECLARE_MESSAGE_MAP()
public:
CMainFrame();
afx_msg void OnButtonClick();
};
// 消息映射
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_COMMAND(ID_BUTTON1, OnButtonClick)
ON_WM_CREATE()
END_MESSAGE_MAP()
// 实现
BOOL CMyApp::InitInstance() {
CMainFrame* pFrame = new CMainFrame();
pFrame->Create(NULL, _T("My MFC App"));
pFrame->ShowWindow(SW_SHOW);
m_pMainWnd = pFrame;
return TRUE;
}
CMyApp theApp; // 全局应用程序对象
MFC逆向特征
MFC程序识别特征:
1. 导入表特征:
- 导入 MFC42.dll / MFC140u.dll 等
- 导入 MSVCRT.dll / MSVCP140.dll
2. 函数命名特征:
- AFX开头的函数 (AfxWinMain, AfxGetApp等)
- 类名格式:C????? (CWnd, CButton等)
3. 消息映射特征:
- 消息映射表结构
- AFX_MSGMAP结构
4. 运行时类型信息:
- CRuntimeClass结构
- DECLARE_DYNAMIC / IMPLEMENT_DYNAMIC
消息映射表结构:
struct AFX_MSGMAP_ENTRY {
UINT nMessage; // Windows消息
UINT nCode; // 控制代码
UINT nID; // 控件ID
UINT nLastID; // 范围最后ID
UINT_PTR nSig; // 签名
AFX_PMSG pfn; // 处理函数
};
📚 学习资料
✅ MFC阶段检查清单
[ ] 了解 MFC 基本框架
[ ] 能识别 MFC 程序
[ ] 理解消息映射机制
[ ] 能在 IDA 中定位消息处理函数
阶段五:核心逆向技术
⏱️ 总时长:2-3 个月
🎯 目标:掌握 DLL 注入和各种 Hook 技术
5.1 Inject(注入)⭐⭐⭐⭐⭐
🎯 目标:掌握各种 DLL 注入技术
⏱️ 时长:3-4 周
5.1.1 远程线程注入
学习小点
[ ] 注入原理理解
[ ] 获取 LoadLibrary 地址
[ ] 在目标进程分配内存
[ ] 写入 DLL 路径
[ ] 创建远程线程
[ ] 等待注入完成
[ ] 清理资源
完整代码示例
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
// 根据进程名获取PID
DWORD GetProcessIdByName(const char* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe = { sizeof(pe) };
DWORD pid = 0;
if (Process32First(hSnapshot, &pe)) {
do {
if (_stricmp(pe.szExeFile, processName) == 0) {
pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return pid;
}
// 远程线程注入
BOOL InjectDll(DWORD pid, const char* dllPath) {
BOOL result = FALSE;
HANDLE hProcess = NULL;
LPVOID remotePath = NULL;
HANDLE hThread = NULL;
// 1. 打开目标进程
hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION,
FALSE, pid
);
if (!hProcess) {
printf("[-] OpenProcess failed: %d\n", GetLastError());
goto cleanup;
}
printf("[+] 打开进程成功\n");
// 2. 在目标进程分配内存
size_t pathLen = strlen(dllPath) + 1;
remotePath = VirtualAllocEx(
hProcess, NULL, pathLen,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (!remotePath) {
printf("[-] VirtualAllocEx failed: %d\n", GetLastError());
goto cleanup;
}
printf("[+] 分配远程内存: %p\n", remotePath);
// 3. 写入DLL路径
if (!WriteProcessMemory(hProcess, remotePath, dllPath, pathLen, NULL)) {
printf("[-] WriteProcessMemory failed: %d\n", GetLastError());
goto cleanup;
}
printf("[+] 写入DLL路径成功\n");
// 4. 获取LoadLibraryA地址
// kernel32.dll在所有进程中基址相同,所以可以直接获取
LPVOID loadLibraryAddr = GetProcAddress(
GetModuleHandleA("kernel32.dll"), "LoadLibraryA"
);
printf("[+] LoadLibraryA地址: %p\n", loadLibraryAddr);
// 5. 创建远程线程执行LoadLibraryA
hThread = CreateRemoteThread(
hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)loadLibraryAddr,
remotePath, 0, NULL
);
if (!hThread) {
printf("[-] CreateRemoteThread failed: %d\n", GetLastError());
goto cleanup;
}
printf("[+] 创建远程线程成功\n");
// 6. 等待线程执行完成
WaitForSingleObject(hThread, INFINITE);
// 获取返回值(模块句柄)
DWORD exitCode;
GetExitCodeThread(hThread, &exitCode);
if (exitCode != 0) {
printf("[+] 注入成功! 模块句柄: 0x%X\n", exitCode);
result = TRUE;
} else {
printf("[-] LoadLibrary返回NULL,注入可能失败\n");
}
cleanup:
if (hThread) CloseHandle(hThread);
if (remotePath) VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
if (hProcess) CloseHandle(hProcess);
return result;
}
int main(int argc, char* argv[]) {
if (argc < 3) {
printf("用法: injector.exe <进程名> <DLL路径>\n");
return 1;
}
const char* processName = argv[1];
const char* dllPath = argv[2];
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
printf("[-] 未找到进程: %s\n", processName);
return 1;
}
printf("[+] 找到进程 %s, PID: %d\n", processName, pid);
// 获取DLL完整路径
char fullPath[MAX_PATH];
GetFullPathNameA(dllPath, MAX_PATH, fullPath, NULL);
printf("[+] DLL路径: %s\n", fullPath);
if (InjectDll(pid, fullPath)) {
printf("[+] 注入完成!\n");
} else {
printf("[-] 注入失败!\n");
}
return 0;
}
📚 学习资料
5.1.2 APC注入
学习小点
[ ] APC 原理(异步过程调用)
[ ] QueueUserAPC 函数
[ ] 目标线程必须处于 alertable 状态
[ ] 枚举目标进程的所有线程
代码示例
#include <windows.h>
#include <tlhelp32.h>
BOOL ApcInject(DWORD pid, const char* dllPath) {
HANDLE hProcess = NULL;
LPVOID remotePath = NULL;
BOOL result = FALSE;
// 打开进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) return FALSE;
// 分配内存写入DLL路径
size_t pathLen = strlen(dllPath) + 1;
remotePath = VirtualAllocEx(hProcess, NULL, pathLen,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(hProcess, remotePath, dllPath, pathLen, NULL);
// 获取LoadLibraryA地址
LPVOID loadLibrary = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
// 枚举目标进程的所有线程
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 te = { sizeof(te) };
if (Thread32First(hSnapshot, &te)) {
do {
if (te.th32OwnerProcessID == pid) {
// 打开线程
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, te.th32ThreadID);
if (hThread) {
// 向线程队列添加APC
QueueUserAPC(
(PAPCFUNC)loadLibrary,
hThread,
(ULONG_PTR)remotePath
);
printf("[+] 向线程 %d 添加APC\n", te.th32ThreadID);
CloseHandle(hThread);
result = TRUE;
}
}
} while (Thread32Next(hSnapshot, &te));
}
CloseHandle(hSnapshot);
CloseHandle(hProcess);
// 注意:APC只有在线程进入alertable状态时才会执行
// 如SleepEx, WaitForSingleObjectEx, MsgWaitForMultipleObjectsEx等
return result;
}
📚 学习资料
5.1.3 手动映射(Manual Map)
学习小点
[ ] PE 文件结构深入理解
[ ] 手动映射原理
[ ] 读取 DLL 文件
[ ] 分配内存
[ ] 复制节区
[ ] 处理重定位
[ ] 处理导入表
[ ] 调用 DllMain
[ ] 优点:不在模块列表中显示
实现概要
// 手动映射步骤概要(完整实现较复杂)
struct ManualMapData {
LPVOID imageBase;
HMODULE (WINAPI *pLoadLibraryA)(LPCSTR);
FARPROC (WINAPI *pGetProcAddress)(HMODULE, LPCSTR);
};
// 1. 读取DLL文件到内存
BYTE* dllData = ReadFileToMemory(dllPath);
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dllData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(dllData + pDos->e_lfanew);
// 2. 在目标进程分配内存
LPVOID remoteBase = VirtualAllocEx(hProcess, NULL, pNt->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 3. 复制PE头
WriteProcessMemory(hProcess, remoteBase, dllData, pNt->OptionalHeader.SizeOfHeaders, NULL);
// 4. 复制各节区
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
LPVOID sectionDst = (LPBYTE)remoteBase + pSection[i].VirtualAddress;
LPVOID sectionSrc = dllData + pSection[i].PointerToRawData;
WriteProcessMemory(hProcess, sectionDst, sectionSrc, pSection[i].SizeOfRawData, NULL);
}
// 5. 处理重定位
// 如果实际加载地址与ImageBase不同,需要修正重定位表
// 6. 处理导入表
// 解析导入表,获取每个导入函数的地址并填入IAT
// 7. 执行TLS回调(如果有)
// 8. 调用DllMain
// 在目标进程中执行shellcode来调用DllMain
// 优点:
// - 不在PEB的模块列表中
// - 不创建LDR_DATA_TABLE_ENTRY
// - 更难被检测
📚 学习资料
5.1.4 其他注入技术
学习小点
[ ] SetWindowsHookEx 注入
[ ] 注册表 AppInit_DLLs
[ ] 劫持 DLL(DLL Hijacking)
[ ] 线程劫持注入
[ ] NtCreateThreadEx
简要说明
// ========== SetWindowsHookEx 注入 ==========
// 通过全局钩子将DLL注入到所有窗口进程
HHOOK hHook = SetWindowsHookEx(
WH_GETMESSAGE, // 钩子类型
HookProc, // 钩子函数(必须在DLL中)
hDllModule, // DLL模块句柄
0 // 0 = 所有线程
);
// 当目标进程接收消息时,系统会自动将DLL加载到该进程
// ========== AppInit_DLLs 注入 ==========
// 注册表位置:
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
// 设置 AppInit_DLLs = C:\path\to\dll.dll
// 设置 LoadAppInit_DLLs = 1
// 所有加载user32.dll的进程都会加载该DLL
// ========== DLL劫持 ==========
// 将恶意DLL命名为程序依赖的DLL名
// 放在程序目录下(DLL搜索顺序问题)
// 程序启动时会加载你的DLL
// ========== 线程劫持 ==========
// 1. SuspendThread暂停目标线程
// 2. GetThreadContext获取线程上下文
// 3. 修改RIP/EIP指向shellcode
// 4. SetThreadContext设置修改后的上下文
// 5. ResumeThread恢复线程
📚 学习资料
✅ 注入技术阶段检查清单
[ ] 完全理解远程线程注入原理
[ ] 能编写远程线程注入器
[ ] 理解 APC 注入原理
[ ] 了解手动映射的原理
[ ] 了解其他注入方式
[ ] 能编写被注入的 DLL
5.2 Hook ⭐⭐⭐⭐⭐
🎯 目标:掌握各种 Hook 技术
⏱️ 时长:3-4 周
5.2.1 IAT Hook
学习小点
[ ] IAT(导入地址表)结构
[ ] PE 文件导入表解析
[ ] 定位目标函数 IAT 项
[ ] 修改 IAT 指针
[ ] 恢复原函数
完整代码示例
#include <windows.h>
#include <stdio.h>
// 原始函数指针
typedef int (WINAPI *MessageBoxA_t)(HWND, LPCSTR, LPCSTR, UINT);
MessageBoxA_t OriginalMessageBoxA = NULL;
// Hook函数
int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
printf("[Hook] MessageBoxA被调用!\n");
printf(" Text: %s\n", lpText);
printf(" Caption: %s\n", lpCaption);
// 修改参数
return OriginalMessageBoxA(hWnd, "Hooked!", "IAT Hook", uType);
}
// IAT Hook实现
BOOL IATHook(HMODULE hModule, const char* dllName, const char* funcName, LPVOID hookFunc, LPVOID* origFunc) {
// 获取PE头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((LPBYTE)hModule + pDos->e_lfanew);
// 获取导入表
DWORD importRVA = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (importRVA == 0) return FALSE;
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)hModule + importRVA);
// 遍历导入表
while (pImport->Name) {
const char* modName = (const char*)((LPBYTE)hModule + pImport->Name);
// 查找目标DLL
if (_stricmp(modName, dllName) == 0) {
// 获取IAT和INT(名称表)
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((LPBYTE)hModule + pImport->FirstThunk);
PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((LPBYTE)hModule + pImport->OriginalFirstThunk);
// 遍历函数
while (pThunk->u1.Function) {
// 检查是否按名称导入
if (!(pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)) {
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)hModule + pOrigThunk->u1.AddressOfData);
// 查找目标函数
if (strcmp(pName->Name, funcName) == 0) {
printf("[+] 找到函数 %s @ %p\n", funcName, (LPVOID)pThunk->u1.Function);
// 保存原函数地址
*origFunc = (LPVOID)pThunk->u1.Function;
// 修改内存保护
DWORD oldProtect;
VirtualProtect(&pThunk->u1.Function, sizeof(LPVOID), PAGE_READWRITE, &oldProtect);
// 替换为Hook函数
pThunk->u1.Function = (ULONG_PTR)hookFunc;
// 恢复内存保护
VirtualProtect(&pThunk->u1.Function, sizeof(LPVOID), oldProtect, &oldProtect);
printf("[+] Hook成功!\n");
return TRUE;
}
}
pThunk++;
pOrigThunk++;
}
}
pImport++;
}
return FALSE;
}
// 测试
int main() {
// 安装Hook
if (IATHook(GetModuleHandle(NULL), "user32.dll", "MessageBoxA",
HookedMessageBoxA, (LPVOID*)&OriginalMessageBoxA)) {
printf("[+] IAT Hook安装成功\n");
}
// 调用测试
MessageBoxA(NULL, "原始消息", "测试", MB_OK);
return 0;
}
📚 学习资料
5.2.2 Inline Hook
学习小点
[ ] Inline Hook 原理
[ ] x86 跳转指令(5 字节:E9 + 偏移)
[ ] x64 跳转指令(14 字节:绝对地址跳转)
[ ] 跳板(Trampoline)实现
[ ] 保存和恢复原始指令
[ ] 线程安全问题
x86 Inline Hook
#include <windows.h>
#include <stdio.h>
// 5字节JMP指令
#pragma pack(push, 1)
struct JmpInstruction {
BYTE opcode; // E9
DWORD offset; // 相对偏移
};
#pragma pack(pop)
// 原始函数指针
typedef int (WINAPI *MessageBoxA_t)(HWND, LPCSTR, LPCSTR, UINT);
MessageBoxA_t TrampolineMessageBoxA = NULL;
// 保存原始字节
BYTE originalBytes[5];
LPVOID targetFunc = NULL;
// Hook函数
int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
printf("[Inline Hook] MessageBoxA被调用!\n");
printf(" Text: %s\n", lpText);
// 调用原函数(通过跳板)
return TrampolineMessageBoxA(hWnd, "Hooked by Inline Hook!", lpCaption, uType);
}
// 安装Inline Hook (x86)
BOOL InstallInlineHook(LPVOID target, LPVOID hook, LPVOID* trampoline) {
DWORD oldProtect;
// 分配跳板内存
*trampoline = VirtualAlloc(NULL, 32, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!*trampoline) return FALSE;
// 复制原始指令到跳板(至少5字节)
// 注意:实际实现需要反汇编确定指令边界
memcpy(*trampoline, target, 5);
// 在跳板末尾添加跳回原函数的指令
BYTE* trampolineEnd = (BYTE*)*trampoline + 5;
trampolineEnd[0] = 0xE9; // JMP
*(DWORD*)(trampolineEnd + 1) = (DWORD)((BYTE*)target + 5 - (trampolineEnd + 5));
// 修改目标函数保护属性
VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
// 保存原始字节
memcpy(originalBytes, target, 5);
// 写入跳转指令
BYTE* pTarget = (BYTE*)target;
pTarget[0] = 0xE9; // JMP rel32
*(DWORD*)(pTarget + 1) = (DWORD)((BYTE*)hook - (pTarget + 5));
// 恢复保护属性
VirtualProtect(target, 5, oldProtect, &oldProtect);
targetFunc = target;
return TRUE;
}
// 卸载Hook
BOOL UninstallInlineHook() {
if (!targetFunc) return FALSE;
DWORD oldProtect;
VirtualProtect(targetFunc, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(targetFunc, originalBytes, 5);
VirtualProtect(targetFunc, 5, oldProtect, &oldProtect);
return TRUE;
}
int main() {
LPVOID target = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
if (InstallInlineHook(target, HookedMessageBoxA, (LPVOID*)&TrampolineMessageBoxA)) {
printf("[+] Inline Hook安装成功\n");
}
MessageBoxA(NULL, "原始消息", "测试", MB_OK);
UninstallInlineHook();
MessageBoxA(NULL, "Hook已卸载", "测试", MB_OK);
return 0;
}
x64 Inline Hook
// x64需要使用14字节绝对跳转
// FF 25 00 00 00 00 ; JMP [RIP+0]
// XX XX XX XX XX XX XX XX ; 8字节绝对地址
BOOL InstallInlineHook64(LPVOID target, LPVOID hook, LPVOID* trampoline) {
DWORD oldProtect;
// 分配跳板(需要在目标函数附近以支持短跳转,或使用绝对跳转)
*trampoline = VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 复制原始指令到跳板(至少14字节)
memcpy(*trampoline, target, 14);
// 跳板末尾:跳回原函数+14
BYTE* trampolineEnd = (BYTE*)*trampoline + 14;
trampolineEnd[0] = 0xFF;
trampolineEnd[1] = 0x25;
*(DWORD*)(trampolineEnd + 2) = 0;
*(ULONGLONG*)(trampolineEnd + 6) = (ULONGLONG)((BYTE*)target + 14);
// 修改目标函数
VirtualProtect(target, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
BYTE* pTarget = (BYTE*)target;
pTarget[0] = 0xFF;
pTarget[1] = 0x25;
*(DWORD*)(pTarget + 2) = 0;
*(ULONGLONG*)(pTarget + 6) = (ULONGLONG)hook;
VirtualProtect(target, 14, oldProtect, &oldProtect);
return TRUE;
}
📚 学习资料
5.2.3 VEH Hook
学习小点
[ ] VEH(向量异常处理)原理
[ ] AddVectoredExceptionHandler
[ ] 硬件断点(DR 寄存器)
[ ] INT3 断点
[ ] 页面保护异常
代码示例
#include <windows.h>
#include <stdio.h>
// 目标函数地址
LPVOID g_targetFunc = NULL;
LPVOID g_hookFunc = NULL;
// VEH处理函数
LONG CALLBACK VEHHandler(PEXCEPTION_POINTERS pExceptionInfo) {
// 检查是否是我们设置的断点
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
// 检查是否是目标地址
if ((LPVOID)pExceptionInfo->ContextRecord->Rip == g_targetFunc) {
printf("[VEH] 捕获到目标函数调用!\n");
// 重定向到Hook函数
pExceptionInfo->ContextRecord->Rip = (DWORD64)g_hookFunc;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
// 设置硬件断点
void SetHardwareBreakpoint(LPVOID address, int index) {
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
HANDLE hThread = GetCurrentThread();
GetThreadContext(hThread, &ctx);
// 设置DR0-DR3中的一个
switch (index) {
case 0: ctx.Dr0 = (DWORD64)address; break;
case 1: ctx.Dr1 = (DWORD64)address; break;
case 2: ctx.Dr2 = (DWORD64)address; break;
case 3: ctx.Dr3 = (DWORD64)address; break;
}
// 启用断点 (DR7)
// 位0,2,4,6: 本地启用DR0-DR3
// 位16-17, 20-21, 24-25, 28-29: 条件类型 (00=执行)
// 位18-19, 22-23, 26-27, 30-31: 长度 (00=1字节)
ctx.Dr7 |= (1 << (index * 2)); // 本地启用
SetThreadContext(hThread, &ctx);
}
// 安装VEH Hook
BOOL InstallVEHHook(LPVOID target, LPVOID hook) {
g_targetFunc = target;
g_hookFunc = hook;
// 注册VEH处理函数
AddVectoredExceptionHandler(1, VEHHandler);
// 设置硬件断点
SetHardwareBreakpoint(target, 0);
return TRUE;
}
📚 学习资料
5.2.4 Hook库推荐
常用Hook库
1. MinHook
- 轻量级、开源
- 支持x86和x64
- GitHub: TsudaKageyu/minhook
2. Microsoft Detours
- 微软官方库
- 功能强大
- GitHub: microsoft/Detours
3. PolyHook
- 现代C++实现
- 支持多种Hook方式
- GitHub: stevemk14ebr/PolyHook_2_0
使用MinHook示例:
#include "MinHook.h"
// 原函数类型
typedef int (WINAPI *MessageBoxA_t)(HWND, LPCSTR, LPCSTR, UINT);
MessageBoxA_t fpMessageBoxA = NULL;
// Hook函数
int WINAPI DetourMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
return fpMessageBoxA(hWnd, "Hooked by MinHook!", lpCaption, uType);
}
int main() {
// 初始化MinHook
MH_Initialize();
// 创建Hook
MH_CreateHook(
&MessageBoxA, // 目标函数
&DetourMessageBoxA, // Hook函数
(LPVOID*)&fpMessageBoxA // 原函数指针
);
// 启用Hook
MH_EnableHook(&MessageBoxA);
// 测试
MessageBoxA(NULL, "Test", "Test", MB_OK);
// 禁用Hook
MH_DisableHook(&MessageBoxA);
// 清理
MH_Uninitialize();
return 0;
}
📚 学习资料
✅ Hook技术阶段检查清单
[ ] 理解 IAT Hook 原理并能实现
[ ] 理解 Inline Hook 原理
[ ] 能实现 x86 Inline Hook
[ ] 了解 x64 Inline Hook 的区别
[ ] 了解 VEH Hook 原理
[ ] 会使用 MinHook 库
[ ] 能编写完整的 Hook 框架
阶段六:驱动开发
⏱️ 总时长:4-6 个月
🎯 目标:掌握 Windows 内核编程和驱动开发
6.1 驱动基础 ⭐⭐⭐⭐
🎯 目标:搭建开发环境,编写第一个驱动
⏱️ 时长:4-6 周
6.1.1 环境搭建
学习小点
[ ] 安装 Visual Studio 2022
[ ] 安装 WDK(Windows Driver Kit)
[ ] 配置 VMware 虚拟机
[ ] 配置内核调试(WinDbg)
[ ] 测试签名设置
环境配置步骤
1. 安装Visual Studio 2022
- 选择"使用C++的桌面开发"
- 选择"Windows 10/11 SDK"
2. 安装WDK
- 下载对应VS版本的WDK
- 安装后会在VS中添加驱动项目模板
3. 虚拟机配置
# 在虚拟机中执行(管理员CMD)
bcdedit /set testsigning on # 允许测试签名
bcdedit /debug on # 启用调试
bcdedit /dbgsettings serial debugport:1 baudrate:115200
# VMware设置
添加串口设备 -> 使用命名管道 -> \\.\pipe\com_1
设置为"该端是服务器"、"另一端是应用程序"
4. WinDbg连接
# 在主机执行
windbg -k com:port=\\.\pipe\com_1,baud=115200,pipe
# 或使用WinDbg Preview
File -> Attach to kernel -> COM
📚 学习资料
6.1.2 Hello World驱动
学习小点
[ ] 驱动入口函数 DriverEntry
[ ] 驱动卸载函数 DriverUnload
[ ] DbgPrint 调试输出
[ ] 驱动加载与卸载
[ ] 使用 DebugView 查看输出
代码示例
// HelloDriver.c
#include <ntddk.h>
// 驱动卸载函数
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("[HelloDriver] Driver Unloading...\n");
}
// 驱动入口函数
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint("[HelloDriver] Hello from Kernel! Driver loaded.\n");
DbgPrint("[HelloDriver] DriverObject: %p\n", DriverObject);
// 设置卸载函数
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
加载驱动方法
// 方法1:使用SC命令
// 创建服务
sc create HelloDriver binPath= "C:\path\to\HelloDriver.sys" type= kernel
// 启动服务
sc start HelloDriver
// 停止服务
sc stop HelloDriver
// 删除服务
sc delete HelloDriver
// 方法2:编程方式加载
#include <windows.h>
BOOL LoadDriver(const char* driverPath, const char* serviceName) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!hSCM) return FALSE;
// 创建服务
SC_HANDLE hService = CreateServiceA(
hSCM,
serviceName,
serviceName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
driverPath,
NULL, NULL, NULL, NULL, NULL
);
if (!hService) {
hService = OpenServiceA(hSCM, serviceName, SERVICE_ALL_ACCESS);
}
if (!hService) {
CloseServiceHandle(hSCM);
return FALSE;
}
// 启动服务
BOOL result = StartService(hService, 0, NULL);
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return result;
}
📚 学习资料
6.1.3 设备与通信
学习小点
[ ] 创建设备对象(IoCreateDevice)
[ ] 创建符号链接(IoCreateSymbolicLink)
[ ] 派遣函数(MajorFunction)
[ ] IRP 处理
[ ] DeviceIoControl 通信
[ ] 读写操作
完整示例
// Driver.c - 带设备通信的驱动
#include <ntddk.h>
// IOCTL定义
#define IOCTL_READ_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_WRITE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 设备名和符号链接
#define DEVICE_NAME L"\\Device\\MyDriver"
#define SYMBOLIC_LINK L"\\??\\MyDriverLink"
PDEVICE_OBJECT g_DeviceObject = NULL;
// 通用派遣函数
NTSTATUS DispatchCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// DeviceIoControl处理
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG bytesReturned = 0;
PVOID inputBuffer = Irp->AssociatedIrp.SystemBuffer;
PVOID outputBuffer = Irp->AssociatedIrp.SystemBuffer;
ULONG inputLength = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outputLength = stack->Parameters.DeviceIoControl.OutputBufferLength;
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_READ_MEMORY:
DbgPrint("[Driver] IOCTL_READ_MEMORY\n");
// 处理读内存请求
break;
case IOCTL_WRITE_MEMORY:
DbgPrint("[Driver] IOCTL_WRITE_MEMORY\n");
// 处理写内存请求
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = bytesReturned;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// 驱动卸载
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING symbolicLink;
RtlInitUnicodeString(&symbolicLink, SYMBOLIC_LINK);
IoDeleteSymbolicLink(&symbolicLink);
if (g_DeviceObject) {
IoDeleteDevice(g_DeviceObject);
}
DbgPrint("[Driver] Unloaded\n");
}
// 驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status;
UNICODE_STRING deviceName, symbolicLink;
DbgPrint("[Driver] Loading...\n");
// 初始化设备名
RtlInitUnicodeString(&deviceName, DEVICE_NAME);
RtlInitUnicodeString(&symbolicLink, SYMBOLIC_LINK);
// 创建设备
status = IoCreateDevice(
DriverObject,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&g_DeviceObject
);
if (!NT_SUCCESS(status)) {
DbgPrint("[Driver] IoCreateDevice failed: 0x%X\n", status);
return status;
}
// 创建符号链接
status = IoCreateSymbolicLink(&symbolicLink, &deviceName);
if (!NT_SUCCESS(status)) {
DbgPrint("[Driver] IoCreateSymbolicLink failed: 0x%X\n", status);
IoDeleteDevice(g_DeviceObject);
return status;
}
// 设置派遣函数
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
DriverObject->DriverUnload = DriverUnload;
DbgPrint("[Driver] Loaded successfully\n");
return STATUS_SUCCESS;
}
用户态通信代码
// Client.cpp
#include <windows.h>
#include <stdio.h>
#define IOCTL_READ_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
int main() {
// 打开设备
HANDLE hDevice = CreateFileA(
"\\\\.\\MyDriverLink",
GENERIC_READ | GENERIC_WRITE,
0, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open device: %d\n", GetLastError());
return 1;
}
printf("[+] Device opened\n");
// 发送IOCTL
DWORD bytesReturned;
char inputBuffer[100] = "Hello from user mode";
char outputBuffer[100] = {0};
BOOL result = DeviceIoControl(
hDevice,
IOCTL_READ_MEMORY,
inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer),
&bytesReturned,
NULL
);
if (result) {
printf("[+] IOCTL succeeded, bytes returned: %d\n", bytesReturned);
} else {
printf("[-] IOCTL failed: %d\n", GetLastError());
}
CloseHandle(hDevice);
return 0;
}
📚 学习资料
✅ 驱动基础检查清单
[ ] 成功搭建开发环境
[ ] 能编译和加载驱动
[ ] 会使用 WinDbg 调试
[ ] 理解设备对象和符号链接
[ ] 能实现用户态和内核态通信
6.2 内核编程进阶 ⭐⭐⭐⭐⭐
🎯 目标:掌握 IRQL、同步、进程操作等核心概念
⏱️ 时长:6-8 周
6.2.1 IRQL(中断请求级别)
学习小点
[ ] IRQL 概念理解
[] PASSIVE_LEVEL (0)
[] APC_LEVEL (1)
[] DISPATCH_LEVEL (2)
[ ] IRQL 规则和限制
[ ] KeRaiseIrql / KeLowerIrql
说明
/*
IRQL(Interrupt Request Level)是Windows内核的核心概念
PASSIVE_LEVEL (0):
- 最低级别,普通代码运行在此级别
- 可以访问分页内存
- 可以等待(Wait)
- 可以调用大多数内核函数
APC_LEVEL (1):
- 异步过程调用级别
- 可以访问分页内存
- 可以等待
DISPATCH_LEVEL (2):
- 线程调度器运行在此级别
- 不能访问分页内存!
- 不能等待!
- 只能使用非分页内存
- 只能使用自旋锁同步
DIRQL (3-26):
- 设备中断级别
HIGH_LEVEL (31):
- 最高级别
*/
// 获取当前IRQL
KIRQL currentIrql = KeGetCurrentIrql();
// 提升IRQL
KIRQL oldIrql;
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
// 执行需要高IRQL的代码...
KeLowerIrql(oldIrql);
// 在DISPATCH_LEVEL的限制
// 1. 不能调用:
// - ZwXxx函数(需要等待)
// - ExAllocatePool(分页池)
// - 访问分页内存
// 2. 只能使用:
// - NonPagedPool内存
// - 自旋锁
📚 学习资料
6.2.2 同步机制
学习小点
[ ] 自旋锁(Spin Lock)
[ ] 互斥体(Mutex)
[ ] 快速互斥体(Fast Mutex)
[ ] 事件(Event)
[ ] 信号量(Semaphore)
代码示例
// ========== 自旋锁(DISPATCH_LEVEL使用)==========
KSPIN_LOCK spinLock;
KIRQL oldIrql;
// 初始化
KeInitializeSpinLock(&spinLock);
// 获取锁(自动提升到DISPATCH_LEVEL)
KeAcquireSpinLock(&spinLock, &oldIrql);
// 临界区代码...
// 释放锁
KeReleaseSpinLock(&spinLock, oldIrql);
// ========== 互斥体(PASSIVE_LEVEL使用)==========
KMUTEX mutex;
// 初始化
KeInitializeMutex(&mutex, 0);
// 获取锁(可以等待)
KeWaitForSingleObject(&mutex, Executive, KernelMode, FALSE, NULL);
// 临界区代码...
// 释放锁
KeReleaseMutex(&mutex, FALSE);
// ========== 快速互斥体 ==========
FAST_MUTEX fastMutex;
ExInitializeFastMutex(&fastMutex);
ExAcquireFastMutex(&fastMutex);
// 临界区...
ExReleaseFastMutex(&fastMutex);
// ========== 事件 ==========
KEVENT event;
// 初始化(同步事件或通知事件)
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
// 等待事件
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
// 设置事件
KeSetEvent(&event, IO_NO_INCREMENT, FALSE);
📚 学习资料
6.2.3 进程与内存操作
学习小点
[ ] PsLookupProcessByProcessId
[ ] KeStackAttachProcess
[ ] MmCopyVirtualMemory
[ ] ZwQuerySystemInformation
[ ] 进程回调注册
代码示例
// ========== 查找进程 ==========
PEPROCESS targetProcess = NULL;
NTSTATUS status = PsLookupProcessByProcessId((HANDLE)targetPid, &targetProcess);
if (NT_SUCCESS(status)) {
// 使用进程对象...
// 必须解引用
ObDereferenceObject(targetProcess);
}
// ========== 附加到进程并访问内存 ==========
KAPC_STATE apcState;
// 附加到目标进程
KeStackAttachProcess(targetProcess, &apcState);
// 现在可以直接访问目标进程的虚拟地址空间
if (MmIsAddressValid(targetAddress)) {
// 读取
ULONG value = *(ULONG*)targetAddress;
// 写入
*(ULONG*)targetAddress = newValue;
}
// 分离
KeUnstackDetachProcess(&apcState);
// ========== 跨进程内存复制 ==========
SIZE_T bytesRead;
status = MmCopyVirtualMemory(
sourceProcess, // 源进程
sourceAddress, // 源地址
targetProcess, // 目标进程
targetAddress, // 目标地址
size, // 大小
KernelMode, // 模式
&bytesRead // 实际复制字节数
);
// ========== 进程创建/退出回调 ==========
VOID ProcessNotifyCallback(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo)
{
if (CreateInfo) {
// 进程创建
DbgPrint("[Callback] Process created: %wZ, PID: %d\n",
CreateInfo->ImageFileName, (ULONG)(ULONG_PTR)ProcessId);
} else {
// 进程退出
DbgPrint("[Callback] Process exited: PID: %d\n",
(ULONG)(ULONG_PTR)ProcessId);
}
}
// 注册回调
PsSetCreateProcessNotifyRoutineEx(ProcessNotifyCallback, FALSE);
// 注销回调
PsSetCreateProcessNotifyRoutineEx(ProcessNotifyCallback, TRUE);
📚 学习资料
✅ 内核编程进阶检查清单
[ ] 完全理解 IRQL 概念
[ ] 会使用各种同步机制
[ ] 能进行跨进程内存操作
[ ] 能注册进程回调
[ ] 理解内核编程的各种限制
6.3 高级保护技术(VT)⭐⭐⭐⭐⭐
🎯 目标:了解 VT 虚拟化和 EPT Hook
⏱️ 时长:8-12 周
6.3.1 VT虚拟化基础
学习小点
[ ] Intel VT-x 概念
[ ] VMX 操作模式(root/non-root)
[ ] VMCS 结构
[ ] VM Entry / VM Exit
[ ] VT 环境搭建
概念说明
Intel VT-x 虚拟化技术:
1. VMX模式
- VMX root模式:Hypervisor运行(Ring -1)
- VMX non-root模式:Guest OS运行
2. VMCS(Virtual Machine Control Structure)
- 控制VM的行为
- Guest状态区
- Host状态区
- VM执行控制区
- VM退出控制区
- VM进入控制区
3. VM Exit
- Guest执行特定指令或事件时退出到Hypervisor
- 如:CPUID, RDMSR, WRMSR, CR访问等
4. EPT(Extended Page Table)
- 物理地址到机器地址的转换
- 可实现内存级别的隐藏Hook
学习资源
推荐学习顺序:
1. 阅读Intel SDM Volume 3C(VMX章节)
2. 学习简单的Hypervisor实现:
- SimpleVisor
- HyperPlatform
- hvpp
3. 理解EPT原理
4. 学习EPT Hook实现
📚 学习资料
✅ VT阶段检查清单
[ ] 理解 VMX 基本概念
[ ] 理解 VMCS 结构
[ ] 理解 VM Exit 机制
[ ] 能阅读简单 Hypervisor 代码
[ ] 了解 EPT 原理
阶段七:图形渲染
⏱️ 总时长:1-2 个月
🎯 目标:使用 ImGui 和 DirectX 制作游戏内置菜单
7.1 ImGui ⭐⭐⭐⭐
🎯 目标:使用 ImGui 创建游戏内置 GUI
⏱️ 时长:2-3 周
7.1.1 ImGui基础
学习小点
[ ] ImGui 项目配置
[ ] ImGui 初始化流程
[ ] 基本控件使用
[ ] 窗口与布局
[ ] 样式自定义
代码示例
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
// 初始化
bool InitImGui(HWND hWnd, ID3D11Device* pDevice, ID3D11DeviceContext* pContext) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// 设置样式
ImGui::StyleColorsDark();
// 初始化后端
ImGui_ImplWin32_Init(hWnd);
ImGui_ImplDX11_Init(pDevice, pContext);
return true;
}
// 渲染
void RenderImGui() {
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
// 绘制菜单
static bool showMenu = true;
if (showMenu) {
ImGui::Begin("Game Menu", &showMenu);
// 基本控件
static bool godMode = false;
ImGui::Checkbox("God Mode", &godMode);
static int health = 100;
ImGui::SliderInt("Health", &health, 0, 100);
static float speed = 1.0f;
ImGui::SliderFloat("Speed", &speed, 0.1f, 10.0f);
if (ImGui::Button("Teleport")) {
// 传送逻辑
}
static char name[64] = "";
ImGui::InputText("Name", name, sizeof(name));
// 分组
if (ImGui::CollapsingHeader("Advanced")) {
static int selectedItem = 0;
const char* items[] = { "Option 1", "Option 2", "Option 3" };
ImGui::Combo("Select", &selectedItem, items, IM_ARRAYSIZE(items));
}
ImGui::End();
}
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
// 清理
void CleanupImGui() {
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
📚 学习资料
7.2 DirectX绘制 ⭐⭐⭐⭐
🎯 目标:Hook DirectX 进行绘制
⏱️ 时长:2-3 周
7.2.1 D3D11 Hook
学习小点
[ ] D3D11 SwapChain 结构
[ ] Hook Present 函数
[ ] 获取设备和上下文
[ ] 集成 ImGui
代码示例
#include <d3d11.h>
#include <dxgi.h>
// 函数类型
typedef HRESULT(__stdcall* Present_t)(IDXGISwapChain*, UINT, UINT);
Present_t oPresent = nullptr;
// 全局变量
ID3D11Device* g_pDevice = nullptr;
ID3D11DeviceContext* g_pContext = nullptr;
ID3D11RenderTargetView* g_pRenderTargetView = nullptr;
bool g_initialized = false;
// Hooked Present
HRESULT __stdcall hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) {
if (!g_initialized) {
// 获取Device和Context
pSwapChain->GetDevice(__uuidof(ID3D11Device), (void**)&g_pDevice);
g_pDevice->GetImmediateContext(&g_pContext);
// 获取后缓冲
ID3D11Texture2D* pBackBuffer = nullptr;
pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer);
g_pDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
pBackBuffer->Release();
// 获取窗口句柄
DXGI_SWAP_CHAIN_DESC desc;
pSwapChain->GetDesc(&desc);
HWND hWnd = desc.OutputWindow;
// 初始化ImGui
InitImGui(hWnd, g_pDevice, g_pContext);
g_initialized = true;
}
// 设置渲染目标
g_pContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL);
// 渲染ImGui
RenderImGui();
return oPresent(pSwapChain, SyncInterval, Flags);
}
// 获取Present地址(通过虚表)
void* GetPresentAddress() {
// 创建临时SwapChain获取虚表
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferCount = 1;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = GetDesktopWindow();
sd.SampleDesc.Count = 1;
sd.Windowed = TRUE;
ID3D11Device* pDevice;
IDXGISwapChain* pSwapChain;
ID3D11DeviceContext* pContext;
D3D11CreateDeviceAndSwapChain(
NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0,
NULL, 0, D3D11_SDK_VERSION,
&sd, &pSwapChain, &pDevice, NULL, &pContext
);
// 获取虚表
void** vtable = *(void***)pSwapChain;
void* presentAddr = vtable[8]; // Present是第9个函数
pSwapChain->Release();
pDevice->Release();
pContext->Release();
return presentAddr;
}
📚 学习资料
✅ 图形渲染检查清单
[ ] 能配置 ImGui 项目
[ ] 会使用 ImGui 各种控件
[ ] 理解 D3D11 基本结构
[ ] 能 Hook Present 函数
[ ] 能制作完整的游戏内置菜单
阶段八:脚本框架
⏱️ 总时长:1-2 个月
🎯 目标:分析游戏中的 Lua 脚本系统
8.1 Lua框架分析 ⭐⭐⭐⭐
🎯 目标:逆向分析游戏 Lua 脚本系统
⏱️ 时长:3-4 周
8.1.1 Lua C API
学习小点
[ ] Lua 栈操作
[ ] Lua 类型系统
[ ] C 调用 Lua
[ ] Lua 调用 C
[ ] 注册自定义函数
代码示例
#include "lua.hpp"
// 创建Lua状态
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// 执行Lua代码
luaL_dostring(L, "print('Hello from Lua')");
// 调用Lua函数
lua_getglobal(L, "myFunction");
lua_pushnumber(L, 10);
lua_pushstring(L, "test");
lua_pcall(L, 2, 1, 0);
int result = lua_tointeger(L, -1);
lua_pop(L, 1);
// 注册C函数给Lua调用
int l_myFunc(lua_State* L) {
int arg1 = luaL_checkinteger(L, 1);
const char* arg2 = luaL_checkstring(L, 2);
// 处理...
lua_pushinteger(L, result);
return 1; // 返回值个数
}
lua_register(L, "myFunc", l_myFunc);
// 获取/设置全局变量
lua_getglobal(L, "playerHealth");
int health = lua_tointeger(L, -1);
lua_pushinteger(L, 999);
lua_setglobal(L, "playerHealth");
8.1.2 Hook Lua函数
学习小点
[ ] 定位 Lua 函数地址
[ ] Hook lua_pcall
[ ] Hook luaL_loadbuffer
[ ] 执行自定义 Lua 代码
代码示例
// Hook luaL_loadbuffer以拦截脚本加载
typedef int (*luaL_loadbuffer_t)(lua_State*, const char*, size_t, const char*);
luaL_loadbuffer_t orig_luaL_loadbuffer = nullptr;
int hooked_luaL_loadbuffer(lua_State* L, const char* buff, size_t sz, const char* name) {
printf("[Lua] Loading script: %s, size: %zu\n", name, sz);
// 可以在这里保存或修改脚本内容
return orig_luaL_loadbuffer(L, buff, sz, name);
}
// Hook lua_pcall以监控函数调用
typedef int (*lua_pcall_t)(lua_State*, int, int, int);
lua_pcall_t orig_lua_pcall = nullptr;
int hooked_lua_pcall(lua_State* L, int nargs, int nresults, int errfunc) {
// 可以在这里打印调用栈等
return orig_lua_pcall(L, nargs, nresults, errfunc);
}
// 执行自定义Lua代码
void ExecuteCustomLua(lua_State* L) {
const char* code = R"(
print("Injected Lua code!")
if player then
player.health = 999
end
)";
luaL_dostring(L, code);
}
📚 学习资料
✅ Lua框架检查清单
[ ] 理解 Lua C API
[ ] 能 Hook Lua 函数
[ ] 能执行自定义 Lua 代码
[ ] 能分析游戏 Lua 脚本
阶段九:游戏引擎逆向
⏱️ 总时长:3-4 个月
🎯 目标:逆向分析 UE 和 Unity 引擎游戏
9.1 UE引擎逆向 ⭐⭐⭐⭐⭐
🎯 目标:逆向分析虚幻引擎游戏
⏱️ 时长:6-8 周
9.1.1 UE核心概念
学习小点
[ ] UObject 系统
[ ] GUObjectArray
[ ] GNames
[ ] Actor 和 Component
[ ] ProcessEvent
关键结构
// UObject基本结构 (UE4/UE5)
class UObject {
void** VTable; // +0x00
int32 ObjectFlags; // +0x08
int32 InternalIndex; // +0x0C
UClass* ClassPrivate; // +0x10
FName NamePrivate; // +0x18
UObject* OuterPrivate; // +0x20
};
// FName结构
struct FName {
int32 ComparisonIndex;
int32 Number;
};
// 从GNames获取名称
const char* GetNameFromFName(FName name) {
// GNames是全局名称表
return GNames->GetEntry(name.ComparisonIndex)->AnsiName;
}
// 遍历所有对象
void DumpObjects() {
for (int i = 0; i < GUObjectArray->NumElements; i++) {
UObject* obj = GUObjectArray->GetObjectPtr(i);
if (obj) {
const char* name = GetNameFromFName(obj->NamePrivate);
const char* className = GetNameFromFName(obj->ClassPrivate->NamePrivate);
printf("%s : %s\n", name, className);
}
}
}
9.1.2 SDK生成
学习小点
[ ] 查找 GNames 特征
[ ] 查找 GUObjectArray 特征
[ ] 遍历对象生成 SDK
[ ] ProcessEvent Hook
代码示例
// 特征搜索示例
// GNames特征 (x64):
// 48 8B 05 ? ? ? ? 48 85 C0 75 50
// GUObjectArray特征:
// 48 8B 05 ? ? ? ? 48 8B 0C C8
uintptr_t FindGNames() {
// 搜索特征码
uintptr_t addr = PatternScan(module, "48 8B 05 ? ? ? ? 48 85 C0 75 50");
if (addr) {
int32_t offset = *(int32_t*)(addr + 3);
return addr + 7 + offset;
}
return 0;
}
// ProcessEvent Hook
typedef void (*ProcessEvent_t)(UObject*, UFunction*, void*);
ProcessEvent_t orig_ProcessEvent = nullptr;
void hooked_ProcessEvent(UObject* obj, UFunction* func, void* params) {
const char* funcName = GetNameFromFName(func->NamePrivate);
printf("[ProcessEvent] %s\n", funcName);
orig_ProcessEvent(obj, func, params);
}
📚 学习资料
9.2 Unity逆向 ⭐⭐⭐⭐⭐
🎯 目标:逆向分析 Unity 引擎游戏
⏱️ 时长:6-8 周
9.2.1 Mono游戏
学习小点
[ ] Mono 运行时
[ ] Assembly-CSharp.dll 分析
[ ] dnSpy 使用
[ ] Mono API Hook
代码示例
// Mono API函数类型
typedef void* (*mono_get_root_domain_t)();
typedef void* (*mono_thread_attach_t)(void*);
typedef void* (*mono_domain_assembly_open_t)(void*, const char*);
typedef void* (*mono_assembly_get_image_t)(void*);
typedef void* (*mono_class_from_name_t)(void*, const char*, const char*);
typedef void* (*mono_class_get_method_from_name_t)(void*, const char*, int);
typedef void* (*mono_runtime_invoke_t)(void*, void*, void**, void**);
// 调用C#方法
void CallCSharpMethod() {
// 获取Mono函数
HMODULE mono = GetModuleHandleA("mono-2.0-bdwgc.dll");
auto mono_get_root_domain = (mono_get_root_domain_t)GetProcAddress(mono, "mono_get_root_domain");
auto mono_thread_attach = (mono_thread_attach_t)GetProcAddress(mono, "mono_thread_attach");
// ... 获取其他函数
// 附加线程
void* domain = mono_get_root_domain();
mono_thread_attach(domain);
// 打开程序集
void* assembly = mono_domain_assembly_open(domain, "Assembly-CSharp");
void* image = mono_assembly_get_image(assembly);
// 获取类和方法
void* klass = mono_class_from_name(image, "", "Player");
void* method = mono_class_get_method_from_name(klass, "SetHealth", 1);
// 调用方法
int health = 999;
void* args[1] = { &health };
mono_runtime_invoke(method, playerInstance, args, NULL);
}
9.2.2 IL2CPP游戏
学习小点
[ ] IL2CPP 原理
[ ] global-metadata.dat
[ ] il2cppdumper 使用
[ ] GameAssembly.dll 分析
流程
IL2CPP逆向流程:
1. 识别IL2CPP游戏
- 存在 GameAssembly.dll
- 存在 global-metadata.dat
2. 使用Il2CppDumper
- 输入: GameAssembly.dll + global-metadata.dat
- 输出: dump.cs (类定义)
- 输出: script.json (IDA脚本)
3. 分析GameAssembly.dll
- 导入Il2CppDumper生成的IDA脚本
- 恢复函数名和类型信息
4. 定位目标函数
- 在dump.cs中搜索目标类/方法
- 在IDA中找到对应地址
5. Hook或修改
- 使用标准Hook技术
📚 学习资料
✅ 游戏引擎逆向检查清单
[ ] 理解 UE UObject 系统
[ ] 能生成 UE SDK
[ ] 能 Hook ProcessEvent
[ ] 能分析 Mono Unity 游戏
[ ] 会使用 dnSpy
[ ] 能分析 IL2CPP Unity 游戏
[ ] 会使用 Il2CppDumper
学习资源汇总
必读书籍
在线资源
常用工具
📌 相关文档:
[[逆向工程完整学习路径]]
[[Windows 驱动开发完整学习路径]]
[[逆向与驱动开发综合学习路径]]