探索新型内核漏洞利用原语:MMIO攻击向量

本文探讨了利用任意内核指针读取漏洞攻击内存映射I/O(MMIO)设备的新方法,详细分析了设备枚举策略、驱动模式及潜在攻击面,并提供了实际研究工具和代码示例。

探索新型内核漏洞利用原语 | MSRC博客

安全格局动态多变,攻击面随之不断演变。MSRC处理各类案例,涵盖不同产品、漏洞类型和利用原语。其中特别有趣的一种原语是任意内核指针读取。这类漏洞常发生在内核模式代码未验证从攻击者控制输入读取的指针是否指向用户模式虚拟地址空间(VAS)部分时。攻击者可利用此类原语提供内核模式指针,随后由内核模式代码解引用。有时还存在进一步限制:尽管攻击者可导致读取任意内核地址/指针,但无法获取读取的内存内容。如果虚拟内存地址X包含数据Y,我们假设攻击者可导致内核读取X,但无法直接获取读取的数据Y。若攻击者能读取Y,则可从VAS读取内存内容,我们通常将其评估为信息泄露漏洞。然而,对于攻击者拥有导致任意内核指针读取但无法泄露数据的原语的情况,评估并不总是明确。传统上,这类漏洞的影响是拒绝服务(DoS),或在某些情况下是二阶内核内存信息泄露(可能通过侧信道或间接探测),但我们好奇这种有限原语是否实际可用于代码执行/权限提升?

在思考上述问题时,我们想探索的想法是:能否利用对外围设备驱动程序的内存映射I/O(MMIO)范围的读取?对MMIO范围的读取用于设备驱动程序与IO设备之间的双向通信,不难想象它们对各自MMIO空间的内存读取顺序、时间甚至大小敏感。我们可以将外部硬件设备逻辑视为受这些MMIO范围的各种读写操作影响的抽象状态机。例如,考虑实现状态/最后错误寄存器的“原子读取和重置”的IO设备,或在读取时从内部缓冲区移除字符的串行输入设备。理论上,这可能导致意外行为,因为IO设备通常仅期望合法实体(操作系统内核或设备驱动程序)向其MMIO空间发出内存读取。但如果攻击者利用内核中的任意指针读取漏洞,可颠覆这些合法读取,在意外时间和顺序发出它们,破坏IO设备的状态机逻辑。攻击者能否利用这一点?

在本博客中,我们将详细介绍对此攻击向量的探索性研究,并逐步指导研究人员如何利用此类利用原语寻找有趣行为。

定位目标设备

第一步是整理候选系统设备列表以供进一步漏洞研究。任何使用MMIO的驱动程序都值得关注,但ring-0状态机逻辑/基于源自这些区域的数据做出的决策越复杂,越可能被利用。为此,我们将讨论以下设备和MMIO地址范围枚举策略:

  • 注册表/设备管理器探测
  • ACPI MCFG表
  • 简单字符串扫描
  • MmMapIoSpace拦截

注册表/设备管理器探测

我们可以查看设备管理器以查找系统上有趣的设备列表,并探测其关联的MMIO范围。我们还可以使用PowerShell脚本提取此信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$dnames = Get-WmiObject Win32_PnPEntity -Filter 'DeviceID like "PCI%"'
$idtable = @{}
$rangetable = @{}
$rangetable2 = @{} 

For ($i=0; $i -lt $dnames.Length; $i++) {
   $name = $dnames[$i].Name;
   $devid = $dnames[$i].DeviceID;
   $idtable[$name] = $devid
   $cis = Get-CimInstance Win32_PNPAllocatedResource | where-object { $_.Dependent.DeviceID -like "*$devid"} | where Antecedent -like "Win32_DeviceMemoryAddress*"
   $cis2 = $cis.Antecedent.StartingAddress
   $rangetable[$name] = $cis2
}

foreach ($h in $rangetable.Keys) {
   $qarr = @()
   if ($rangetable[$h] -is [system.array]) {
      For ($t=0; $t -lt $rangetable[$h].Length; $t++) {
         $qarr += , ((Get-CimInstance Win32_DeviceMemoryAddress | where StartingAddress -eq $rangetable[$h][$t]).Name);
      }
   }
   else {
      $qarr += , ((Get-CimInstance Win32_DeviceMemoryAddress | where StartingAddress -eq $rangetable[$h]).Name)
   }
   $rangetable2[$h] = $qarr
}

$endtable = @()
foreach ($h in $idtable.Keys) {
   $obj = [PSCustomObject]@{
      Name = $h
      DeviceID = $idtable[$h]
      Ranges = $rangetable2[$h]
   }
   $endtable += , $obj
}

Write-Output $endtable

示例MMIO范围如下:

名称 DeviceID 物理地址
Mobile 6th/7th Generation Intel(R) Processor Family I/O PCI Express Root Port #1 - 9D10 PCI\VEN_8086&DEV_9D10&SUBSYS_72708086&REV_F1\3&11583659&0&E0 {0xD4400000-0xD45FFFFF}
Intel(R) UHD Graphics 620 PCI\VEN_8086&DEV_5917&SUBSYS_00271414&REV_07\3&11583659&0&10 {0xD3000000-0xD3FFFFFF, 0xB0000000-0xBFFFFFFF}
Marvell AVASTAR Wireless-AC Network Controller PCI\VEN_11AB&DEV_2B38&SUBSYS_045E0009&REV_00\4&32FA7CC7&0&00E0 {0xD4500000-0xD45FFFFF, 0xD4400000-0xD45FFFFF}
Intel(R) Management Engine Interface #1 PCI\VEN_8086&DEV_9D3A&SUBSYS_72708086&REV_21\3&11583659&0&B0 {0xDFBDF000-0xDFBDFFFF}

首先显而易见的是,这些都是物理地址。在我们考虑的场景中,攻击者只能导致读取任意虚拟地址(VA)。出于研究目的,我们可以使用内核调试器(KD)检查这些是否映射到相应的VA,但对于任何实际攻击,给定的物理MMIO地址范围需要映射到内核VAS,因此利用还需要额外原语以绕过内核地址空间布局随机化(KASLR)。

ACPI MCFG表

高级配置与电源接口(ACPI)是我们可以检查的另一个来源以查找有趣设备。ACPI在最低级别控制与系统硬件设备在主要和(可选)外围总线上的交互,形成硬件设备固件与操作系统(OS)之间的抽象层。ACPI将一些低级固件管理操作的控制带给OS,减少对x86/x86-64系统管理模式(SMM)处理此类操作的依赖。

ACPI信息组织成多个表,存储在注册表中,可使用ACPI组件架构项目ACPICA的开源工具转储。根据ACPI规范,许多ACPI表是必需的,但也存在许多可选、保留的表。“MCFG”表是一个可选表,包含外围组件互连(PCI)设备配置信息,包括注册的MMIO范围和PCI基地址寄存器(BAR)。规范将MCFG表描述为“PCI Express内存映射配置空间基地址描述表”。PCI配置条目的详细信息在PCI固件规范3.0版表4-2中详细说明。我们可以使用ACPICA工具在运行时转储注册的ACPI表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
C:\Workspace\ACPI\iasl-win-20210730>acpidump.exe > acpitabl.dat 
C:\Workspace\ACPI\iasl-win-20210730>acpixtract.exe -l acpitabl.dat 

Intel ACPI Component Architecture 
ACPI Binary Table Extraction Utility version 20210730 
Copyright (c) 2000 - 2021 Intel Corporation 

 Signature  Length    Version Oem       Oem         Oem         Compiler
                              Id        TableId     RevisionId  Name
 _________  __________  ____  ________  __________  __________  _______

 01)  MCFG  0x0000003C  0x01  "ALASKA"  "A M I   "  0x01072009  "MSFT"
 02)  FACP  0x000000F4  0x04  "ALASKA"  "A M I   "  0x01072009  "AMI "
 03)  APIC  0x0000009E  0x03  "ALASKA"  "A M I   "  0x01072009  "AMI "
 04)  HPET  0x00000038  0x01  "ALASKA"  "A M I   "  0x01072009  "AMI "
 05)  FPDT  0x00000044  0x01  "ALASKA"  "A M I   "  0x01072009  "AMI "
 06)  SSDT  0x00001714  0x01  "AMD   "  "POWERNOW"  0x00000001  "AMD "
 07)  XSDT  0x00000054  0x01  "ALASKA"  "A M I   "  0x01072009  "AMI "
 08)  DSDT  0x00005BC1  0x02  "ALASKA"  "A M I   "  0x00000000  "INTL"

Found 8 ACPI tables in acpitabl.dat

可以使用相同工具提取和解编译MCFG表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
C:\Workspace\ACPI\iasl-win-20210730>acpixtract -s MCFG acpitabl.dat 

Intel ACPI Component Architecture 
ACPI Binary Table Extraction Utility version 20210730 
Copyright (c) 2000 - 2021 Intel Corporation 

 MCFG -      60 bytes written (0x0000003C) - mcfg.dat 

C:\Workspace\ACPI\iasl-win-20210730>iasl mcfg.dat 

Intel ACPI Component Architecture 
ASL+ Optimizing Compiler/Disassembler version 20210730 
Copyright (c) 2000 - 2021 Intel Corporation 

File appears to be binary: found 38 non-ASCII characters, disassembling 
Binary file appears to be a valid ACPI table, disassembling 
Input file mcfg.dat, Length 0x3C (60) bytes 
ACPI: MCFG 0x0000000000000000 00003C (v01 ALASKA A M I    01072009 MSFT 00010013) 
Acpi Data Table [MCFG] decoded 
Formatted output:  mcfg.dsl - 1568 bytes

解编译结果是ACPI源语言(ASL/DSL),为我们提供包含MCFG原始表数据的基地址(物理地址):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[000h 0000   4]                    Signature : "MCFG"   [Memory Mapped Configuration Table] 
[004h 0004   4]                 Table Length : 0000003C 
[008h 0008   1]                    Revision : 01 
[009h 0009   1]                    Checksum : 84 
[00Ah 0010   6]                      Oem ID : "ALASKA" 
[010h 0016   8]                Oem Table ID : "A M I" 
[018h 0024   4]               Oem Revision : 01072009 
[01Ch 0028   4]            Asl Compiler ID : "MSFT" 
[020h 0032   4]      Asl Compiler Revision : 00010013 

[024h 0036   8]                   Reserved : 0000000000000000 

[02Ch 0044   8]               Base Address : 00000000E0000000 
[034h 0052   2]       Segment Group Number : 0000 
[036h 0054   1]         Start Bus Number : 00 
[037h 0055   1]           End Bus Number : FF 
[038h 0056   4]                   Reserved : 00000000 

给定此MCFG物理基地址,我们可以从此位置解析每个PCI设备的物理PCI总线、设备和功能号。RWEverything工具包含PCI解析实用函数,可简单显示此信息(尽管这需要在未启用基于虚拟化的安全(VBS)的机器上运行)。使用此方法转储的PCI设备示例,带有注册的MMIO范围:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Bus 00, Device 02, Function 00 - ATI Technologies Inc. PCI-to-PCI Bridge (PCIE) 
 ID=5A161002, SID=5A141002, Int Pin=INTA, IRQ=0B, PriBus=00, SecBus=01, SubBus=01 
 MEM=FEA00000-FEAFFFFF C0000000-D07FFFFF  IO=0000E000-0000EFFF  

Device/Vendor ID 0x5A161002 
Revision ID 0x00 
Class Code 0x060400 
Cacheline Size 0x10 
Latency Timer 0x00 
Interrupt Pin INTA 
Interrupt Line IRQ11 
BAR1  0x00000000 
BAR2  0x00000000 
Primary Bus# 0x00 
Secondary Bus# 0x01 
Subordinate Bus# 0x01 
IO Range 
  0x0000E000 - 0x0000EFFF 
Memory Range 
  0xFEA00000 - 0xFEAFFFFF 
Prefetchable Memory Range 
  0xC0000000 - 0xD07FFFFF 
Expansion ROM 0x00000000 
Subsystem ID 0x5A141002

值得一提的是,在我们的分析过程中,测试机器上的Hyper-V VM配置均未实现此可选MCFG表,因此此方法并非在所有情况下都可行。同样,读取物理内存的要求可能阻止在启用VBS的情况下从注册了表的设备转储MCFG配置。

简单字符串扫描

由于上述方法的VBS/HVCI问题,我们还可以使用简单的PowerShell脚本扫描C:\Windows\System32\Drivers中所有包含字符串“MMIO”的驱动程序映像。可以转储定位的字符串以及驱动程序映像名称。一些PowerShell:

1
2
3
4
5
6
7
8
9
$ascii_grep = Get-ChildItem -Recurse | Select-String "MMIO" -List -Encoding "ASCII" | Select Path
$mbs_grep = Get-ChildItem -Recurse | Select-String "MMIO" -List -Encoding "Unicode" | Select Path

$merged_list = & {
    $ascii_grep
    $mbs_grep
}

echo $merged_list

这可以在要扫描的目录中执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
PS C:\Windows\System32\drivers> C:\grep_drivers.ps1

Path
----
C:\Windows\System32\drivers\iaStorAVC.sys
C:\Windows\System32\drivers\USBXHCI.SYS
C:\Windows\System32\drivers\Vid.sys
C:\Windows\System32\drivers\vmbkmcl.sys
C:\Windows\System32\drivers\vmbus.sys
C:\Windows\System32\drivers\dxgkrnl.sys
C:\Windows\System32\drivers\iaLPSS2i_GPIO2.sys
C:\Windows\System32\drivers\iaLPSS2i_GPIO2_BXT_P.sys
C:\Windows\System32\drivers\iaLPSS2i_GPIO2_CNL.sys
C:\Windows\System32\drivers\iaLPSS2i_GPIO2_GLK.sys
C:\Windows\System32\drivers\iaLPSS2i_I2C.sys
C:\Windows\System32\drivers\iaLPSS2i_I2C_BXT_P.sys
C:\Windows\System32\drivers\iaLPSS2i_I2C_CNL.sys
C:\Windows\System32\drivers\iaLPSS2i_I2C_GLK.sys
C:\Windows\System32\drivers\iaLPSSi_I2C.sys

在这些驱动程序上运行SysInternals“strings”实用程序将提供每个识别驱动程序的详细内容。使用此方法,我们在实验室机器上识别了总共24个驱动程序文件,供进一步分析。此列表主要包括与GPIO、I2C、DirectX/视频、虚拟化(Hyper-V)、USB和OEM设备特定硬件相关的组件。

MmMapIoSpace拦截

MmMapIoSpace/MmMapIoSpaceEx内核驱动程序例程用于将给定的物理内存地址范围映射到非分页系统地址空间。我们可以拦截对这些函数的调用以收集所有映射和取消映射的VA列表(注意:MmMapIoSpace映射可写和可执行内存(如果HVCI关闭),而MmMapIoSpaceEx允许指定页面保护)。这两个函数都从ntoskrnl.exe导出,因此易于使用公共调试符号定位。

要交互式拦截这些调用,我们可以在WinDbg中检测这些函数并读取返回值。在正常流程中,设备驱动程序必须将设备的物理地址范围映射到VA以执行任何操作,因此我们可以转储设备驱动程序映射IO空间寄存器的所有VA列表。要转储所有映射,可以使用以下命令:

1
2
3
bu nt!MmMapIoSpace ".block{ r $t1 = @rcx; r $t2 = @rdx; r $t3 = @r8; .printf /D \"[+] MmMapIoSpaceEx - Physical Address: %p, Size: %p, Cache Type: %p) \n\", @$t1, @$t2, @$t3}; gc"

bu nt!MmMapIoSpaceEx ".block{ r $t1 = @rcx; r $t2 = @rdx; r $t3 = @r8; .printf /D \"[+] MmMapIoSpaceEx - Physical Address: %p, Size: %p, Protect: %p) \n
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计