Windows 10 x64:无法在 Windbg 上获得 PXE

Windows 10 x64: Unable to get PXE on Windbg

无法理解 Windows 内存管理器的工作原理。 我查看附加的用户进程 (dbgview.exe)。 它是 WOW64 进程。在指定的地址 (0x76560000) 有 kernel32.dll 模块(也是 WOW64)的 .text 部分。 为什么进程页table中没有PTE和其他table指向那些虚拟地址?

kd> db 76560000
00000000`76560000  8b ff 55 8b ec 51 56 57-33 f6 89 55 fc 56 68 80  ..U..QVW3..U.Vh.
<...>

kd> !pte 76560000
                                           VA 0000000076560000
PXE at FFFFF6FB7DBED000    PPE at FFFFF6FB7DA00008    PDE at FFFFF6FB40001D90    PTE at FFFFF680003B2B00
Unable to get PXE FFFFF6FB7DBED000

kd> db FFFFF680003B2B00 
fffff680`003b2b00  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????
<...>

我知道页面将在第一次访问(有页面错误)发生后分配,但为什么也没有原型 PTE?

我发现自 Windows 10 周年更新(1607、10.0.14393)以来,PML4 table 已被随机化以减轻内核堆喷射。 这意味着页面 Table 可能 而不是 位于 0xFFFFF6800000.

首先,使用!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 页面又名。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 始终选择0x1ed111101101。这样做的原因是因为 PML4 包含一个指向自身的 PML4,即 PML4 的物理框架,因此它需要在层次结构的每个级别保持对同一位置的索引。

PML4,作为一个页面table页面,必须驻留在内核中,内核地址是高规范的,即前缀为1111111111111111,内核地址以[=33=开头] 到 11111 即从 08ff

使用 8TiB 作为用户地址 space 的 64 位 OS 可以放置的可能地址范围因此是 31*(2^4) = 496 个不同的可能位置,而不是实际上 2^9:

1111111111111111 000010000 000010000 000010000 000010000 000000000000

1111111111111111 111111111 111111111 111111111 111111111 000000000000

即第一个是 FFFF080402010000,第二个是 FFFF088442211000,最后一个是 FFFFFFFFFFFFF000

Note:

Up until Windows 10 TH2, the magic index for the Self-Reference PML4 entry was 0x1ed as mentioned above. But what about Windows 10 from 1607? Well Microsoft uped their game, as a constant battle for improving Windows security the index is randomized at boot-time, so 0x1ed is now one of the 512 [sic. (496)] possible values (i.e. 9-bit index) that the Self-Reference entry index can have. And side effect, it also broke some of their own tools, like the !pte2va WinDbg command.

0xFFFFF68000000000是第一页table页中第一个PTE的地址,所以基本上MmPteBase,除了因为在Windows10 1607上PML4E可以不同于 0x1ed,因此基数并不总是 0xFFFFF68000000000,它使用变量 nt!MmPteBase 立即知道页面 table 页面分配的基数从哪里开始.以前,这个符号在 ntoskrnl.exe 中不存在,因为它有一个硬编码的基数 0xFFFFF68000000000。第一页和最后一页的地址 table 页将是:

                        first   last
 *   pml4e_offset     : 0x1ed   0x1ed
 *   pdpe_offset      : 0x000   0x1ff
 *   pde_offset       : 0x000   0x1ff
 *   pte_offset       : 0x000   0x1ff
 *   offset           : 0x000   0x000

当 PML4E 索引为 0x1ed 时,第一页为 0xFFFFF68000000000,最后一页为 table 页为 0xFFFFF6FFFFFFF000。 PDE + PDPTE + PML4E + PTE 分配在此范围内。

因此,为了能够将虚拟地址转换为它的 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 应该工作的方式,与 [= 相反46=].

类似地,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 页面 table 页面范围内的 pdpte 偏移量时,突然之间,您再次在 PML4 中循环,所以您得到了 PDE .

如果它说虚拟页面中的地址没有 PTE,而相应的物理框架被 VMMap 显示为工作集的一部分,那么您可能会遇到 my issue,在这里您如果您正在进行实时内核调试(本地或远程),需要使用 .process /P 来明确告诉调试器您要在进程上下文中而不是调试器中转换用户和内核地址。