如何修改内存中加载的PE文件.rdata节访问权限?代码异常求解
问题根源与解决方案
核心问题分析
你的代码和输出异常,本质是编译器优化、PE只读节的写时复制(COW)机制,以及字符串字面量的只读属性三者共同作用的结果:
1. 字符串字面量的编译器优化
你定义的"\x01\x02\x03\x04\x05"是字符串字面量,属于C标准中的只读常量,编译器会将其放入PE的.rdata节。现代编译器(如MSVC)会对这类常量的访问做激进优化:
- 第一次修改后的
printf未被完全优化,读取了内存中写时复制生成的私有副本值,所以输出正确; - 第二次及循环内的
printf被编译器优化为直接输出原始常量值,完全跳过内存读取,导致你看到的输出和实际内存值不符。
2. 写时复制(COW)机制的陷阱
当你通过VirtualProtect修改只读页权限并写入时,Windows会触发页错误,为该页创建私有可写副本,你的修改会被应用到副本上。但如果编译器优化了输出逻辑,直接使用原始常量,你就看不到修改后的结果。此外,编译器可能会让指针重新指向原始只读页,导致之前的修改看起来被“重置”。
3. 指针类型的违规
用unsigned char*指向字符串字面量违反C标准(字符串字面量应为const unsigned char*),这会触发编译器的异常优化行为。
解决方案
要真正修改PE的.rdata节并验证结果,需避开编译器优化,正确定位目标内存区域:
步骤1:规避编译器优化
使用volatile修饰指针,强制编译器每次从内存读取值,禁止常量替换优化:
volatile unsigned char* data = (volatile unsigned char*)"\x01\x02\x03\x04\x05";
或直接在项目设置中关闭优化(MSVC中设置优化为禁用(/Od))。
步骤2:正确定位.rdata节内存地址
若要直接操作PE的.rdata节,应通过PE头遍历节表定位,而非依赖字符串字面量。示例代码如下:
#include <windows.h> #include <stdio.h> #include <imagehlp.h> #pragma comment(lib, "imagehlp.lib") int main() { // 获取当前模块基地址 HMODULE hModule = GetModuleHandleA(NULL); PIMAGE_NT_HEADERS pNtHeaders = ImageNtHeader(hModule); // 遍历节表查找.rdata节 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders); for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++, pSection++) { if (strcmp((char*)pSection->Name, ".rdata") == 0) { // 计算.rdata节的内存起始地址和大小 LPVOID rdataStart = (LPVOID)((uintptr_t)hModule + pSection->VirtualAddress); SIZE_T rdataSize = pSection->Misc.VirtualSize; // 修改内存权限为可读写执行 DWORD oldProtect; if (!VirtualProtect(rdataStart, rdataSize, PAGE_EXECUTE_READWRITE, &oldProtect)) { printf("VirtualProtect failed: %lu\n", GetLastError()); return 1; } // 使用预先定义在.rdata中的常量(需确保该常量存在于.rdata节) const unsigned char customData[] = "\x01\x02\x03\x04\x05"; volatile unsigned char* data = (volatile unsigned char*)customData; // 验证修改结果 printf("Before: %02X %02X %02X %02X %02X\n", data[0], data[1], data[2], data[3], data[4]); data[4] = 0x17; printf("After modify data[4]: %02X %02X %02X %02X %02X\n", data[0], data[1], data[2], data[3], data[4]); data[2] = 0x43; printf("After modify data[2]: %02X %02X %02X %02X %02X\n", data[0], data[1], data[2], data[3], data[4]); // 循环验证修改 while (1) { data[4] = 0x17; printf("Loop: %02X %02X %02X %02X %02X\n", data[0], data[1], data[2], data[3], data[4]); Sleep(1000); } // 恢复原始内存权限(可选,建议执行) VirtualProtect(rdataStart, rdataSize, oldProtect, &oldProtect); break; } } return 0; }
关键注意事项
- 不要随意修改
.rdata节中的未知系统/编译器生成数据,否则会导致程序崩溃; - 修改权限后建议恢复原始权限,保证程序稳定性;
- 部分场景下修改内存权限需要管理员权限。
对你现有异常的解释
- 循环前两次修改看似有效:第一次修改后编译器未完全优化,读取了COW副本的内存值;第二次修改后编译器启动优化,直接输出原始常量,导致data[4]看似被重置;
- 循环内修改无效:循环内的
printf被完全优化为输出原始常量,实际内存中的COW副本已被修改,但无法通过输出体现; GetLastError返回0:VirtualProtect调用确实成功,问题出在编译器优化而非系统调用失败。
内容的提问来源于stack exchange,提问作者user16196636
相关产品推荐
相关产品推荐

