An image showing a snippet of source code related to cybersecurity. The code includes a mixture of comments, function calls, and variable assignments. Syntax highlighting differentiates comments in red, function names in purple, keywords in yellowish-orange, and strings in light blue.

Investigating a New Kernel Exploit Primitive Class

Attack surfaces change as a result of the dynamic and frequently changing security landscape. Numerous cases involving various products, bug types, and exploit primitives are received by MSRC. An arbitrary kernel pointer read is one of the most intriguing primitives we observe. These frequently occur when kernel mode code fails to verify that pointers read from attacker-controlled input actually point to the Virtual Address Space’s (VAS ) user-mode portion. Such a primitive can be used by an attacker to provide kernel mode-pointers, which will then be de-referenced by kernel-mode code. The attacker may be able to read an arbitrary kernel address or pointer, but there is occasionally a further restriction that prevents them from retrieving the memory read’s contents. We assume that an attacker can cause the kernel to read X if the virtual memory at a given address is Y and that there is no direct way to obtain the read data. We would typically consider this to be an information disclosure vulnerability if the attacker could read Y and then read memory contents from the VAS. However, it’s not always easy to determine whether an attacker can leak data while still causing an arbitrary kernel pointer read. We wonder if such a constrained primitive could actually be used for code execution or privilege escalation. Traditionally, these would have an effect of Denial of Service ( DoS ) or, in some cases, second-order Kernel Memory Information Disclosure ( where side channels or indirect probing are possible ).

When considering the aforementioned query, we wanted to know if we could take advantage of peripheral device drivers’ reads to Memory Mapped I/O ( MMIO ) ranges. It would n’t be far-fetched to imagine that reads to MMIO ranges would be sensitive to the order, timing, and even the size of memory writes issued to their respective mmIO spaces since they are used for two-way communication between the device driver and the IO device. The various read and write operations to these MMIO ranges can be seen as having an impact on the external hardware device logic, which is an abstract state machine. Consider an IO device that implements an “atomic read-and-reset” of a state/last-error register, or one that uses serial input to read from an internal buffer while removing characters. The IO device would typically expect only a legitimate entity—the OS kernel or the device driver—to issue the memory reads to its MMIO space, so this could theoretically result in unexpected behaviors. But what if an attacker can corrupt the IO device’s state machine logic by using an arbitrary pointer read bug in the kernel to subvert these legitimate reads, issue them out-of-order, and at unexpected times? Could this be used as a ruse by an attacker?

In this blog, we’ll go over our exploratory research into this attack vector in detail and explain how researchers can use primitives like this to look for interesting behaviors.

Finding the intended devices

To conduct additional vulnerability research, it is necessary to compile a list of potential system devices. Any driver that makes use of MMIO is intriguing, but the likelihood that it will be exploited increases with the complexity of the ring-0 state-machine logic and decisions made on data from those regions. In order to achieve this, we’ll talk about the following MMIO address range enumeration techniques:

  1. Probing Registry/Device Manager
  2. Table ACPIMCFG
  3. string scanningNave
  4. Interception by MmMapIoSpace

Probing Registry/Device Manager

A list of interesting devices on a system can be found in device manager, where we can also look for the MMIO ranges they are associated with. Using Power Shell, we can also script the extraction of this data:

$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#$ranges = Get-CimInstance Win32_DeviceMemoryAddress | where StartingAddress -eq $cis.Antecedent.StartingAddress; #$i, $name, $devid, $cis.Antecedent.StartingAddress }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 = $hDeviceID = $idtable[$h]Ranges = $rangetable2[$h]}$endtable += , $obj}Write-Output $endtable

These are some MMIO range examples that you can see:

Name DeviceID Address in person
Mobile 6th/ 7th Generation Intel ( R ) Processor Family I/O PCI Express Root Port# 1- 9D10 PCIVEN_8086&DEV_9D10&SUBSYS_72708086&REV_F13&11583659&0&E0 {0xD4400000-0xD45FFFFF}
UHD Graphics 620 Intel ( R ) PCIVEN_8086&DEV_5917&SUBSYS_00271414&REV_073&11583659&0&10 The numbers 0xD3000000- 00xxD3FFFF, and 0000000 are as follows:
Marvell AVASTAR Wireless-AC Network Controller PCIVEN_11AB&DEV_2B38&SUBSYS_045E0009&REV_004&32FA7CC7&0&00E0 The numbers 0xD4500000 and 00xSD45FFFFF are as follows:
First Intel ( R ) Management Engine Interface PCIVEN_8086&DEV_9D3A&SUBSYS_72708086&REV_213&11583659&0&B0 {0xDFBDF000-0xDFBDFFFF}

All of these are physical addresses, which should be obvious right away. The attacker can only cause reads of arbitrary Virtual Addresses (VA ) in the scenario we’re considering. A given physical MMIO address range would need to be mapped to the kernel VAS for any practical attack, so an exploit would also need an additional primitive to get around Kernel Address Space Layout Randomization (KASLR ) for research purposes.

Table ACPIMCFG

Another source we can look at to find interesting devices is the Advanced Configuration and Power Interface (ACPI). A layer of abstraction between the operating system ( OS ) and hardware device firmware is created by ACPI, which regulates interactions with system hardware devices over primary and ( optionally ) peripheral busses. Low-level firmware management operations are now under ACPI’s control, relieving the OS of the need to use x86/X86-64 System Management Mode ( SMM) to manage them.

The ACPI Component Architecture Project, ACPICA, organizes the data into a number of tables that are stored in the registry and can be dumped using open-source tools. The ACPI specification specifies that a certain number of tables must be used, but there are also some optional, reserved tables. Peripheral Component Interconnect (PCI) device configuration data, such as registered MMIO ranges and PCI Base Address Registers ( BARs ), is contained in the optional “MCFG” table. The specification refers to the MCFG table as “PC I Express memory mapped configuration space base address Description Table.” The PCI Firmware Specification, Revision 3.0, Table 4- 2, provides more information about the entries for thePCI configuration. Using the ACPICA tools and the following commands, we can runtime dump the registered ACPI tables:

C:WorkspaceACPIiasl-win-20210730>acpidump.exe > acpitabl.dat C:WorkspaceACPIiasl-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

The same tools can be used to extract and decompile the MCFG table:

C:WorkspaceACPIiasl-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:WorkspaceACPIiasl-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

The decompiled result, ACPI Source Language ( ASL/DSL), gives us the physical address ( base address ) of the raw table data for MCFG:

/* * Intel ACPI Component Architecture * AML/ASL+ Disassembler version 20210730 (32-bit version) * Copyright (c) 2000 - 2021 Intel Corporation *  * Disassembly of mcfg.dat, Wed Sep 22 22:08:07 2021 * * ACPI Data Table [MCFG] * * Format: [HexOffset DecimalOffset ByteLength]  FieldName : FieldValue (in hex) */ [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 Raw Table Data: Length 60 (0x3C)     0000: 4D 43 46 47 3C 00 00 00 01 84 41 4C 41 53 4B 41   // MCFG<.....ALASKA     0010: 41 20 4D 20 49 00 00 00 09 20 07 01 4D 53 46 54   // A M I.... ..MSFT     0020: 13 00 01 00 00 00 00 00 00 00 00 00 00 00 00 E0   // ................     0030: 00 00 00 00 00 00 00 FF 00 00 00 00               // ............ 

We can extract the physical PCI bus, device, and function numbers from this location for each MCFG device using this physical base address. This information is displayed to us simply using PCI parsing utility functions in the RWEverything tool, though it must be run on a computer without Virtualization-Based Security (VBS ) enabled. This method is used to dump registered MMIO ranges on a PCI device as an example:

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 ID0x5A161002 Revision ID0x00 Class Code0x060400 Cacheline Size0x10 Latency Timer0x00 Interrupt PinINTA Interrupt LineIRQ11 BAR10x00000000 BAR20x00000000 Primary Bus#0x00 Secondary Bus#0x01 Subordinate Bus#0x01 IO Range  0x0000E000 - 0x0000EFFF Memory Range  0xFEA00000 - 0xFEAFFFFF Prefetchable Memory Range  0xC0000000 - 0xD07FFFFF Expansion ROM0x00000000 Subsystem ID0x5A141002

It is important to note that this method is not always practical because none of the Hyper-V VM configurations on our test machines used this optional MCFG table during our analysis. Additionally, devices with tables registered where VBS is enabled may not dump their MCFG configurations due to the requirement to read physical memory.

Nave String Scanning

We could also use a straightforward PowerShell script to scan all driver images stored in C: WindowsSystem32Drivers that contain the string” MMIO” due to the VBS / HVCI issues with the approaches mentioned above. Both the driver image names and the strings that are located can be discarded. a little Power Shell:

$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

The directory you want to scan can run this:

PS C:WindowsSystem32drivers> C:grep_drivers.ps1Path----C:WindowsSystem32driversiaStorAVC.sysC:WindowsSystem32driversUSBXHCI.SYSC:WindowsSystem32driversVid.sysC:WindowsSystem32driversvmbkmcl.sysC:WindowsSystem32driversvmbus.sysC:WindowsSystem32driversdxgkrnl.sysC:WindowsSystem32driversiaLPSS2i_GPIO2.sysC:WindowsSystem32driversiaLPSS2i_GPIO2_BXT_P.sysC:WindowsSystem32driversiaLPSS2i_GPIO2_CNL.sysC:WindowsSystem32driversiaLPSS2i_GPIO2_GLK.sysC:WindowsSystem32driversiaLPSS2i_I2C.sysC:WindowsSystem32driversiaLPSS2i_I2C_BXT_P.sysC:WindowsSystem32driversiaLPSS2i_I2C_CNL.sysC:WindowsSystem32driversiaLPSS2i_I2C_GLK.sysC:WindowsSystem32driversiaLPSSi_I2C.sys

Specific information about the contents of each identified driver will be provided by running the Sys Internals ‘ strings utility over these. On our lab machines, we used this technique to find a total of 24 driver files, which we then chose to analyze further. This list primarily includes parts for GPIO, I2C, DirectX/Video, Virtualization ( Hyper-V), USB, and OEM device-specific hardware.

Interception by MmMapIoSpace

A specific physical memory address range is mapped to the non-paged system address space using the MmMapIoSpace/MrMapInfoSpaceEx kernel driver routines. When MmMapIoSpace mapswriteable and executable memory ( if HVCI is disabled ), and mMMMapInEx allows you to specify page protections, we can intercept calls to these and compile a list of all the VA that have been mapped and unmapped. These two operations are exported from ntoskrnl. exe, so public debug symbols make them simple to find.

We can use WinDbg’s instrument functions to read the returned values and intercept these calls interactively. We can dump a list of all the VAs that device drivers map IO space registers into because, according to the normal flow, in order to perform any operations, they would need to map the device’s physical address range to that VA. The following commands can be used to dump every mapping created:

bu nt!MmMapIoSpace ".block{ r $t1 = @rcx; r $t2 = @rdx; r $t3 = @r8; .printf /D "[+] MmMapIoSpaceEx - Address in person: %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 - Address in person: %p, Size: %p, Protect: %p) n", @$t1, @$t2, @$t3}; gc"bu nt!MmUnmapIoSpace ".block{ r $t1 = @rcx; .printf /D "[-] Unmapped at Virtual Address: %pn", @$t1}; gc"

Finally, we can add a break to dump the mapped VA for each entry if we find the return address of the same MmMapIoSpace/Ex functions. The offset for your build may need to be slightly adjusted as follows:

bu nt!MmMapIoSpace+0x34 ".block{ r $t1 = @rax; .printf /D "[+] Mapped at Virtual Address: %pn", @$t1}; gc"bu nt!MmMapIoSpaceEx+0x2b ".block{ r $t1 = @rax; .printf /D "[+] Mapped at Virtual Address: %pn", @$t1}; gc"

The common pattern among these was device drivers mapping the IO device’s physical address in the VA and then immediately unmapping it after performing some operation, we discovered after observing a large number of output hits. The vast majority of the results we obtained from hooking these were as follows:

[+] MmMapIoSpaceEx - Address in person: 00000000000f93d0, Size: 000000000000439b, Protect: 0000000000000004) [+] Mapped at Virtual Address: ffffb980cff123d0 [-] Unmapped at Virtual Address: ffffb980cff123d0 [+] MmMapIoSpaceEx - Address in person: 00000000f7ff0300, Size: 0000000000000024, Protect: 0000000000000204) [+] Mapped at Virtual Address: ffffb980d04da300 [-] Unmapped at Virtual Address: ffffb980d04da300

An attacker would need to race this tiny window in order to take advantage of an entry like these. We observed that many calls were made during the boot process, before the system had fully loaded, or after a user had logged in and out. Attacking such an entry might be possible, but winning this race might present its own difficulties. Based on the available devices, our observations will vary, and as we’ll see later, some device classes may choose to dynamically map MMIO regions based on user-triggered actions. We also discovered that IO device drivers mapped about 15 virtual addresses on our test system, and these addresses persisted long after boot.

Interesting driver patterns for devices

It’s time to uninstall your preferred decompiler after identifying a few target device drivers. We began by looking at some driver images that were included in the naive string scanning scripts because doing so would have given us more context for the code we were examining. Before conducting any in-depth analysis, the debug trace and logging strings left in such drivers frequently provide a hint as to their intended use. We were able to focus our efforts more precisely as a result.

Let’s look at a few intriguing drivers we found and talk about potential attack surfaces.

Iacamera64 Intel AVStream Camera Driver. ( Sys )

The iacamera64 was chosen. Due to the intriguing string we discovered:” The MMIO base address is 0x %08x,” we were able to identify the system driver early in the process. “” A driver trace routine call contains this string:

<img src="https://dodcybersecurityblogs.com/wp-content/uploads/2022/03/wp-content-uploads-2022-03-Intel_AV_Stream_RE_Snippet1.webp" alt="TraceRoutine (1, “The MMIO base address is exx08x.”, ((unsigned int)a1 + 12));_LABEL_7:_mm_1fence();PVAMapping = MmMapIoSpaceEx(a1[6], ((unsigned int)a1 + 14), 4164);// PAGE_READWRITEa1[8] = pVAMapping;if (!pVAMapping )return exc000009A164;__mm_1fence();CACHE_VA_REGION (PVAMapping);v17[0] = 0;v19 = 0164;// STATUS_INSUFFICENT_RESOURCES// Store Mapped MMIO Region VA in GlobalTraceRoutine_0(&v19, “IISPHWConfigManager::Dereference”);if (_InterlockedExchangeAdd((volatile signed __int32 *)a1 + 10, 0xFFFFFFFF) == 1)” loading=”lazy” decoding=”async” class=”mx-auto d-block img-fluid max-height-600″>

Further examination of this driver revealed that it was designed to support the laptop’s embedded camera, which we used for testing. This driver uses MMIO in a few well-known locations. The following analysis can be more detailed without using binary offsets because, despite the lack of publicly available symbol information for the binary, several trace routines helpfully leak the function names.

The EventCategoryDeviceInterfaceChange’s PnP notifications are intercepted using the IspinterfaceNotification routine. The PnP manager uses this event handler to handle any upcoming arrivals or removals of device interface instances. CISPInterfacedConfigMgr is invoked when a PnP event is registered: IaIspArrival upon arrival. For the duration of its use, this routine appears to map to some cached globals ( MMIO un-mapping routines are also registered ). These MMIO regions are accessible through wrap routines that use spin locks and memory fences for R/W operations. This cached region contains both MMIO Read and Write wrappers.

According to sub-routine cross-reference analysis, the driver we analyzed had at least 261 locations that could perform MMIO Write operations and an additional 184 that can perform Read operations. Many of the locations mentioned involved performance counter data and device configuration, but in at least a few, the device driver relies on an MMIO read’s outcome, such as for device FW image authentication:

<img src="https://dodcybersecurityblogs.com/wp-content/uploads/2022/03/wp-content-uploads-2022-03-Intel_AV_Stream_RE_Snippet2.webp" alt="TraceRoutine (1,CDriverControl::AuthenticateFW");v5 = (unsigned int )DO_MMIO_READ (2u, 0x300u);TraceRoutine (1,SECURITY CTL register value before authentication: %x", v5); if ((v5 & 0x1F) == 0 )}v1 = sub_140022ECO(al,(_QWORD)(a1 + 1760), *(_DWORD *)(a1 + 1768), *(_DWORD *)(a1 + 1776),*(_DWORD *)(a1 + 1780));v5 (unsigned int)DO_MMIO_READ(2u, 0x300u);TraceRoutine (1, SECURITY_CTL register value after authentication: %x", v5); if (v1 < 0 && (_DWORD) qword_140110560 == 2 && !dword_140110568)” loading=”lazy” decoding=”async” class=”mx-auto d-block img-fluid max-height-600″>

Attackers would be good candidates to target device drivers like this one because they make a lot of decisions based on the state of data read from the MMIO region.

We did n’t find any proof that this code synchronizesin-between the reads themselves, despite the fact that each MMIO Read operation is synced with mem fences or spin locks. However, interleaving operations with interpolated reads could undoubtedly result in bugs in the state expected by the device driver. Clearly, this assumption may be sound because it entirely depends on what the machine is doing in response to such an operation.

A Note on Caching and Memory Barriers

The order of two sequential writes to various device addresses may not be seen when cache write-buffers are used. As a result, it is not guaranteed that MMIO data writes will be received by memory banks or registers of peripherals in the same order. Therefore, it may also be of interest to any driver that is sensitive to the order of operations and does not automatically include memory barrier or cache-flushing instructions after sensitive writes. Without altering the final store state, write optimization can be used to lessen device redundancy or hide memory access latency. Therefore, using a write buffer allows the cache to interleave read requests while writing is happening. The potential for racial conditions in the distribution of MMIO operations is increased by this additional complexity.

( iaLPSS2i_I2C ) Intel Serial IO II Driver. ( Sys )

An example of a completely different class of device driver that uses dynamic MMIO device registration is the Intel Low Power Subsystem Support Integrated Circuit Driver. A linked list of Device Driver object entries with various event registration handlers hooked up ( presumably so that power events can be sent to devices registered on a specific bus by bus device index ) appears to be the responsibility of this specific device for registering devices onto PCI buses. There are several variations of this driver that, while not significantly different from one another in terms of the functionality mentioned here, may exist for various bus types. ).

The OnDeviceAdd routine is used to register fixed functions when a device is added. Some of these functions appear to be overwritten after function dispatch, but this is unimportant to us at this time.

 memset(Dst, 0, sizeof(Dst));  LODWORD(Dst[0]) = 144;  Dst[5] = (__int64)OnPrepareHardware;  Dst[6] = (__int64)OnReleaseHardware;  Dst[1] = (__int64)OnD0Entry;  Dst[3] = (__int64)OnD0Exit;  Dst[9] = (__int64)OnSelfManagedIoInit;  Dst[7] = (__int64)OnSelfManagedIoCleanup;  Dst[14] = (__int64)OnQueryStop; 

Both the device’s Interrupt Service Routine ( ISR ) and Deferred Procedure Call ( DPC ) handlers are registered by the same OnDeviceAdd routine:

 Dst[3] = (__int64)OnInterruptIsr;  Dst[4] = (__int64)OnInterruptDpc;  Dst[1] = v20; 

An MMIO range is registered by the OnPrepareHardware function, which is later unmapped by a paired OnReleaseHarshware routine:

 pVAMapping = MmMapIoSpaceEx(*(_QWORD *)(v15 + 4), *(unsigned int *)(v15 + 12), 516i64);  if ( !pVAMapping )  {  v10 = 0xC000009A; // ERROR_INSUFFICENT_RESOURCES  if ( ((__int64)WPP_MAIN_CB.Queue.Wcb.DeviceObject & 1) != 0 )  ETWLogThunk(  (unsigned int)&iaLPSS2_I2C_PROVIDER_Context,  (unsigned int)&EvtPrepareHardware_MmioMap_Error,  (_DWORD)v9,  0,  *(_QWORD *)(v15 + 4),  *(_DWORD *)(v15 + 12),  154);  goto LABEL_53;  }  *(_QWORD *)(v8 + 32) = pVAMapping;  *(_QWORD *)(v8 + 24) = *(_QWORD *)(v15 + 4);  *(_DWORD *)(v8 + 40) = *(_DWORD *)(v15 + 12);  *(_QWORD *)(v8 + 56) = pVAMapping + 512;  *(_QWORD *)(v8 + 272) = pVAMapping + 2048;  *(_QWORD *)(v8 + 48) = pVAMapping;  if ( ((__int64)WPP_MAIN_CB.Queue.Wcb.DeviceObject & 2) != 0 )  ETWLogThunk2(  (unsigned int)&iaLPSS2_I2C_PROVIDER_Context,  (unsigned int)&EvtPrepareHardware_MmioMapped_Info,  (_DWORD)v9,  0,  *(_QWORD *)(v15 + 4),  *(_DWORD *)(v15 + 12),  pVAMapping);

Our suspicion is that MMIO is being used in this instance to support the registration of an ACPI-compliant device with the PCI bus so that, for instance, the DCPI subsystem (ACPI ) can register a device’s temperature cut-off sensor. power management events will be sent to device firmware handlers by sys. Given that the aforementioned statesD0 and D3 relate to device power states, this explanation would make sense.

The ACPI subsystem manages operations like system power, hibernation, sleep states, IO probing, and PnP, which should be sufficient for our discussion. The ACPI controls the subsystem, which operates in ring 0 and. driver for Sys. An ACPI Machine Language ( AML) Interpreter ( minimal VM ) that can issue System Control Interrupts (SCI) when a thermal event trigger is exceeded is included in this driver. A registered ACPI AML control method that corresponds to the interrupt can be run by the run-time in this scenario. Device IO can be dynamically issued by this code. According to the code, it appears that this targets ( at least specific classes of ) these device IO requests with the MMIO region mentioned. The ACPI driver power event registration system is not the subject of this blog’s discussion, but it is covered here, along with a summary of the coreACPI tables and the Differentiated System Description Table ( DSDT ) defined in section 5.2.11.

The entire ACPI implementation for a specific machine, including all devices rooted on the system bus, is contained in the DSDT. Using the previously mentioned ACPICA tools, the following is an example of some decompiled ACPI Machine Language ( AML) code from the DSDT:

Device (S8F0){ Name (_ADR, 0x00070000) // _ADR: Address Name (_SUN, 0x47) // _SUN: Slot User Number OperationRegion (REGS, PCI_Config, 0x00, 0x04) Field (REGS, DWordAcc, NoLock, Preserve) { ID, 32 } Method (_STA, 0, NotSerialized) // _STA: Status { Return (BSTA (ID)) } Method (_EJ0, 1, NotSerialized) // _EJx: Eject Device { BEJ0 (Arg0, _ADR) } Method (_DSM, 4, Serialized) // _DSM: Device-Specific Method { Return (BDSM (Arg0, Arg1, Arg2, Arg3, _ADR)) } Name (_PRW, Package (0x02) // _PRW: Power Resources for Wake { 0x03, 0x03 })}

This ACPI Source Language ( ASL ) should be sufficient to demonstrate that there is dynamic interaction with the device directly here, on the processing of power management event state handling, in a Virtual Machine operating in Ring0. This routine is just an example of one for an unregistered device PCI Configuration Register OperationRegion. Can we now consider an ACPI code that has been registered and its interpreter to be a legitimate attack surface? Any interpolated MMIO read that is triggered by an ACPI SCI Interrupt could have unintended effects on the interpreter state, which is then running a Turing-complete programming language and appears to be open to the same observations as device driver objects.

Any ACPI-compliant device with dynamic registration handlers might now be interesting if our claims are true. If an exploit were based on more generic interactions between a standards-compliant component, like ACPI, and an external device, the stability of the exploit might also be improved.

Address ranges for Fuzzing MMIO

We made the decision to try fuzzing the 15 MMIO address ranges we found on our test device, which remained mapped long after boot, using the aforementioned MmMapIoSpace intercept. To test whether we could elicit any interesting behavior, we created multiple threads that continuously read from these addresses within the size range of each mapping. The addresses listed below are not fixed and change whenever MmMapIoSpaceEx is called, so please take note of some examples of the targeted MMIO addresses.

0xfffff67c84600000; // nt  size 0x300000 0xffffe68063040000; // BOOTVID  size 0x20000 0xffffe68063616000; // BasicDisplay  size 0x20000 0xffffe6806343e4f0; // vmgencounter  size 0x10 0xffffe680631e8064; // ACPI size 0xff0xffffe680631ff000; // winhv size 0x10000xffffe6806336c000; // fvevol size 0x4000 0xfffff67c89000000; // DXGKrnl   size 0x300000 

We did n’t notice any notable crashes or behaviors from this simple black-box fuzzing exercise on our test devices. The few crashes we saw were caused by device drivers eventuallyun- or re-mapping the VAS. Targeting particular MMIO regions, such as those discovered during reverse engineering efforts, at specific transitional points within the driver state machine would be a better strategy. This could be brought on by adding dynamic breakpoints and scripting dynamically issued reads. Additionally, it’s possible that there are driver problems that wo n’t cause a visible crash in the device driver. Perhaps as a result of our fuzzing, the device hardware itself performs an intriguing action that is more difficult to observe. This raises the issue of measuring positive outcomes once more and necessitates in-depth familiarity with the particular IO device. On their own setup and peripheral devices, we encourage interested readers to experiment with these techniques. If you discover anything cool, let us know!

Thoughts for Parting

Low-level device interactions programming is difficult and complicated. Bugs are typically present in complex environments. Our investigations should spur additional study into using MMIO ranges to exploit arbitrary pointer reads in order to generate more lucrative results, such as RCE or EoP. After all, there are numerous IO devices available. However, any exploit is unlikely to be portable and will only allow for hardware interactions. Such bugs have a limited reachability, which reduces their blast radius. A motivated attacker may be less concerned about this in situations where the hardware eco-system is less fragmented than Windows ( such as some classes of devices in the mobile handset space ).

From the perspective of exploitation research, we think there is still a lot to gain from improving the state-of-the-art kernel read extraction. In order to fully comprehend the viability and impact of such attacks in practice, we believe that this article has only begun to scratch the surface. Today, an attacker can pursue much lower-hanging fruit. It is also much more likely to be portable to find another more easily exploitable primitive in a” standard” ring-0 component. Therefore, it may be less appealing to invest in bug weaponization in the MMIO space. Such efforts might become more bearable if an attacker discovers numerous MMIO crashes on various devices using a custom fuzzer. Similar to this, it would lower the barrier to entry and give attackers more value if a particular class of IO devices were found to have particular behavior ( for instance, unique state machine logic ) that could be exploited using this primitive.

Primitive forms of exploitation are not all created equal. According to current knowledge, a generic MMIO blind-read primitive is less exploitable than an information disclosure or blind kernel-write vulnerability, necessitating the use of another bug and very particular hardware configuration on the target. What could be cooler than RCE from a pointer read, though, given the theoretical possibility? There is a ton of hardware available, but we have only tested the bare minimum of it. If you come across any intriguing tools or behaviors that can make using this primitive easier, please let us know!

Microsoft Security Response Center ( MSRC ): Andrew Ruddick, Rohit Mothe, and Carolina Hatanpaa

Acknowledgements

Thanks to Saar Amar, Joe Bialek, Nicolas Joly, Saruhan Karademir, Nick Stephens, Bruce Sherwin, David Zhang, and David Hepkin in particular.

Skip to content