虚拟与物理内存地址转换

PML4与转换

x86虚拟内存地址与物理内存地址通过分页机制实现,其转换流程如下:

  1. Page Map Level 4

  2. Page Directory Pointers

  3. Page Directories

  4. Page Tables

  5. 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和内核入口的内存地址。

image-cgvo.png

  • 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寄存器

参考文章:

Comment