运行时修改内存段权限:如何获取页面基址而非函数地址调整权限?
运行时修改函数所在内存页权限的解决方案
你说得对,内存权限是按页来管理的,不是针对单个函数地址,所以必须先找到函数所在内存页的基地址,才能调整权限。我给你一步步拆解怎么做:
第一步:获取系统内存页大小
不同系统的页大小可能不一样(大多是4KB,但也有大页场景),所以首先要拿到当前系统的页大小:
- Windows下用
GetSystemInfo函数获取 - Linux下用
sysconf(_SC_PAGESIZE)获取
第二步:计算函数所在页的基地址
把函数地址和「页大小-1」的取反值做按位与操作,就能把地址对齐到页的起始位置。举个例子:
Windows 代码片段
SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); DWORD pageSize = sysInfo.dwPageSize; // 把函数地址转成字节指针 PBYTE funcAddr = reinterpret_cast<PBYTE>(&encrypt_function); // 计算页基地址:对齐到页起始 PBYTE pageBase = reinterpret_cast<PBYTE>(reinterpret_cast<ULONG_PTR>(funcAddr) & ~(pageSize - 1));
Linux 代码片段
long pageSize = sysconf(_SC_PAGESIZE); uint8_t* funcAddr = reinterpret_cast<uint8_t*>(&encrypt_function); uint8_t* pageBase = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(funcAddr) & ~(pageSize - 1));
第三步:修改内存页权限并修改代码
拿到页基地址后,就可以调用系统API修改权限,完成代码修改后再恢复权限:
Windows 完整示例
#include <windows.h> #include <iostream> void encrypt_function() { std::cout << "Original function\n"; } int main() { SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); DWORD pageSize = sysInfo.dwPageSize; PBYTE funcAddr = reinterpret_cast<PBYTE>(&encrypt_function); PBYTE pageBase = reinterpret_cast<PBYTE>(reinterpret_cast<ULONG_PTR>(funcAddr) & ~(pageSize - 1)); DWORD oldProtect; // 先把页权限改成可读可写,方便修改代码 if (!VirtualProtect(pageBase, pageSize, PAGE_READWRITE, &oldProtect)) { std::cerr << "VirtualProtect 失败,错误码:" << GetLastError() << std::endl; return 1; } // 这里可以修改encrypt_function的代码,比如替换成NOP指令(仅作演示,实际要根据指令集修改) *funcAddr = 0x90; *(funcAddr + 1) = 0x90; // 改回原来的权限(用旧权限比硬编码更稳妥) if (!VirtualProtect(pageBase, pageSize, oldProtect, &oldProtect)) { std::cerr << "恢复权限失败,错误码:" << GetLastError() << std::endl; return 1; } // 调用修改后的函数 encrypt_function(); return 0; }
Linux 完整示例
#include <unistd.h> #include <sys/mman.h> #include <iostream> void encrypt_function() { std::cout << "Original function\n"; } int main() { long pageSize = sysconf(_SC_PAGESIZE); uint8_t* funcAddr = reinterpret_cast<uint8_t*>(&encrypt_function); uint8_t* pageBase = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(funcAddr) & ~(pageSize - 1)); // 修改页权限为可读可写 if (mprotect(pageBase, pageSize, PROT_READ | PROT_WRITE) == -1) { perror("mprotect 修改权限失败"); return 1; } // 修改函数代码,示例替换为NOP指令 *funcAddr = 0x90; *(funcAddr + 1) = 0x90; // 恢复为可读可执行权限 if (mprotect(pageBase, pageSize, PROT_READ | PROT_EXEC) == -1) { perror("mprotect 恢复权限失败"); return 1; } encrypt_function(); return 0; }
几个要注意的点
- 一定要保证修改的指令是完整的,不要截断指令,否则程序会崩溃
- Windows下建议保存原来的权限并恢复,而不是硬编码新权限,避免破坏页上其他内容的权限设置
- Linux下
mprotect要求地址必须严格页对齐,不然会直接报错 - 如果系统开启了DEP(数据执行保护),恢复权限时必须设置可执行位,否则修改后的代码无法运行
内容的提问来源于stack exchange,提问作者Yoram Abargel
相关产品推荐
相关产品推荐

