-
-
[原创]物理地址、线性地址、逻辑地址之我见
-
2021-6-26 17:31 9953
-
前言
闲来无事,翻看收藏夹的连接,看到了描述这些地址的文章,看完之后总觉得缺少些什么。思之片刻有感,文章中没有给出简单的证实方法。随后百度之……论坛之……,然而,并未找到想要的结果,反而,产生了新的疑问。在众多文章中对于这些地址的概念描述差异不大,但是,对于范围大小的描述却各有千秋。这在……让我懵逼的同时,也牵动了我较真的神经!因此,本文试图解决以下几个问题:
- 如何简单的证实这些地址,或者在一些触手可及的工具上找到些许线索证实。
- 明确这些地址的范围。
- 理解这些地址的基本作用。
本节虽名为前言,实则是发帖时才写的。下面的内容原本应为笔记,但独享不如分享!如有谬误,还望海涵,且请!多多指正。
文中内容以 Intel CPU 和 Windows 系统为例。
基本概念
Physical address: CPU 可访问的地址空间(如 8086 可访问 20 Bits 地址空间)。物理空间的范围为 36 ≤ MAXPHYADDR ≤ 52 Bits(64 GBytes ~ 4 PBytes),MAXPHYADDR 的值由 CPUID.80000008H:EAX[7:0] 查询。(Pentium Pro 之前皆为 32 Bits)。
Linear address: 有时也称 Virtual address。Linear address 是 Physical address 与 Logical address 的中间层,或称隔离层(保护模式隔离用户和资源……个人理解)。空间的范围为 32/48 Bits(4 GBytes/256 TBytes),具体由 CPUID.80000008H:EAX[15:8] 查询(如果 CPUID.80000001H:EDX.LM [29] = 1 则为 48 Bits,否则为 32 Bits。不支持此查询的 CPU 皆为 32 Bits)。
Logical address: 程序代码中使用的地址,在不同的编程环境中略有差异,实则已被削弱(见后文)。
1 2 3 4 5 | mov eax, 0x80000008 cpuid ; Intel I7 6850K : ; Linear address space : ah = 48 Bits(支持 256 TBytes) ; Phyiscal address space: al = 46 Bits(支持 64 TBytes) = MAXPHYADDR |
在 Intel 的文档中,同为 6 系列的 CPU 中 MAXPHYADDR 值不同。
物理地址
物理地址中映射的是什么?答案是各种外部设备。一些地方将物理地址称为内存地址,个人认为亦对亦错,对的原因是 CPU 操作内存最为快捷(似乎也只能操作内存),而物理地址是为了让 CPU 将各种设备都视为内存。错的原因是物理地址中可以映射内存(条),但不局限于此!Intel 的 CPU 有两种物理空间:
Memory: 前面描述的大小为 MAXPHYADDR 的物理空间,这个空间中映射了各种外部设备:显卡、声卡、网卡、SATA、USB、APIC 等等。
I/O Ports: 独立编址的空间,仅有 64 KBytes(0 ~ 0xFFFF),此空间映射的也是各种外部设备。可通过 in/out 指令访问。0 ~ 255 的端口号使用立即数表示,超过 255 的端口号使用 dx 寄存器表示,存取的数据置于 eax/ax/al 中。
Windows 设备管理器按类型列出资源,其中的[内存]和[输入/输出(I/O)]项,即上述两种物理空间所映射的设备。在 x64 Windows 10 的[内存]项中,物理地址的值范围在 2^32 以内,这也许是设备厂商出于向下(x86)兼容的考虑。
做些简单的测试:
- 测试一:将虚拟机内存调至 1 GBytes 或更低,然后启动虚拟机查看[内存]项,其中的地址值超过 1 GBytes。这就证实了物理地址……并非内存。
- 测试二:[内存]项中皆为物理地址,而非线性地址,诸如,0x000A0000 ~ 0x000BFFFF,在本地机和虚拟机中皆为显卡相关,而这块地址也与实模式的显存映射地址一致。这就证实了这里使用的皆为物理地址。
线性地址
x86/x64 CPU 支持 32/64 Bits 寄存器寻址(4 GBytes ~ 16 EBytes),前者(x86)不会有任何问题,因为线性地址范围最小支持 32 Bits(当然也兼容更古老的 CPU)。然而,后者(x64)却引发了问题:CPU 仅支持 48 Bits 线性地址,因此,厂商规定线性地址的高 16 Bits 被作为符号扩展(0x0000... 或 0xFFFF...),这样的线性地址称为 canonical 地址;反之视为 non-canonical 地址(如高 16 Bits 的 0x0001... 或 0xFFF0... 等等),使用 non-canonical 地址将引发异常(Intel 手册描述)。
CPU 线性地址布局:
- 0x00000000`00000000 ~ 0x00007FFF`FFFFFFFF:canonical 地址共 128 TBytes。
- 0xFFFF8000`00000000 ~ 0xFFFFFFFF`FFFFFFFF:canonical 地址共 128 TBytes。
- 0x00008000`00000000 ~ 0xFFFF7FFF`FFFFFFFF:non-canonical 地址共 16,776,960 TBytes。
合法线性地址的 Bit 47 必须和 Bit 48 ~ 63 一致,否则为非法地址。若以后需要更大的空间,只需将符号位上移。
从 x64 Windows 8.1 及其之后的版本线性地址支持 256 TBytes,这与 CPU 描述的线性地址吻合。而之前的版本仅支持 16 TBytes 线性地址(微软描述,实测为 248 TBytes)。
1 2 3 4 5 6 7 8 9 10 | SYSTEM_INFO info = {}; GetSystemInfo (&info); / * x64 Win7: info.lpMinimumApplicationAddress = 0x00000000 ` 00010000 info.lpMaximumApplicationAddress = 0x000007FF `FFFEFFFF x64 Win10: info.lpMinimumApplicationAddress = 0x00000000 ` 00010000 info.lpMaximumApplicationAddress = 0x00007FFF `FFFEFFFF * / |
可以通过 windbg 的 !address 命令枚举线性地址布局,还可以使用 !pte 命令查看线性地址是否 canonical:
x64 Win7:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | kd>!address .... BaseAddress EndAddress + 1 RegionSize VaType - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 ` 00000000 0 ` 00010000 0 ` 00010000 UserRange .... 7FF `FFFF0000 800 ` 00000000 0 ` 00010000 UserProbeArea 800 ` 00000000 8000 ` 00000000 7800 ` 00000000 <unknown> 8000 ` 00000000 FFFF8000` 00000000 FFFF0000` 00000000 NonAddressable FFFF0800` 00000000 FFFF8000` 00000000 7800 ` 00000000 SystemRange FFFF8000` 00000000 FFFFF680` 00000000 7680 ` 00000000 SystemRange FFFFF680` 00000000 FFFFF700` 00000000 80 ` 00000000 PageTables .... FFFFFFFF`FFC00000 FFFFFFFF`FFFFFFFF 0 ` 00400000 HAL |
用户模式线性地址空间是固定大小的:0x800`00000000(因从 0 计数,有效地址要 - 1) = 8 TBytes。未知区域(unknown)大小 = 0x7800`00000000 = 120 TBytes。
内核模式从高到低对地址进行布局:HAL - PageTables + 1 = 0xFFFFFFFF`FFFFFFFF - 0xFFFFF680`00000000 + 1 = 0x980`00000000 = 9.5 TBytes。
从上向下数第一块 SystemRange 的 0xFFFF0800`00000000 和 0xFFFF8000`00000000 - 1 这两个地址都是 non-canonical,但如果将这两个地址的高 16 Bits 清零,则与 unknown 吻合(应该是微软将用户模式没有使用的 120 TBytes 归为内核了)。
第二块 SystemRange 的大小 = 118.5 TBytes(128 - 9.5)。综上内核模式共占 248 TBytes(0xFFFFFFFF`FFFFFFFF - 0xFFFF0800`00000000 + 1)。
x64 Win10:
1 2 3 4 5 6 7 8 9 10 11 | kd>!address .... BaseAddress EndAddress + 1 RegionSize VaType - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 ` 00000000 0 ` 7ffe0000 0 ` 7ffe0000 UserRange .... 7fff `ffff0000 ffff8000` 00000000 ffff0000` 00010000 UserProbeArea 8000 ` 00000000 ffff8000` 00000000 ffff0000` 00000000 NonAddressable ffff8000` 00000000 ffffa908`ac201000 2908 `ac201000 SystemRange .... ffffffff`ffc00000 ffffffff`ffffffff 0 ` 00400000 HAL |
标准的 canonical 地址,用户与内核模式各占 128 TBytes。(SystemRange 块的 EndAddress 的值是随机的……不明所以)。
实模式,或保护模式分段下,线性地址就是物理地址;保护模式分页下,线性地址转物理地址涉及 32/48 Bits 的两种分页模式,论坛中有其他文章介绍,因此,就不赘言了。
逻辑地址
言不如表;表不如图。下图截取自 Intel 手册,图中绘出了逻辑地址到线性地址的转换过程。从图中大致可以将逻辑地址归纳为 'Segment:Offset' 形式,通常 Segment 是隐式的。而现今的编程环境基本皆为 Flat Model,也就是说逻辑地址已经被削弱,取而代之的是线性地址,至少 c/c++ 中指针使用线性地址。
参考资料
Memory Limits for Windows and Windows Server Releases
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。