直接写入函数代码会触发访问违规,因为代码段默认只读,须用VirtualProtect临时设为PAGE_EXECUTE_READWRITE并FlushInstructionCache,修改后立即恢复原权限。

为什么直接写入函数代码会触发访问违规?
因为现代操作系统默认将代码段所在的内存页设为只读(PAGE_EXECUTE_READ),尝试用 memcpy 或指针赋值覆盖指令时,会触发 EXCEPTION_ACCESS_VIOLATION。必须先调用 VirtualProtect 临时改写权限,否则任何修改都会崩溃。
调用 VirtualProtect 修改权限的最小必要步骤
关键不是“改成可执行”,而是“在修改前后精准切换权限”,避免留下安全隐患或被杀软拦截:
- 用
VirtualQuery获取目标地址所在内存页的起始地址(lpAddress参数必须是对齐到页边界的地址,通常是(uintptr_t)func & ~(0x1000 - 1)) - 调用
VirtualProtect将该页设为PAGE_EXECUTE_READWRITE(不能只用PAGE_READWRITE,否则后续跳转会失败) - 保存原始权限(
DWORD oldProtect),用于还原 - 修改完指令后,**必须立即**用
VirtualProtect恢复原始权限(哪怕只延迟几毫秒,也可能被 EDR 检测到异常写入)
常见错误:PAGE_EXECUTE_WRITECOPY 和缓存一致性问题
有人误用 PAGE_EXECUTE_WRITECOPY,以为它更安全——其实它在 x86/x64 上几乎等价于 PAGE_EXECUTE_READ,且某些 Windows 版本下根本不会真正启用写时复制,导致修改无效。更隐蔽的问题是 CPU 指令缓存(i-cache)和数据缓存(d-cache)不同步:
- 修改完内存后,必须调用
FlushInstructionCache(GetCurrentProcess(), pageAddr, pageSize),否则 CPU 可能继续执行旧指令 -
FlushInstructionCache不是可选优化,是强制要求;漏掉它会导致 Hook 表现不稳定(有时生效、有时不生效) - 不要依赖
__builtin___clear_cache或_mm_sfence,它们不保证指令缓存刷新
64 位下要注意的地址对齐与跳转偏移计算
在 x64 中,直接覆写函数开头几个字节为 jmp rel32 时,目标地址可能超出 ±2GB 范围,导致 0xE9 指令无法编码。此时不能硬写,得用三步法:
立即学习“C++免费学习笔记(深入)”;
- 分配一块可执行内存(
VirtualAlloc(..., PAGE_EXECUTE_READWRITE)) - 写入完整的跳转 stub(例如
mov rax, imm64; jmp rax) - 把原函数头改为
jmp [rip + offset]到该 stub,再用VirtualProtect改权限 +FlushInstructionCache
注意:VirtualProtect 的 dwSize 参数传的是页大小(通常 0x1000),不是指令长度;传小了会导致部分内存仍受保护,传大了可能意外改写相邻页权限。


















