用 std::search 进行二进制特征码匹配更安全高效,需校验内存范围、长度一致、权限修改及指令缓存刷新,避免手写 kmp 和字符串函数误用。

用 std::search 做内存内二进制特征码匹配,别手写 KMP
直接用标准库比自己撸字符串匹配更稳——std::search 支持任意迭代器,天然适配 std::vector<uint8_t></uint8_t> 或裸指针内存块,且底层通常有优化(如 x86 上可能用 memmem 或 SIMD)。手写 KMP 容易在边界(比如模式长度为 0 或 1)出错,还难调试。
常见错误现象:std::search 返回 end() 却误判为“找到了”,或传入的 last 迭代器越界导致未定义行为。
- 确保搜索范围是合法可读内存:若从文件映射或
mmap得到指针,先检查size是否足够容纳特征码 - 特征码用
std::vector<uint8_t></uint8_t>存,别用std::string—— 中间可能含\x00,std::string::c_str()会截断 - 匹配失败时返回值等于
last,不是nullptr;判断用it != last,不是it != nullptr
替换前必须验证目标地址可写,否则 segfault 是秒级的
Windows 下要用 VirtualProtect,Linux/macOS 用 mprotect,改页权限不是可选步骤,是硬性前提。直接对只读页 memcpy 或赋值,程序立刻崩,没第二次机会。
使用场景:打补丁、热修复、逆向调试时 patch 某个函数入口。不能假设当前进程对自己代码段有写权限——现代系统默认启用 W^X(Write XOR Execute)。
- 调用
mprotect前,地址需按页对齐(addr & ~(getpagesize()-1)),长度向上取整到页大小 - Windows 上
VirtualProtect的flNewProtect至少要加PAGE_READWRITE,别漏掉PAGE_EXECUTE(如果原页可执行) - 替换完立即调用
FlushInstructionCache(Windows)或__builtin___clear_cache(GCC/Clang),否则 CPU 可能执行旧指令
std::memcpy 替换字节时,源和目标长度必须严格相等
特征码长度和 patch 数据长度不一致是高频翻车点。比如想用 \x90\x90\x90(3 字节 NOP)覆盖一个 call 指令(5 字节),不补全就 memcpy,后面 2 字节代码就被干掉了——程序跳转错乱,不是崩溃而是静默行为异常。
参数差异:特征码可能是 4 字节 \x83\xEC\x10\xC3(sub rsp, 16; ret),而你想 patch 成 \x90\x90\x90\x90(4 字节 NOP),这时才安全;若 patch 数据只有 3 字节,必须显式补 1 字节(比如再加个 \x90)。
- 把 patch 数据也存成
std::vector<uint8_t></uint8_t>,用.size()和特征码长度比对,不等就拒绝操作 - 别用
strcpy或strncpy处理二进制数据——它们按\x00截断,且不保证长度可控 - 如果必须“伸缩”替换(比如用短指令替代长指令),得自己挪动后续代码,这已超出简易工具范畴,容易引入重定位错误
跨平台时路径、内存映射、页大小处理最容易漏掉条件编译
同一个工具在 Linux 和 Windows 上跑,mmap vs CreateFileMapping、getpagesize() vs GetSystemInfo()、甚至字节序(虽然特征码通常是小端指令,但如果你搜的是数据结构字段就得小心)——这些不包裹好,编译都过不去。
性能影响:Windows 的 CreateFileMapping + MapViewOfFile 开销略高于 Linux mmap,但差别在微秒级;真正拖慢的是反复打开/映射大文件。建议一次映射,多次搜索+替换。
- 用
#ifdef _WIN32/#else分隔内存映射逻辑,别试图写“通用封装”,容易藏 bug - 页大小不要硬编码
4096:Linux 可能是huge page,Windows 在某些环境下页是2MB,务必调系统 API 获取 - 文件读取推荐用
std::ifstream配合std::ios::binary,别用 C 风格fopen—— C++ 流在 RAII 下更不容易泄露句柄
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











