You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改内存中加载的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 18:35:00