ReadProcessMemory无法读取特定AllocationProtect值页面的原因排查
内存扫描器中ReadProcessMemory无法读取Protect值为1或260的页面问题解析
问题概述
开发内存扫描器时,ReadProcessMemory()可读取进程90%的内存页,但当mbi.Protect值为1或260时读取失败,返回ERROR 299(ERROR_PARTIAL_COPY)且BytesRead为0。已通过管理员权限运行程序、设置调试权限,并以VM_READ权限打开进程,但仍无法读取这些特定页面。
原因分析
这种无法读取的情况是正常现象,属于Windows内存保护机制的限制,并非操作有误:
- Protect值1(
PAGE_NOACCESS):
该属性标记的页面完全不可访问,系统会阻止任何读写操作,即使拥有调试权限也无法绕过,是内存安全防护的核心机制之一。 - Protect值260(
PAGE_GUARD | PAGE_READWRITE):
十进制260对应十六进制0x104,是PAGE_GUARD(一次性保护标记)与PAGE_READWRITE的组合。PAGE_GUARD页面首次被访问时会触发目标进程的异常,系统会自动移除该标记,但跨进程读取时无法处理目标进程的异常,因此ReadProcessMemory直接返回失败。
代码优化建议
针对现有代码,可通过以下调整避免无效读取、提升稳定性:
- 提前过滤不可读页面
在读取前判断保护属性,直接跳过无法读取的页面:# 定义保护属性常量(可从依赖代码中获取或自行定义) PAGE_NOACCESS = 0x1 PAGE_GUARD = 0x100 if mbi.State == MEM_COMMIT: # 跳过不可访问或带GUARD标记的页面 if (mbi.Protect & PAGE_NOACCESS) != 0 or (mbi.Protect & PAGE_GUARD) != 0: Adress += mbi.RegionSize continue # 后续读取逻辑... - 修复内存分配异常处理
原代码中create_string_buffer分配大内存页失败时仅用pass跳过,会导致后续读取使用未初始化缓冲区,建议捕获异常后直接跳过该页:try: ContentsBuffer = create_string_buffer(mbi.RegionSize) except MemoryError: print(f"无法分配缓冲区,页大小: {mbi.RegionSize}") Adress += mbi.RegionSize continue - 完善句柄与错误处理
修复setDebugPriv函数中的句柄泄漏和错误调用问题:def setDebugPriv(): token_handle = wintypes.HANDLE() if not OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, byref(token_handle), ): print("Error:", kernel32.GetLastError()) return False try: luidvalue = win32security.LookupPrivilegeValue(None, win32security.SE_DEBUG_NAME) se_debug_name_value = LUID(luidvalue) LAA = LUID_AND_ATTRIBUTES(se_debug_name_value, SE_PRIVILEGE_ENABLED) tkp = TOKEN_PRIVILEGES(1, LAA) if not AdjustTokenPrivileges( token_handle, False, byref(tkp), sizeof(tkp), None, None, ): print("Error:", kernel32.GetLastError()) return False finally: CloseHandle(token_handle) # 确保句柄被关闭,避免泄漏 return True
总结
无法读取这两类页面是Windows内存保护的正常表现,无需强行尝试读取。通过提前过滤不可读属性的页面,可有效提升扫描器的运行效率和稳定性。
内容的提问来源于stack exchange,提问作者João P 2018
相关产品推荐
相关产品推荐

