@sambodhi
        
        2017-02-20T08:18:09.000000Z
        字数 6485
        阅读 3104
    AMOSSYS Security团队的Adrien Chevalier撰写了一系列文章,讲述了基于虚拟化的安全。
作者在文章中使用了CC协议,InfoQ翻译本文。
本文是第二篇文章,涉及基于虚拟化的安全和设备保护功能。在第一篇中,我们介绍了从Windows引导加载程序到VTL0启动的系统引导过程。在这篇文章中,我们将解释VTL0和VTL1之间如何进行内核通信。当它们使用超级调用进行通信时,我们将首先描述Hyper-V的超级调用实现,然后内核如何使用它们进行通信。为了完成这一描述,我们将列出这项工作中我们发现的所有不同的超级调用和安全服务调用。
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:
ShvlpHypercallCodePage, 相当于NTOS HvlpHypercallCodePage(偏移0),并指向第一个trampoline;ShvlpVtlReturn,它将RCX强制为0x11并允许VTL0和VTL1通信;ShvlpVtlCall,它将RCX强制为0x12,并且仅在VTL1初始化期间使用。后两者使用0xD0002虚拟寄存器得到(通过ShvlpGetVpRegisterreturn返回值的24个最低有效位,每个偏移量为12位的长度)。这两个偏移指向0x11和0x12trampoline。
顺便说一句,VTL0 NTOS内核使用同一进程获得其HvlpVsmVtlCallCodeVa值(用于VTL0到VTL1通信的超级trampoline),但获得是相反的结果。正是因为Hyper-V会根据VTL所寄宿VM的不同而返回不一样的值,所以我们认为任何VM使用trampolines访问同样的超级调用页面时,都会请求一遍虚拟寄存器的值。
下表是每个trampoline可能的VTL1超级调用:
ShvlpVtlCall
| Hypercall 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)
Remark: We wrote an (H) when realizing an hypothesis.
| 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 ShvlEnableVpVtl, seems to send several settings to HV | 
| 0xF | ShvlEnableVpVtl – Sends settings to HV, and notably the ShvlpVtl1Entryfunction 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 | Unknown | 
| 13 & 14 | SkmmMapMdl / IumpGetSetContext | 
| 15 | SkeReferenceProcessByHandle / SkmmMapDataTransfer / SkpEncryptWithTrustletKey | 
| 16 | SkeReferenceProcessByHandle / Unknown | 
| 17 | SkRetrieveMailbox | 
| 18 | SkIstTrustletRunning | 
| 19 | SkmmCreateSecureAllocation | 
| 20 | SkmmMapDataTransfer / SkmiFillSecureAllocation | 
| 21 | SkmmConvertSecureAllocationToCatalog | 
| 22 | SkmmCreateSecureImageSection | 
| 23 | SkmmFinializeSecureImageHash | 
| 24 | SkmmFinishSecureImageValidation | 
| 25 | SkmmPrepareImageRelocations | 
| 26 | SkmmRelocateImage | 
| 27 | SkobCloseHandleEx | 
| 28 | SkmmValidateDynamicCodePages | 
| 29 | SkmmTransferImageVersionResource | 
| 30 | EntropyProvideData / BCryptGenRandom | 
| 31 | SkpEncryptHiberData / SkpSetHiberCrashState | 
| 32 | SkpSetHiberCrashState / SkpgHibernateActive = 0 / SkFinalizePageEncryption | 
| 33 | SkmmConfigureDynamicMemory | 
| 34 | IumConnectSwInterrupt | 
| 35 | Unknown = 0x3000 | 
| 36 | SkLiveDumpStart | 
| 37 | SkpLiveDumpContext / Unknown | 
| 38 | SkLiveDumpSetupBuffer | 
| 39 | SkLiveDumpFinalize | 
| 40 | SkpLiveDumpFreeContext / SkpReleaseLiveDumpLock | 
| 41 | SkNotifyPowerState | 
| 42 | IumDispatchQueryProfileInformation | 
| 192 | SkeReferenceProcessByHandle | 
| 193 | SkmmValidateSecureImagePages | 
| 208 | SkmmInitSystem / IumpInitializeSystem | 
| 209 | SkpWorkItemList / Unknown | 
| 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如何进行内核通信。
如果你想要更多的Hyper-V相关信息,你也可以阅读这两篇文章:
我们计划将发表第三篇关于VBS的文章,将重点介绍HVCI内部,特别是*W^X*VTL0内核保护。