AOB扫描本质是内存字节匹配,需解析特征码为字节数组与掩码数组,遍历目标进程内存块并逐字节带掩码比对,注意通配符处理、页对齐、ASLR及反作弊干扰。

AOB扫描本质是内存字节匹配,不是字符串搜索
特征码(AOB)搜的是机器码字节序列,比如 \x48\x89\x5C\x24\x08 这种原始指令编码,不是函数名或符号。C++ 里没现成的 aob_scan 函数,得自己写字节比对逻辑,或者用第三方库封装好的接口。
常见错误是把特征码当正则写,比如加 * 或 ?? 后直接 memcmp —— 这不行,通配符必须手动解析并跳过比对。
- 通配符
??表示任意单字节,比对前要先解析特征码字符串,拆成字节数组 + 掩码数组 - Windows 上读目标进程内存要用
ReadProcessMemory,且目标进程需有PROCESS_VM_READ权限 - 64 位进程搜 64 位特征码时,注意 RIP 相对寻址、地址截断等导致的偏移错位问题
手写 AOBSerach 函数的关键三步
核心就是:解析特征码 → 遍历内存块 → 字节级带掩码比对。别依赖 std::string::find,它只处理字符,不处理二进制通配。
一个轻量可用的实现要点:
立即学习“C++免费学习笔记(深入)”;
- 用
std::vector<uint8_t></uint8_t>存解析后的 pattern,std::vector<bool></bool>存对应位置是否为通配 - 遍历内存时,每次取
pattern.size()字节,逐字节检查:若掩码为 true 则跳过;否则比较是否相等 - 起始地址必须按页对齐(
0x1000),且避开不可读区域,建议先用VirtualQueryEx过滤有效内存块
示例片段(关键逻辑):
for (size_t i = 0; i <= regionSize - pattern.size(); ++i) {
bool match = true;
for (size_t j = 0; j < pattern.size(); ++j) {
if (mask[j]) continue; // ?? 通配,跳过
if (buffer[i + j] != pattern[j]) {
match = false;
break;
}
}
if (match) return baseAddr + i;
}用 ScanPattern 库(如 sigscan)省事但要注意兼容性
像 sigscan 或 minhook 附带的扫描器,底层仍是上面那套逻辑,但封装了通配解析和内存遍历。直接调 FindPattern 很方便,但容易忽略两点:
- 特征码字符串格式必须严格,比如
"48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20",空格分隔,??大写,不能混用* - 返回地址是目标进程中的 VA,如果在 DLL 里调用,需确认当前模块是否加载到相同基址(ASLR 可能导致偏移漂移)
- 某些游戏反作弊会 hook
ReadProcessMemory并返回假数据,此时任何用户态扫描都会失败
64 位下相对跳转指令的特征码经常失效
比如 call、jmp 后面跟的 4 字节相对偏移,在不同加载基址下每次都不一样。硬编码这种指令的完整字节序列,十次有九次找不到。
正确做法是把可变部分标为通配:
- 原指令:
E8 12 34 56 78→ 特征码写成"E8 ?? ?? ?? ??" - 更稳妥的是找其前后稳定的“锚点”指令,比如前面的
push rbp(55)或后面紧跟的mov rax, ...(固定操作码) - 不要只扫一页内存,至少覆盖整个模块的
.text段,大小可用GetModuleInformation获取
真正难的从来不是怎么写循环比对,而是怎么选一组在所有版本、所有 ASLR 偏移下都稳定存在的字节锚点。



















