DMAR表定位

InitializeAcpiTable

根据DMAR表头的signiture定位 image-20251115192541631 image-20251115192549203

DMAR预处理 && VT-d初始化

InitializeVtdFromDmar

遍历DMAR表,解析所有DRHD(DMA Remapping Hardware Unit Definition)的信息。同时校验相关寄存器的合法性,检查DMA Remapping是否正确开启,包括:

  • Capability Register
  • Global Status Register
  • Root Table Address Register
  • Protected Memory Enable Register

在此过程中会同时收集并保存相关的寄存器地址,初始化VT-d Context image-20251115192642315

VT-d 开启

这一部分被vm不方便分析,根据后面的VT-d卸载以及[该开源项目](tandasat/HelloIommuPkg: The sample DXE runtime driver demonstrating how to program DMA remapping.)推断出大致的逻辑。完美大概率也参考了此项目

这一步包括:

  • 建表(Root Table和Context Table)
  • 写入RTAR(Root Table Address Register)
  • 执行IOTLB invalidate/ Context invalidate
  • 使能VT-d

建表

注意这里没有显式配置ContextEntry.TranslationType,默认为00b,即采用二级页表进行转换的模式

 1STATIC
 2VOID
 3BuildPassthroughTranslations (
 4
 5    OUT VTD_CONTEXT* VtdContext,
 6
 7    IN CONST EXTENDED_PAGE_TABLES* ExtendedPageTables
 8
 9    )
10
11{
12
13    VTD_ROOT_ENTRY rootEntry;
14
15    VTD_CONTEXT_ENTRY contextEntry;
16
17  
18
19    ASSERT(((UINT64)ExtendedPageTables % SIZE_4KB) == 0);
20
21  
22	//
23	// 填充root table,让所有的root entries指向同一个context table
24	//
25	
26    rootEntry.Upper64.AsUInt = rootEntry.Lower64.AsUInt = 0;
27
28    rootEntry.Lower64.Present = TRUE;
29
30    rootEntry.Lower64.ContextTablePointer =
31
32        gOs->GetPhysicalAddress(gOs, VtdContext->ContextTable) >> MV_PAGE_SHIFT;
33
34    for (UINT64 bus = 0; bus < ARRAY_SIZE(VtdContext->RootTable); bus++)
35
36    {
37
38        VtdContext->RootTable[bus] = rootEntry;
39
40    }
41
42  
43
44	//
45	// 填充context table,控制所有的context entries指向同一个二级PML4
46	// 在这里使用的是EPT,因为该项目用了EPT来控制读写权限
47	// 寻址流程:GPA --> Root Table --> Context Table --> SLPML4 --> ...
48	//
49	// 由于这里所有的context entries都指向了同样的地址,所以可以简单的domain id置1 
50	// 完美的生产级实现可能更复杂,但核心原理只需要保证每个domain都不映射游戏地址
51	// 即可实现对游戏的保护
52	//
53	
54    contextEntry.Upper64.AsUInt = contextEntry.Lower64.AsUInt = 0;
55
56    contextEntry.Lower64.Present = TRUE;
57
58    contextEntry.Lower64.SecondLevelPageTranslationPointer =
59
60        gOs->GetPhysicalAddress(gOs, &ExtendedPageTables->Pml4) >> MV_PAGE_SHIFT;
61
62    contextEntry.Upper64.AddressWidth = BIT1;  // 010b: 48-bit AGAW (4-level page table) 表示采用四级页表
63
64    contextEntry.Upper64.DomainIdentifier = 1;
65
66    for (UINT64 i = 0; i < ARRAY_SIZE(VtdContext->ContextTable); i++)
67
68    {
69
70        VtdContext->ContextTable[i] = contextEntry;
71
72    }
73
74}

写入RTAR && 刷新页表Cache && 使能VT-d

这一步向VT-d硬件写入Root Table地址(类似CR3)。刷新TLB

 1STATIC
 2VOID
 3EnableDmaRemapping (
 4
 5    IN CONST DMAR_UNIT_INFORMATION* DmarUnit,
 6
 7    IN CONST VTD_ROOT_ENTRY* RootTable
 8
 9    )
10
11{
12
13    UINT32 status;
14
15    VTD_ROOT_TABLE_ADDRESS_REGISTER rootTableAddressReg;
16
17  
18	// 这里的断言是根据Intel手册设置的
19	// 即当设置Root Table地址时,VT-d必须是禁用状态
20
21    MV_HOST_ASSERT(MV_IS_FLAG_SET(MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS),                                  VTD_GLOBAL_STATUS_TRANSLATION_ENABLE_STATUS_FLAG) == FALSE);
22
23  
24
25	//
26	// 设置Root Table地址
27	// 类比到CR3操作中,其实就是mov cr3,cr3_value;
28	//
29
30    MV_HOST_DEBUG("Setting the root table pointer to %p (VA: %p)", VaToPa(RootTable), RootTable);
31
32    rootTableAddressReg.AsUInt = 0;
33
34    rootTableAddressReg.RootTableAddress = VaToPa(RootTable) >> MV_PAGE_SHIFT;
35
36    MmioWrite64(DmarUnit->RegisterBaseVa + VTD_ROOT_TABLE_ADDRESS, rootTableAddressReg.AsUInt);
37
38    status = MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS);
39
40    MmioWrite32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_COMMAND,
41
42                status | VTD_GLOBAL_COMMAND_SET_ROOT_TABLE_POINTER_FLAG);
43
44    while (MV_IS_FLAG_SET(MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS),
45
46                          VTD_GLOBAL_STATUS_ROOT_TABLE_POINTER_STATUS_FLAG) == FALSE)
47
48    {
49
50        CpuPause();
51
52    }
53
54  
55
56	//
57	// 这一步也是Intel手册要求的
58	// 设置新的Root Table后,必须失效Context Cache,否则会读到旧页表
59	//
60
61    InvalidateContextCache(DmarUnit);
62
63    InvalidateIotlb(DmarUnit);
64
65  
66
67    //
68    // 将TE位(Translation Enable)置1,开启DMA remapping
69    //
70
71    MV_HOST_DEBUG("Enabling DMA-remapping");
72
73    status = MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS);
74
75    MmioWrite32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_COMMAND,
76
77                status | VTD_GLOBAL_COMMAND_TRANSLATION_ENABLE_FLAG);
78
79    while (MV_IS_FLAG_SET(MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS),
80
81                          VTD_GLOBAL_STATUS_TRANSLATION_ENABLE_STATUS_FLAG) == FALSE)
82
83    {
84
85        CpuPause();
86
87    }
88
89}

这一步完成之后,DMA attack尝试读取游戏内存就会通过地址转换重定向到其他地方(可能是空地址区域);同时,也可以记录尝试读取游戏内存的DMA,实现检测的效果。 需要注意的一点是,如果反作弊其他的操作影响了EPT页表,需要将这一影响同步至VT-d,如下:

 1VOID
 2ApplyTranslationChangesToVtd (
 3    IN CONST VTD_CONTEXT* VtdContext
 4    )
 5
 6{
 7
 8    WriteBackDataCacheRange(VtdContext->SecondLevelTableBase, VtdContext->SecondLevelTableSize);
 9
10  
11
12    for (UINT64 i = 0; i < VtdContext->DmarUnitCount; ++i)
13
14    {
15
16        CONST DMAR_UNIT_INFORMATION* dmarUnit;
17
18        dmarUnit = &VtdContext->DmarUnits[i];
19
20        InvalidateContextCache(dmarUnit);
21
22        InvalidateIotlb(dmarUnit);
23
24    }
25
26}

VT-d 卸载

DisableOrCheckVtd

完美将统计VT-d状态的checker和关闭VT-d的禁用器写在了一个函数

operationMode == 2时,清除pass through条目 image-20251115192702198 关闭VT-d image-20251115192705639 在tanda Statoshi的项目中给出了这些功能函数的实现,比起IDA有更好的可读性:

 1STATIC
 2VOID
 3DisableDmaRemapping (
 4    IN CONST DMAR_UNIT_INFORMATION* DmarUnit
 5    )
 6{
 7
 8    UINT32 status;
 9    UINT32 command;
10  
11    //
12    // See: 11.4.4.1 Global Command Register
13    //
14
15    MV_HOST_DEBUG("Disabling IOMMU");
16
17    status = MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS);
18
19    status &= 0x96FFFFFF; // Reset the one-shot bits
20
21    command = (status & ~VTD_GLOBAL_COMMAND_TRANSLATION_ENABLE_FLAG);
22
23    MmioWrite32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_COMMAND, command);
24
25    while (MV_IS_FLAG_SET(MmioRead32(DmarUnit->RegisterBaseVa + VTD_GLOBAL_STATUS),
26
27                          VTD_GLOBAL_STATUS_TRANSLATION_ENABLE_STATUS_FLAG) != FALSE)
28
29    {
30
31        CpuPause();
32
33    }
34
35}