开门见山,本文的核心思路就是通过填充页表项,将一块连续的虚拟地址映射到新的地址,同时将需要修改的只读内存对应页表项的Dirty位置位。在Windows操作系统下,写保护是通过保护特定虚拟地址实现的,若不建立新映射,则即使将Dirty位置位,尝试写只读内存照样会触发BugCheck,若建立了新映射但不置位Dirty则触发PAGE_FAULT的BugCheck,两个步骤缺一不可。
填充页表项首先需要动态定位PTEBase,国际惯例是采用大表哥的页表自映射法,代码如下:ULONG_PTR PTEBase = 0;
BOOLEAN hzqstGetPTEBase()
{
BOOLEAN Result = FALSE;
ULONG_PTR PXEPA = __readcr3() & 0xFFFFFFFFF000;
PHYSICAL_ADDRESS PXEPAParam;
PXEPAParam.QuadPart = (LONGLONG)PXEPA;
ULONG_PTR PXEVA = (ULONG_PTR)MmGetVirtualForPhysical(PXEPAParam);
if (PXEVA)
{
ULONG_PTR PXEOffset = 0;
do
{
if ((*(PULONGLONG)(PXEVA + PXEOffset) & 0xFFFFFFFFF000) == PXEPA)
{
PTEBase = (PXEOffset + 0xFFFF000) <
Result = TRUE;
break;
}
PXEOffset += 8;
} while (PXEOffset
}
return Result;
}
这里我顺便也给出另一种获取方案,基本思路是通过NT导出函数NTKERNELAPI ULONG NTAPI KeCapturePersistentThreadState(PCONTEXT Context, PKTHREAD Thread, ULONG BugCheckCode, ULONG_PTR BugCheckParameter1, ULONG_PTR BugCheckParameter2, ULONG_PTR BugCheckParameter3, ULONG_PTR BugCheckParameter4, PVOID VirtualAddress);
Dump下0x40000大小的数据,里面就存放有MmPfnDataBase,MmPfnDataBase是一个以物理地址页帧号为索引的内存信息数组,其中就有物理页对应的PTE项的地址PteAddress,我们传一个有效的物理地址进去(如当前CR3: __readcr3() & 0xFFFFFFFFF000)取出其中的PteAddress,由于PTEBase必定是0x8000000000的倍数,因此可由PteAddress直接算出PTEBase。另外,从Win10 RS1周年预览版开始,KeCapturePersistentThreadState开始受全局变量ForceDumpDisabled的控制,若注册表“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CrashControl”中有关子项满足条件,此变量会在开机启动时置为1,导致KeCapturePersistentThreadState调用失败。综上所述我们得到第二种获取PTEBase的代码如下://若在Win10上测试,需要在这里加上"#define _WIN10 1"
#ifdef _WIN10
#define OFFSET_PTEADDRESS 0x8
#elif
#define OFFSET_PTEADDRESS 0x10
#endif
ULONG_PTR PTEBase = 0;
PBOOLEAN LocateForceDumpDisabledInRange(ULONG_PTR StartAddress, ULONG MaximumBytesToSearch)
{
PBOOLEAN Result = 0;
ULONG_PTR p = StartAddress;
ULONG_PTR pEnd = p + MaximumBytesToSearch;
do
{
//cmp cs:ForceDumpDisabled, al
//jnz ...
if ((*(PULONGLONG)p & 0xFFFF00000000FFFF) == 0x850F000000000538)
{
Result = p + 6 + *(PLONG)(p + 2);
break;
}
p++;
} while (p
return Result;
}
BOOLEAN GetPTEBase()
{
BOOLEAN Result = FALSE;
CONTEXT Context = { 0 };
Context.Rcx = (ULONG64)&Context;
<