首先,将任意虚拟地址转换为物理地址,使用!vtop查看正在转换的进程的dirbase,或者使用!process查找进程的dirbase:
lkd> .process /p fffffa8046a2e5f0
Implicit process is now fffffa80`46a2e5f0
lkd> .context 77fa90000
lkd> !vtop 0 13fe60000
Amd64VtoP: Virt 00000001`3fe60000, pagedir 7`7fa90000
Amd64VtoP: PML4E 7`7fa90000
Amd64VtoP: PDPE 1`c2e83020
Amd64VtoP: PDE 7`84e04ff8
Amd64VtoP: PTE 4`be585300
Amd64VtoP: Mapped phys 6`3efae000
Virtual address 13fe60000 translates to physical address 63efae000.
然后在 PFN 数据库中找到该物理帧(在这种情况下,PML4 的物理页面(cr3 page aka.dirbase)是77fa90,完整的物理地址为77fa90000:
lkd> !pfn 77fa90
PFN 0077FA90 at address FFFFFA80167EFB00
flink FFFFFA8046A2E5F0 blink / share count 00000005 pteaddress FFFFF6FB7DBEDF68
reference count 0001 used entry count 0000 Cached color 0 Priority 0
restore pte 00000080 containing page 77FA90 Active M
Modified
因此地址FFFFF6FB7DBED000 是PML4 页面的虚拟地址,FFFFF6FB7DBEDF68 是PML4E 自引用条目的虚拟地址(1ed*8 = f68)。
FFFFF6FB7DBED000 = 1111111111111111111101101111101101111101101111101101000000000000
1111111111111111 111101101 111101101 111101101 111101101 000000000000
PML4 只能位于 PML4E、PDTPE、PDE 和 PTE 索引相同的虚拟地址,因此实际上存在 2^9 的不同组合,Windows 7 始终选择 0x1ed 即 111101101。这样做的原因是因为 PML4 包含一个指向自身的 PML4,即 PML4 的物理框架,因此它需要在层次结构的每个级别上保持对同一位置的索引。
PML4作为页表页,必须驻留在内核中,内核地址是高规范的,即以1111111111111111为前缀,内核地址以00001开头到11111即从08开始给ff
因此,使用 8TiB 作为用户地址空间的 64 位操作系统可以放置的可能地址范围是 31*(2^4) = 496 个不同的可能位置,而不是实际上 2^9:
1111111111111111 000010000 000010000 000010000 000010000 000000000000
1111111111111111 111111111 111111111 111111111 111111111 000000000000
即第一个是FFFF080402010000,第二个是FFFF088442211000,最后一个是FFFFFFFFFFFFF000。
Note:
在 Windows 10 TH2 之前,Self-Reference PML4 条目的神奇索引是0x1ed,如上所述。但是从 1607 开始的 Windows 10 呢?好吧,微软升级了他们的游戏,作为提高 Windows 安全性的持续战斗,索引在启动时是随机的,所以0x1ed 现在是 512 [原文如此。 (496)] 自引用条目索引可以具有的可能值(即 9 位索引)。还有副作用,它还破坏了一些自己的工具,比如!pte2vaWinDbg 命令。
0xFFFFF68000000000 是第一个页表页中第一个 PTE 的地址,所以基本上是 MmPteBase,除了因为在 Windows 10 1607 上 PML4E 可以是 0x1ed 以外的其他地址,基并不总是 @987654359 @ 作为结果,它使用变量nt!MmPteBase 立即知道页表页面分配的基础从哪里开始。以前,ntoskrnl.exe 中不存在此符号,因为它有一个硬编码的基址 0xFFFFF68000000000。第一个和最后一个页表页的地址是:
first last
* pml4e_offset : 0x1ed 0x1ed
* pdpe_offset : 0x000 0x1ff
* pde_offset : 0x000 0x1ff
* pte_offset : 0x000 0x1ff
* offset : 0x000 0x000
当 PML4E 索引为 0x1ed 时,这为第一个页表页提供 0xFFFFF68000000000,为最后一个页表页提供 0xFFFFF6FFFFFFF000。 PDEs + PDPTEs + PML4Es + PTEs 分配在这个范围内。
因此,为了能够将虚拟地址转换为其 PTE 虚拟地址(!pte2va 与此相反),请将111101101 附加到虚拟地址的开头,然后截断最后 12 位(页面偏移量,不再有用),然后将其乘以 8 个字节(PTE 大小)(即在末尾添加 3 个零,这会从最后一级索引到包含PTE 乘以 PTE 结构的大小)。将 PML4E 索引连接到开头只会导致它循环一次,以便您实际获得 PTE 而不是 PTE 指向的内容。将其连接到开头与将其添加到 MmPteBase 相同。
下面是简单的 C++ 代码:
// pte.cpp
#include<iostream>
#include<string>
int main(int argc, char *argv[]) {
unsigned long long int input = std::stoull(argv[1], nullptr, 16);
long long int ptebase = 0xFFFFF68000000000;
long long int pteaddress = ptebase + ((input >> 12) << 3);
std::cout << "0x" << std::hex << pteaddress;
}
C:\> pte 13fe60000
0xfffff680009ff300
要获得 PDE 虚拟地址,您必须将其附加两次,然后截断最后 21 位,然后乘以 8。这就是 !pte 的工作方式,与 !pte2va 正好相反。
类似地,PDEs + PDPTEs + PML4Es 分配在范围内:
first last
* pml4e_offset : 0x1ed 0x1ed
* pdpe_offset : 0x1ed 0x1ed
* pde_offset : 0x000 0x1ff
* pte_offset : 0x000 0x1ff
* offset : 0x000 0x000
因为当你在页表页面范围内到达0x1ed 的 pdpte 偏移量时,突然之间,你又在 PML4 中循环,所以你得到了 PDE。
如果它说虚拟页面中的地址没有 PTE,对应的物理框架显示为 VMMap 工作集的一部分,那么您可能遇到my issue,您需要使用@ 987654376@ 如果您正在进行实时内核调试(本地或远程),以明确告诉调试器您要在进程上下文中转换用户和内核地址,而不是调试器。