PML4与转换
x86虚拟内存地址与物理内存地址通过分页机制实现,其转换流程如下:
Page Map Level 4
Page Directory Pointers
Page Directories
Page Tables
Page Table Entries

例如地址虚拟内存地址0x7FFFB442000
Page-Map Level-4 Table (PML4) (Bits 47-39)
Page-Directory-Pointer Table (PDPT) (Bits 38-30)
Page-Directory Table (PDT) (Bits 29-21)
Page Table (PT) (Bits 20-12)
Offset(Bit 0-11)
二进制如下0111 1111 1111 1111 1011 0100 0100 0010 0000 0000 0000
PML4: 011111111(0x1ff)
PDP: 111111111(0x1ff)
PD: 011010001(0xd1)
PT: 000010000(0x10)
PO: 000000000000(0x0)
代码实现如下
UINT64 po = address & ~(~0ul << 12);
UINT64 pt = ((address >> 12) & (0x1ffll));
UINT64 pd = ((address >> 21) & (0x1ffll));
UINT64 pdp = ((address >> 30) & (0x1ffll));
UINT64 pml4 = ((address >> 39) & (0x1ffll));Windows NTOS Kernel PML4物理内存地址获取
在Windows操作系统下,lowstub一般位于小于0x100000的物理内存地址中,其中存储着NTOS kernel PML4指针和内核入口地址,存储这些信息的页的起始部分会有一个如下所示的JMP指令,一般通过该指令序列定位并读取PML4和内核入口的内存地址。


PML4的偏移为0xa0
内核入口的偏移为0x70
LowStub内存扫描UEFI代码实现方法如下:
STATIC BOOLEAN CheckLow(UINT64 *pml4, UINT64 *kernelEntry)
{
UINT64 o = 0;
while (o < 0x100000)
{
o += 0x1000;
// Check if address is okay
if (IsAddressValid(o) == TRUE)
{
if (0x00000001000600E9 != (0xffffffffffff00ff & *(UINT64 *)(VOID *)(o + 0x000)))
{
continue;
} // START
if (0xfffff80000000000 != (0xfffff80000000003 & *(UINT64 *)(VOID *)(o + 0x070)))
{
continue;
} // KERNEL
if (0xffffff0000000fff & *(UINT64 *)(VOID *)(o + 0x0a0))
{
continue;
} // PML4
*pml4 = *(UINT64 *)(VOID *)(o + 0xa0);
*kernelEntry = *(UINT64 *)(VOID *)(o + 0x70);
return TRUE;
}
}
return FALSE;
}进程PML4地址获取方法
在操作系统中拥有内核权限下也可以通过读取CR3寄存器获取PML4的地址。
proc
mov rax, cr3
ret
endp若在UEFI SMM模式下,可通过SmmCpu->ReadSaveState读取CR3寄存器