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

运行时修改内存段权限:如何获取页面基址而非函数地址调整权限?

运行时修改函数所在内存页权限的解决方案

你说得对,内存权限是按页来管理的,不是针对单个函数地址,所以必须先找到函数所在内存页的基地址,才能调整权限。我给你一步步拆解怎么做:

第一步:获取系统内存页大小

不同系统的页大小可能不一样(大多是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:28:31