{aa35aa} 的 Adrien Chevalier 撰写了 {aa34aa},讲述了基于虚拟化的安全。
作者在文章中使用了 CC 协议,InfoQ 翻译本文。
VTL0 和 VTL1 之间的内核通信使用 Hyper-V 超级调用。使用 VMCALL 指令执行这些超级调用,其中在 RCX 寄存器中具有超级调用号,并且 RDX 指向包含参数的 Guest 物理页(Guest Physical Page,GPA)。如果设置了 0x10000 RCX 位,超级调用是一个 "FAST" 超级调用,参数(和返回值)存储在 XMM 寄存器中。为了执行调用,Windows 使用 "hypercall trampoline",这是一个小的 fastcall 例程,用来执行 VMCALL 和 RET。
该例程存储在 "超级调用页面" 中。此页面包含 5 个 trampoline,由 Hyper-V 在启动时提供给 winload.efi,这将在 VTL0 和 VTL1 地址空间中复制它。这五个 trampoline 之间的主要区别是,第一个只是一个 VMCALL/RET,但是接下来的四个(它们被连续定义)将 RCX 存储到 RAX,并且在 RCX 中强制使用固定值。第二个和第三个将 RCX 强制为 0x11,下一个强制为 0x12。
这四个 trampoline 实际上由不同的 VTL 使用。每个内核可以使用专用的超级调用来 "询问"Hyper-V 的 0xD0002 虚拟处理器寄存器值(可以使用其标识符来查询或设置的内部 Hyper-V 值),其将返回两个偏移量。这些偏移量与超级页面相关,并且将被内核用来调用正确的 trampoline。实际上,VTL1 和 VTL0 使用 0x11trampoline 来相互通信,并且 VTL1 使用 0x12trampoline 来完成其初始化。
超级调用页面的内容可以表示为:
- VMCALL <-- first trampoline
- RET
- MOV RAX, RCX <-- second one (enforces 0x11)
- MOV RCX, 0x11
- VMCALL
- RET
- MOV RAX, RCX <-- third (0x11)
- MOV RCX, 0x11
- VMCALL
- RET
- MOV RAX, RCX <-- fourth (0x12)
- MOV RCX, 0x12
- VMCALL
- RET
- MOV RAX, RCX <-- fifth (0x12)
- MOV RCX, 0x12
- VMCALL
- RET
- db 0x00
- db 0x00
- db 0x00
- db 0x00
- db 0x00
- db 0x00
- ...
- ...
- db 0x00
因此,我们有五个 trampoline,偏移量为 0x00、0x04、0x0F、0x1D 和 0x28。注意,可以使用 WinDbg 轻松地在 Windows 崩溃转储中获取其内容,或在 Hyper-V 二进制文件(hvix64.exe/hvax64.exe,适用于 Intel/AMD)的内部代码找到。
备注:几个超级调用可以指定 RCX 最高有效位 DWORD 的 12 个最低有效位中的数据大小。这些大小不是以字节为单位的数据长度,而是与当前调用相关,且可能表示入口计数等。
对于一个超级调用的例子,VTL1 的 ShvlProtectContiguousPageshypercall(12) 参数是遵循此方案的结构体:
- typedef struct _param {
- ULONGLONG infinite; // always 0xFFFFFFFFFFFFFFFF
- ULONG protection_asked; // 0xD EXECUTE, 0xF WRITE
- ULONG zero_value; // always 0
- ULONGLONG pfns[]; // entries count is set in the hypercall number
- }
- param;
为了告诉 Hyper-Vpfns 参数的大小,RCX 的高位 DWORD 必须包含其元素数量。对于只有一个入口和 FAST 超级调用而言,RCX 值因此必须为 0x10010000C。
两个 VTL 能够执行多个超级调用,以便与 Hyper-V 进行通信。它们可以执行相同的超级调用,但 Hyper-V 将拒绝一些来自 VTL0 调用的超级调用。两个 VTL 还使用一个专用的超级调用来彼此通信。总结见图 1。
- 图1:Hypercalls类别
让我们首先描述 "VTL1 到 Hyper-V" 的超级调用(绿色)。然后我们将描述 0x11 的超级调用。
VTL1 可以使用三种超级 trampoline:
后两者使用 0xD0002 虚拟寄存器得到(通过 ShvlpGetVpRegisterreturn 返回值的 24 个最低有效位,每个偏移量为 12 位的长度)。这两个偏移指向 0x11 和 0x12trampoline。
顺便说一句,VTL0 NTOS 内核使用同一进程获得其 HvlpVsmVtlCallCodeVa 值(用于 VTL0 到 VTL1 通信的超级 trampoline),但获得是相反的结果。正是因为 Hyper-V 会根据 VTL 所寄宿 VM 的不同而返回不一样的值,所以我们认为任何 VM 使用 trampolines 访问同样的超级调用页面时,都会请求一遍虚拟寄存器的值。
下表是每个 trampoline 可能的 VTL1 超级调用:
ShvlpVtlCallHypercall number | Caller - usage |
0x12 | ShvlInitSystem – end of VTL1 initialization? |
ShvlpVtlReturn (VTL0 returns/calls)
Hypercall number | Caller - usage |
0x11 | SkCallNormalMode/SkpPrepareForReturnToNormalMode – returns to NTOS / calls NTOS 返回 NTOS / 调用 NTOS |
ShvlpHypercallCodePage (HyperV)
Hypercall number |
Caller - usage |
0x2 |
ShvlFlushEntireTb, etc. - Translation buffer flushs |
0x3 |
Translation buffer flushs |
0xB |
SkpgPatchGuardCallbackRoutine – (H) HyperGuard delayed routines registering |
0x15 |
|
0xC |
ShvlProtectContiguousPages – Memory protection modification |
0xD |
ShvlInitSystem – Called just before , seems to send several settings to HV |
0xF |
ShvlEnableVpVtl – Sends settings to HV, and notably the function pointer |
0x50 |
ShvlGetVpRegister – Gets a virtual processor (VP) register |
0x51 |
ShvlSetVpRegister – Sets a virtual processor register |
0x52 |
SkpgTranslateVa – (H) VTL0 memory access by HyperGuard |
0x86 |
ShvlPrepareForHibernate |
0x87 |
ShvlNotifyRootCrashDump |
0x94 |
BugCheck |
0x8E |
LiveDumpCollect |
0x97 |
SkpGetPageList |
0xAE |
ShvlSetGpaPageAttributes – 1607 build: changes a GPA attributes, seems to only been used on VTL1 memory |
几乎所有 NTOS"Vsl" 前缀函数最终以 VslpEnterIumSecureMode 结尾,带有安全服务调用号(Secure Service Call Number,SSCN)。此函数调用 HvlSwitchToVsmVtl1,它使用 HvlpVsmVtlCallVa 超级调用 trampoline(常规 hyper-V 超级调用使用 HvcallCodeVatrampoline)。然后将 SSCN 复制到 RAX 中,并将 RCX 值设置为 0x11。
Hyper-V 将 0x11 超级调用分派到 securekernel.exe 函数 SkpReturnFromNormalMode 中,然后调用 IumInvokeSecureService(实际上我们不确定 IumInvokeSecureService 是否被直接调用,我们认为必须首先调用 SkpReturnFromNormalMode,以使 IumInvokeSecureService 在安全服务调用完成后返回到 VTL0)。IumInvokeSecureService 主要是一个大的 switch/case 块,它处理所有的 SSCN。
最后,调用 SkCallNormalMode,以 SkpPrepareForReturnToNormalMode 为结尾。实际上,安全内核的 NTOS 调用可以被认为是对 VTL0 的 "伪返回",因为它们也包含在 0x11 超级调用中。
我们已经从下面的阵列中确认了来自 VTL0 的所有可能的 SSCN。对于每一个,我们指出了被调用的函数,它们的名字通常是不言自明的。相应的参数必须通过逆向 VTL0 调用者或 VTL1 调用源来确定。
SSCN |
Called function |
1 | SkmmInitializeUserSharedData / SkInitSystem |
2 | SkeStartProcessor |
3 | SkpsRegisterSystemDll |
4 | InterlockedCompareExchange(IumSystemProcessRegistered) / PsIumSystemProcess manipulation |
5 | SkmmCreateProcessAddressSpace/SkobCreateHandle |
6 | SkeInitializeProcess |
7 | IumCreateThread |
8 | SkiTerminateAllThreads |
9 | IumTerminateThread |
10 | SkeRundownProcess |
11 | SkpsIsProcessDebuggingEnabled / SkmmDisableProcessMemoryProtection |
12 | |
13 & 14 | SkmmMapMdl / IumpGetSetContext |
15 | SkeReferenceProcessByHandle / SkmmMapDataTransfer / SkpEncryptWithTrustletKey |
16 | SkeReferenceProcessByHandle / |
17 | SkRetrieveMailbox |
18 | SkIstTrustletRunning |
19 | SkmmCreateSecureAllocation |
20 | SkmmMapDataTransfer / SkmiFillSecureAllocation |
21 | SkmmConvertSecureAllocationToCatalog |
SkmmCreateSecureImageSection | |
SkmmFinializeSecureImageHash | |
SkmmFinishSecureImageValidation | |
SkmmPrepareImageRelocations | |
SkmmRelocateImage | |
SkobCloseHandleEx | |
SkmmValidateDynamicCodePages | |
SkmmTransferImageVersionResource | |
30 | EntropyProvideData / BCryptGenRandom |
31 | SkpEncryptHiberData / SkpSetHiberCrashState |
32 | SkpSetHiberCrashState / SkpgHibernateActive = 0 / SkFinalizePageEncryption |
33 | SkmmConfigureDynamicMemory |
34 | IumConnectSwInterrupt |
35 | = 0x3000 |
36 | SkLiveDumpStart |
37 | SkpLiveDumpContext / |
38 | SkLiveDumpSetupBuffer |
39 | SkLiveDumpFinalize |
40 | SkpLiveDumpFreeContext / SkpReleaseLiveDumpLock |
41 | SkNotifyPowerState |
42 | IumDispatchQueryProfileInformation |
192 | SkeReferenceProcessByHandle |
193 | SkmmValidateSecureImagePages |
208 | SkmmInitSystem / IumpInitializeSystem |
209 | SkpWorkItemList / |
210 | SkmiReleaseUnprivilegedPagesInRange / SmiReserveNtAddressRange |
211 | SkmmApplyDynamicRelocations |
212 | SkEtwEnableCallback |
224 | SkiAttachProcess / SkmiFlushAddressRange |
225 | SkmmFastFlushRangeList |
226 | SkmmSlowFlushRangeList |
227 | SkmmRemoveProtectedPage |
228 | SkmmCopyProtectedPage |
229 | SkmmMakeProtectedPageWritable |
230 | SkmmMakeProtectedPageExecutable |
231 | (H) Gets *Skmi *flags |
232 | SkhalEfiInvokeRuntimeService |
233 | SkLiveDumpCOllect |
234 | SkmmRegisterFailureLog |
235 | SkPrepareForHibernate |
236 | SkPrepareForCrashDump |
237 | SkhalpEfiRuntimeInitialize / SkhalpReportBugCheckInProgress |
238 & 240 | Returns an error code |
241 | SkKsrCall |
Otherwise | SkeBugCheckEx(0x121, 0xFFFFFFFFC000001C, , 0, 0) |
正如你所见,几个被调用的函数是未知的。这是因为它们没有执行明显的调用,我们没有花时间去继续分析。
本文描述了基于虚拟化的安全 VTL0-VTL1 如何进行内核通信。
来源: http://www.infoq.com/cn/articles/virtualization-based-security-part02