BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

基于虚拟化的安全性 - 第2篇:内核通信

| 作者 Adrien Chevalier 关注 0 他的粉丝 ,译者 刘志勇 关注 2 他的粉丝 发布于 2017年3月9日. 估计阅读时间: 21 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

AMOSSYS Security团队的Adrien Chevalier撰写了一系列文章,讲述了基于虚拟化的安全。

作者在文章中使用了CC协议,InfoQ翻译本文。

本文是第二篇文章,涉及基于虚拟化的安全和设备保护功能。在第一篇中,我们介绍了从Windows引导加载程序到VTL0启动的系统引导过程。在这篇文章中,我们将解释VTL0和VTL1之间如何进行内核通信。当它们使用超级调用进行通信时,我们将首先描述Hyper-V的超级调用实现,然后内核如何使用它们进行通信。为了完成这一描述,我们将列出这项工作中我们发现的所有不同的超级调用和安全服务调用。

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)

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 ShvlpVtl1Entry 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

VTL0到VTL1的转换

几乎所有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内核保护。


感谢魏星对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT