Clairvoyance (/klɛərˈvɔɪəns/;源自法语 clair,意为 clear,以及 voyance,意为 vision),来自 Wikipedia。
clairvoyance 为运行在 Windows 64 位内核上的整个 64 位进程地址空间(用户态和内核态)创建彩色的页保护可视化。
为了将地址空间这一维空间转换为二维可视化,使用了 hilbert 空间填充曲线。上图中的每个彩色像素代表虚拟内存中一个 4KB 页的页保护(UserRead、UserReadWrite 等)。
该地址空间是通过手动解析与进程关联的 四级 页表层次结构直接计算得出的,这些页表来自使用 WindDbg 生成的内核崩溃转储。
最后,程序会输出一个包含元数据的文件,这些元数据既用于在二维画布上显示,也能够计算与某个特定高亮像素对应的虚拟地址。
已编译的二进制文件可在 releases 部分获取。另有一个在线查看器托管在 0vercl0k.github.io/clairvoyance。
特别致谢:
要生成内核崩溃转储,建议使用 WinDbg、KDNet 配合 .dump /f 命令。
获得转储文件后,你可以将其路径以及你感兴趣的页目录物理地址传递给 clairvoyance:
./clairvoyance <dump path> [<page dir pa>]
这将会生成一个扩展名为 clairvoyance 的文件,然后你可以在浏览器中打开 0vercl0k.github.io/clairvoyance 查看它,或者通过检出托管该查看器的 gh-pages 分支来查看。
CI 在 Linux 上使用 clang++-11 构建 clairvoyance,并在 Windows 上使用微软的 Visual studio 2019 构建。
要自行构建,可以使用 build/ 中的脚本:
(base) clairvoyance\build>build-msvc.bat
(base) clairvoyance\build>cmake ..
-- Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.19042.
-- Configuring done
-- Generating done
-- Build files have been written to: clairvoyance/build
(base) clairvoyance\build>cmake --build . --config RelWithDebInfo
Microsoft (R) Build Engine version 16.8.2+25e4d540b for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
clairvoyance.vcxproj -> clairvoyance\build\RelWithDebInfo\clairvoyance.exe
Building Custom Rule clairvoyance/CMakeLists.txt
以下是我在从一台运行 Windows 的 Hyper-V 虚拟机生成的内核崩溃转储中注意到的一些现象:
kd> vertarget
Windows 10 Kernel Version 18362 UP Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Edition build lab: 18362.1.amd64fre.19h1_release.190318-1202
Machine Name:
Kernel base = 0xfffff805`36800000 PsLoadedModuleList = 0xfffff805`36c432f0
Debug session time: Sat Jul 25 10:00:19.637 2020 (UTC - 8:00)
System Uptime: 0 days 0:18:53.609
Windows 似乎并未使用巨型页(1GB),至少在我收集到的所有转储文件中,我都没有看到有使用巨型页的情况。
例如,大型页被大量用于映射某些内核可执行文件,如 Windows 内核 nt:
kd> ? nt
Evaluate expression: -8773703827456 = fffff805`36800000
VA:0xfffff80536800000, PA:0x2400000 (KernelReadWriteExec, Large, PML4E:0xd5745f80, PDPTE:0x42080a0, PDE:0x4209da0, PTE:0x0)
此外,还有大量并非大型页的内核读、写、执行页,这多少有些令人意外。我原本知道内核 / hal 可以使用大型页映射,且这些页是 krwx。原因在于 2MB 如此之大,以至于它同时跨越可执行节和数据节;这意味着该页必须既可写又可执行。
我唯一能找到的公开提及是在这篇 博客文章 中(感谢 `Ivan):
我联系了微软,微软声称这是有意为之,因为“在某些情况下,内核会以大型页映射”,并且可以通过启用基于虚拟化的保护(VBS)来阻止这种情况。
大量内核内存区域被映射到同一个物理页(由零填充):
VA:0xffffc27ef4401000, PA:0x4200000 (KernelRead, Normal, ...)
VA:0xffffc27ef4402000, PA:0x4200000 (KernelRead, Normal, ...)
VA:0xffffc27ef4403000, PA:0x4200000 (KernelRead, Normal, ...)
...
VA:0xffffc27ef63fb000, PA:0x4200000 (KernelRead, Normal, ...)
VA:0xffffc27ef63fc000, PA:0x4200000 (KernelRead, Normal, ...)
VA:0xffffc27ef63fd000, PA:0x4200000 (KernelRead, Normal, ...)
VA:0xffffc27ef63fe000, PA:0x4200000 (KernelRead, Normal, ...)
VA:0xffffc27ef63ff000, PA:0x4200000 (KernelRead, Normal, ...)
下面是一个较小的区域(该区域并非完全连续,存在一些空洞):
VA:0xffffc27ed2201000, PA:0x4300000 (KernelRead, Normal, ...)
VA:0xffffc27ed2202000, PA:0x4300000 (KernelRead, Normal, ...)
VA:0xffffc27ed2203000, PA:0x4300000 (KernelRead, Normal, ...)
...
VA:0xffffc27ed25fc000, PA:0x4300000 (KernelRead, Normal, ...)
VA:0xffffc27ed25fd000, PA:0x4300000 (KernelRead, Normal, ...)
VA:0xffffc27ed25fe000, PA:0x4300000 (KernelRead, Normal, ...)
VA:0xffffc27ed25ff000, PA:0x4300000 (KernelRead, Normal, ...)
这一节展示一些在地址空间某些区域中可以看到的很酷的图案。
页堆分配及其守护页看起来非常酷,而且很容易识别:
内核栈由于其大小和守护页而具有很好辨识的形状:
在我看过的转储文件中,内核中的系统缓存区域看起来像一片星云:
Axel '0vercl0k' Souchet