DMAR表定位
InitializeAcpiTable
根据DMAR表头的signiture定位

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

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条目
关闭VT-d
在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}