一、前言
现今的Windows操作系统已经融合了虚拟化技术作为自身的核心功能,Windows平台下的虚拟化技术的应用也不仅限于Hyper-V虚拟化软件的应用,在一些用于保护Windows系统的安全功能中,虚拟化技术的使用也变得尤为重要。现在我们就来简单探索下Windows的VTL机制和IUM进程,这两种基于虚拟化技术的安全功能。
二、VTL机制和IUM进程介绍
Virtual Secure Mode(VSM)最早在Windows10和Windows Server 2016中被引入,是基于Windows平台下的虚拟化技术的安全功能,使用的是Hyper-V的虚拟化组件。
VSM的主要作用是,在这个安全功能开启的情况下,即使主机上的内核或者驱动程序(即Ring0层)受到攻击,受到VSM体系保护的数据依然可以保持安全,保证数据不被篡改或者无法被访问,甚至攻击者处于Ring0权限下。
与我们熟知的VMware或者QEMU虚拟化软件不同,Hyper-V的虚拟化实现是裸金属架构的,即Hypervisor层的组件运行在实体的硬件上,而无论是所谓的Guest(Child Parition)或者Host(Root Parition)都是Hypervisor层之上抽象出来的。
那么,Hyper-V的技术架构是VSM功能的基础,无论是Guest还是Host,实际上都可以通过Hypervisor来控制不同分区对硬件资源的使用能力从而实现在VSM中提及的安全功能。
VSM功能的引入同时引入了一个新的概念——VTL。
Virtual Trust Levels(VTL),VSM通过VTL来实现和维护隔离。VTL是有层级概念的,例如熟知的Ring0~3层级权限。但是与Ring0~3概念不同的是,VTL的层级权限是级别高的权限要高于级别低的权限。举个例子:VTL0的权限 < VTL1的权限;VTL1的权限 < VTL2的权限。在微软对VTL的代码实现里,最多支持16个级别VTL,但是截止到目前的关于VTL的代码中,微软只实现了2个级别的VTL。
为了更直观的展示VTL的作用,这里使用了微软官方给出的IUM架构体系图。
从图中可以看到,我们所熟知的Windows内核态和用户态都属于VTL0级别下,而SecureKernel和运行在同VTL层级下的用户态进程属于VTL1级别下。此时,假如我们在VTL0种进行内核调试,都无法修改VTL1层级下的用户态进程空间内存。这个例子说明了低级别VTL无法影响到高级别VTL内存空间,不同的VTL级别之间有着明显的隔离边界。
有了VTL这个概念之后,下面我们来介绍下什么是Isolated User Mode(IUM)进程。通俗来讲,IUM进程就是运行在VTL1层级下的用户态进程。最简单的例子就是LSAIso.exe进程,当这个进程运行在VTL1层级下时,无论是在用户态还是内核态都无法修改这个进程的内存空间,同样地,对其进行调试也是无法进行的。
下面,我们一起来探索VTL机制和IUM进程(以及如何调试IUM进程)的一些内部细节。
三、必要的硬件虚拟化知识(Intel)
在探索之前,我们还需要简单的了解下CPU硬件虚拟化知识,这里我们以Intel处理器(Intel VT-x)加以介绍。
这里首先要介绍的是Intel VT-x和VMX是什么。Intel VT-x的全称是Intel Virtualization Technology for x86,这个东西就是所谓的Intel硬件虚拟化技术,而VMX是其实现的架构,全称为Virtual-Machine Extensions。在VMX下引入了两个概念:VMX root operation和VMX non-root operation模式。
VMX的root和non-root operation模式可以简单地理解成,hypervisor也就是虚拟化层,即VM管理者(VMM, virtual machine monitor)和Guest所使用的环境。这两种模式可以互相转换,当从VMX的root模式转换到non-root模式时,这个行为被称作VM-entry。那么当VMX的non-root模式切换到root模式,这个行为被称作VM-Exit。
假设目前VMX处于non-root状态,此时是在执行Guest中的代码,如果执行时遇到了比如CPUID,读写MSR寄存器等操作时,Guest操作系统会被暂停,并产生Vm-Exit事件,同时陷落入VMM,即root operation状态中。VMM根据不同的VM-Exit原因来处理(模拟)此时Guest的执行指令,处理数据并返回结果,最后vmm通过执行vmresume指令重新让Guest系统继续运行,此时的VMX状态又变回了non-root状态。
在VMX进行root和non-root operation状态切换时,VMCS(Virtual Machine Control Structure)用来配置此时发生切换的处理器状态和执行的环境。在Hyper-V的虚拟化环境中,每个虚拟处理器都对应着一个或者多个VMCS。
VMCS中有很多字段,对应着当前虚拟处理器的状态信息,比如用于记录当前VM-Exit信息的"Exit reason"字段,通过阅读Intel手册发现这个字段对应的ID是0x4402。因为VMCS中的字段信息无法直接通过读取物理内存的方式读取到,所以这里必须使用Intel给出的指令集vmread/vmwrite来读写对应的字段内容。
#define EXIT_REASON_EXCEPTION_NMI 0
#define EXIT_REASON_EXTERNAL_INTERRUPT 1
#define EXIT_REASON_TRIPLE_FAULT 2
#define EXIT_REASON_INIT_SIGNAL 3
#define EXIT_REASON_SIPI_SIGNAL 4
#define EXIT_REASON_INTERRUPT_WINDOW 7
#define EXIT_REASON_NMI_WINDOW 8
#define EXIT_REASON_TASK_SWITCH 9
#define EXIT_REASON_CPUID 10
#define EXIT_REASON_HLT 12
#define EXIT_REASON_INVD 13
#define EXIT_REASON_INVLPG 14
#define EXIT_REASON_RDPMC 15
#define EXIT_REASON_RDTSC 16
#define EXIT_REASON_VMCALL 18
#define EXIT_REASON_VMCLEAR 19
#define EXIT_REASON_VMLAUNCH 20
#define EXIT_REASON_VMPTRLD 21
#define EXIT_REASON_VMPTRST 22
#define EXIT_REASON_VMREAD 23
#define EXIT_REASON_VMRESUME 24
#define EXIT_REASON_VMWRITE 25
#define EXIT_REASON_VMOFF 26
#define EXIT_REASON_VMON 27
#define EXIT_REASON_CR_ACCESS 28
#define EXIT_REASON_DR_ACCESS 29
#define EXIT_REASON_IO_INSTRUCTION 30
#define EXIT_REASON_MSR_READ 31
#define EXIT_REASON_MSR_WRITE 32
#define EXIT_REASON_INVALID_STATE 33
#define EXIT_REASON_MSR_LOAD_FAIL 34
#define EXIT_REASON_MWAIT_INSTRUCTION 36
#define EXIT_REASON_MONITOR_TRAP_FLAG 37
#define EXIT_REASON_MONITOR_INSTRUCTION 39
#define EXIT_REASON_PAUSE_INSTRUCTION 40
#define EXIT_REASON_MCE_DURING_VMENTRY 41
#define EXIT_REASON_TPR_BELOW_THRESHOLD 43
#define EXIT_REASON_APIC_ACCESS 44
#define EXIT_REASON_EOI_INDUCED 45
#define EXIT_REASON_GDTR_IDTR 46
#define EXIT_REASON_LDTR_TR 47
#define EXIT_REASON_EPT_VIOLATION 48
#define EXIT_REASON_EPT_MISCONFIG 49
#define EXIT_REASON_INVEPT 50
#define EXIT_REASON_RDTSCP 51
#define EXIT_REASON_PREEMPTION_TIMER 52
#define EXIT_REASON_INVVPID 53
#define EXIT_REASON_WBINVD 54
#define EXIT_REASON_XSETBV 55
#define EXIT_REASON_APIC_WRITE 56
#define EXIT_REASON_RDRAND 57
#define EXIT_REASON_INVPCID 58
#define EXIT_REASON_VMFUNC 59
#define EXIT_REASON_ENCLS 60
#define EXIT_REASON_RDSEED 61
#define EXIT_REASON_PML_FULL 62
#define EXIT_REASON_XSAVES 63
#define EXIT_REASON_XRSTORS 64
#define EXIT_REASON_UMWAIT 67
#define EXIT_REASON_TPAUSE 68
#define EXIT_REASON_BUS_LOCK 74
#define EXIT_REASON_NOTIFY 75
通过vmread读取0x4402 id字段的内容,就可以得到VM-Exit的原因,VMM根据上图中这些若干的原因进行处理,完成处理后,将结果改写到例如Guest中的寄存器中,此时也需要通过vmwrite改写其中关于Guest寄存器信息的字段,最后通过vmresume将控制权交还Guest。
这里还要介绍一对重要的指令:vmptrld/vmptrst,vmptrld这个指令是用来从内存中加载一个64位的物理地址作为当前VMCS指针;vmptrst是用来将当前的VMCS指针保存到内存中。vmptrld是用来切换当前VMCS的指令,而vmread/vmwrite所读写的也是当前VMCS。
下面要介绍的是SLAT(Second Level Address Translation),上面介绍了通过VMX root、non-root operation和VMCS来控制虚拟处理器,那么在虚拟机中的内存也需要和宿主机/其他虚拟机的内存做隔离。广为人知的四级页表转换已经无法满足这个需求,所以在此Intel提出了二级地址转换这个功能。
如quarkslab博客中文章中使用的图片所示,下图展现了SLAT完整的转换流程。
首先,在Guest中的虚拟地址通过四级页表转化成了Guest中的物理地址(GPA),然后GPA通过相似的四级页表转换,转换成了Host中的物理地址(SPA)。这样就实现了Guest和Host以及其他Guest之间的内存隔离,并且在Guest操作系统中是完全透明的,因为在GPA到SPA转换中,CPU是处于VMX root operation模式下的。
这里要提到EPT这个东西,EPT(Extend Page Table)的作用和CR3寄存器是一样的,用来确定页目录基址,完成GPA到SPA转换。我们可以通过读取当前VMCS中的EPT pointer字段(ID:0x201a)获取到EPT的指针。
这里举个简单的例子:首先我们准备一个Guest,这里我使用的是Linux,编写一个驱动,驱动主要是打印了一个buffer所在的物理地址的信息,如下。
$dmesg –w|grep Hello
[+]phy address of string:"AAAAAAAAAAAA......" 0x96eee000
通过对Hyper-V的hypervisor进行逆向和调试,获得当前Guest所在的VMCS中的ept指针信息
Breakpoint 4 hit
hv+0x2a0b35:
fffff800`078ceb35 b91a200000 mov ecx,201Ah
0: kd> r rax
rax=00000001ea40901e
下面我们可以使用!vtop这个windbg命令快速求出Guest中我们的驱动的buffer的GPA对应SPA
hv+0x23b7a0:
fffff800`078697a0 cc int 3
1: kd> !vtop 1ea40901e 0x96eee000
Amd64VtoP: Virt 00000000`96eee000, pagedir 00000001`ea409000
Amd64VtoP: PML4E 00000001`ea409000
Amd64VtoP: PDPE 00000001`ea408010
Amd64VtoP: PDE 00000001`4da3a5b8
Amd64VtoP: PTE 00000001`4daf3770
Amd64VtoP: Mapped phys 00000001`972ee000
Virtual address 96eee000 translates to physical address 1972ee000.
1: kd> !db 1972ee000
#1972ee000 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee010 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee020 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee030 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee040 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee050 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee060 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
#1972ee070 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
可以看到GPA:0x96eee000 对应的SPA:0x1972ee000。
四、VTL内部细节研究
这里先看一下一个简单的vmcall处理函数:HvDeletePartition,由于创建或者删除分区的权限只有root分区才有,所以在调用vmcall指令对应的功能时,要先检测当前陷入VM-Exit的分区的权限,以保证Child分区不会影响到Root分区。
.text:FFFFF800003528C0 ; __int64 __fastcall vmcall_0x43_HvDeletePartition_sub_FFFFF800003528C0(_QWORD *)
.text:FFFFF800003528C0 vmcall_0x43_HvDeletePartition_sub_FFFFF800003528C0 proc near
.text:FFFFF800003528C0 ; DATA XREF: .pdata:FFFFF80000028D54↑o
.text:FFFFF800003528C0 ; CONST:FFFFF8000002B648↑o
.text:FFFFF800003528C0
.text:FFFFF800003528C0 arg_0 = qword ptr 8
.text:FFFFF800003528C0
.text:FFFFF800003528C0 000 mov [rsp+arg_0], rbx
.text:FFFFF800003528C5 000 push rdi
.text:FFFFF800003528C6 008 sub rsp, 20h
.text:FFFFF800003528CA 028 mov rax, gs:103E0h
.text:FFFFF800003528D3 028 mov rdi, rcx
.text:FFFFF800003528D6 028 bt qword ptr [rax+0F0h], 20h ; ' '
.text:FFFFF800003528DF 028 jb short loc_FFFFF800003528E8
.text:FFFFF800003528E1 028 mov ebx, 6
.text:FFFFF800003528E6 028 jmp short loc_FFFFF80000352905
.text:FFFFF800003528E8 ; ---------------------------------------------------------------------------
.text:FFFFF800003528E8
.text:FFFFF800003528E8 loc_FFFFF800003528E8: ; CODE XREF: vmcall_0x43_HvDeletePartition_sub_FFFFF800003528C0+1F↑j
.text:FFFFF800003528E8 028 mov rcx, [rcx]
.text:FFFFF800003528EB 028 call sub_FFFFF8000029A71C
.text:FFFFF800003528F0 028 movzx ebx, ax
.text:FFFFF800003528F3 028 test ax, ax
.text:FFFFF800003528F6 028 jnz short loc_FFFFF80000352905
.text:FFFFF800003528F8 028 mov r8, [rdi]
.text:FFFFF800003528FB 028 mov edx, 4102h
.text:FFFFF80000352900 028 call sub_FFFFF8000027FE6C
.text:FFFFF80000352905
.text:FFFFF80000352905 loc_FFFFF80000352905: ; CODE XREF: vmcall_0x43_HvDeletePartition_sub_FFFFF800003528C0+26↑j
.text:FFFFF80000352905 ; vmcall_0x43_HvDeletePartition_sub_FFFFF800003528C0+36↑j
.text:FFFFF80000352905 028 movzx eax, bx
.text:FFFFF80000352908 028 mov rbx, [rsp+28h+arg_0]
.text:FFFFF8000035290D 028 add rsp, 20h
.text:FFFFF80000352911 008 pop rdi
.text:FFFFF80000352912 000 retn
从这个HvDeletePartition函数中可以看到,代码先从gs:[0x103E0]处拿到了一个指针,然后对这个指针offset 0xF0处的64bit的数据进行比特位检测运算,检测的比特位是第32位。通过逆向得知,这个64位数据就是Hyper-V Hypervisor TLFS中的HV_PARTITION_PRIVILEGE_MASK数据,比较幸运的是,微软公开了这个数据的数据结构,下面是它的参考。
typedef struct
{
// Access to virtual MSRs
UINT64 AccessVpRunTimeReg:1;
UINT64 AccessPartitionReferenceCounter:1;
UINT64 AccessSynicRegs:1;
UINT64 AccessSyntheticTimerRegs:1;
UINT64 AccessIntrCtrlRegs:1;
UINT64 AccessHypercallMsrs:1;
UINT64 AccessVpIndex:1;
UINT64 AccessResetReg:1;
UINT64 AccessStatsReg:1;
UINT64 AccessPartitionReferenceTsc:1;
UINT64 AccessGuestIdleReg:1;
UINT64 AccessFrequencyRegs:1;
UINT64 AccessDebugRegs:1;
UINT64 AccessReenlightenmentControls:1
UINT64 Reserved1:18;
// Access to hypercalls
UINT64 CreatePartitions:1; //这里就是第32位要检测的bit位
UINT64 AccessPartitionId:1;
UINT64 AccessMemoryPool:1;
UINT64 AdjustMessageBuffers:1;
UINT64 PostMessages:1;
UINT64 SignalEvents:1;
UINT64 CreatePort:1;
UINT64 ConnectPort:1;
UINT64 AccessStats:1;
UINT64 Reserved2:2;
UINT64 Debugging:1;
UINT64 CpuManagement:1;
UINT64 Reserved:1
UINT64 Reserved:1;
UINT64 Reserved:1;
UINT64 AccessVSM:1;
UINT64 AccessVpRegisters:1;
UINT64 Reserved:1;
UINT64 Reserved:1;
UINT64 EnableExtendedHypercalls:1;
UINT64 StartVirtualProcessor:1;
UINT64 Reserved3:10;
} HV_PARTITION_PRIVILEGE_MASK;
在Windbg中表现为:
14: kd> dq gs:[103e0]
0020:00000000`000103e0 ffffe800`00001000 ffffe800`01746050
0020:00000000`000103f0 00000080`01c00000 00000000`0009b922
0020:00000000`00010400 00000000`00000000 00000000`00000000
0020:00000000`00010410 00000000`00000000 00000000`00000000
0020:00000000`00010420 00000000`00000001 80000000`00000000
0020:00000000`00010430 00000000`00000000 80000000`00000000
0020:00000000`00010440 00000000`00000000 00000000`00000000
0020:00000000`00010450 00000000`00000000 00000000`00000000
14: kd> dq ffffe800`00001000+f0
ffffe800`000010f0 002bb9ff`00003fff ffffe800`00000000
ffffe800`00001100 00000000`00000000 ffffe800`0064acd0
ffffe800`00001110 00000017`00000018 00000000`00000000
ffffe800`00001120 ffffe800`0067c050 ffffe800`015c5130
ffffe800`00001130 ffffe800`015e9050 ffffe800`01607050
ffffe800`00001140 ffffe800`01624050 ffffe800`01641050
ffffe800`00001150 ffffe800`0165e050 ffffe800`0167b050
ffffe800`00001160 ffffe800`01698050 ffffe800`016b5050
14: kd> .formats 2bb9ff`00003fff
Evaluate expression:
Hex: 002bb9ff`00003fff
Decimal: 12307928866373631
Octal: 0000535637740000037777
Binary: 00000000 00101011 10111001 11111111 00000000 00000000 00111111 11111111
Chars: .+....?.
Time: Mon Jan 2 14:54:46.637 1640 (UTC + 8:00)
Float: low 2.29575e-041 high 4.01565e-039
Double: 7.7117e-308
通过windbg结果可以看到此时的分区权限拥有比如创建分区的权限等较多的权限,它目前就是root分区。
可以看到,HvDeletePartition函数先检测了当前的分区是否拥有创建分区权限,再进行下一步操作。这一步给我们的逆向思路提供了经验,gs:[103e0]中存储的很有可能就是代表partition的大结构体,其中offset 0xF0处代表这个分区的权限信息。通过对HvCreateVp这个函数的逆向,我们得出了进一步的信息。这里省略对HvCreateVp的逆向过程,感兴趣的小伙伴可以自行研究,很简单。
逆向得出,vmcall_0x4e_HvCreateVp_sub_FFFFF80000294100 –> sub_FFFFF800002A1D28,sub_FFFFF800002A1D28函数中会更新目前的partition中的虚拟处理器的信息。如下所示。
.text:FFFFF800002A1E04
.text:FFFFF800002A1E04 loc_FFFFF800002A1E04:
.text:FFFFF800002A1E04 0D8 test byte ptr [rbx+0E0h], 1
.text:FFFFF800002A1E0B 0D8 mov rdi, [rbx+r13*8+120h]
.text:FFFFF800002A1E13 0D8 jz short loc_FFFFF800002A1E37
.text:FFFFF800002A1E13
上述代码中的rbx指向的就是当前partition的指针,offset 0x120代表着当前partition index为0的虚拟处理器的指针,假设我有8个虚拟处理器,那么0x120 代表index为0的虚拟处理器指针,0x128代表index为1的虚拟处理器指针,剩下的以此类推。
通过逆向还发现partition指针offset 0x110处的4bytes代表着虚拟处理器的数量,0x114处的4bytes代表着虚拟处理器的最大index值。windbg中表现为:
2: kd> dq poi(gs:[103e0])+0x110 L?20
ffffe800`00001110 00000017`00000018 00000000`00000000
ffffe800`00001120 ffffe800`0067c050 ffffe800`015cd050
ffffe800`00001130 ffffe800`015e9050 ffffe800`01607050
ffffe800`00001140 ffffe800`01624050 ffffe800`01641050
ffffe800`00001150 ffffe800`0165e050 ffffe800`0167b050
ffffe800`00001160 ffffe800`01698050 ffffe800`016b5050
ffffe800`00001170 ffffe800`016d2050 ffffe800`016ef050
ffffe800`00001180 ffffe800`0170c050 ffffe800`01729050
ffffe800`00001190 ffffe800`01746050 ffffe800`01763050
ffffe800`000011a0 ffffe800`01780050 ffffe800`0179d050
ffffe800`000011b0 ffffe800`017ba050 ffffe800`017d7050
ffffe800`000011c0 ffffe800`017f4050 ffffe800`01811050
ffffe800`000011d0 ffffe800`0182e050 ffffe800`0184b050
ffffe800`000011e0 00000000`00000000 00000000`00000000
ffffe800`000011f0 00000000`00000000 00000000`00000000
ffffe800`00001200 00000000`00000000 00000000`00000000
由于这里演示用的partition是root分区,而且我的被调试机中硬件CPU的核心数量为24个,所以此时:
partition + 0x110: 最大的虚拟处理器数量
partition + 0x114: 最大的虚拟处理器index值
partition + 0x120:index为0的虚拟处理器指针
partition + 0x128:index为的虚拟处理器指针
...
根据微软的TLFS文档中的内容,HvVtlCall这个函数的作用是将当前虚拟处理器的VTL权限升高。通过逆向这个函数内部实现,我们发现在后续的调用中出现了VTL实现的相关结构,如下代码所示。
这个函数包含三个参数,其中的第二参数是我们上面提到的Viurtal Process结构体指针,第三参数是VTL等级。通过代码得知,VP offset 0x328处使用了VTL级别来进行引索,随后取出一个指针,我们暂时称这个指针为struc1。然后从struc1 offset 0x10d0处取出了一个指针,这个指针我们暂时称为struc2。后续又从struc2 offset 0x188中取出一个64bits的地址,这个地址就是我们上文提到过的VMCS的地址。代码运行到vmptrld这里,就实现了VMCS的切换,同时抽象理解成为VTL也完成了切换。
Windbg中过程如下:
Breakpoint 0 hit
hv+0x211058:
fffff83b`24f56058 48895c2410 mov qword ptr [rsp+10h],rbx
2: kd> r rdx
rdx=ffffe800015e9050 //当前的VirtualProcess指针(VP 指针)
2: kd> r r8
r8=0000000000000001
2: kd> rdmsr 0xc0000101
msr[c0000101] = ffffe800`006eb000
2: kd> dq ffffe800`006eb000+103e0
ffffe800`006fb3e0 ffffe800`00001000 ffffe800`015e9050
//可以发现gs:[0x103e0]是partition pointer,gs:[0x103e8]是当前的VP指针
ffffe800`006fb3f0 00000080`00400000 00000000`0000da94
ffffe800`006fb400 00000000`00000000 00000000`00000000
ffffe800`006fb410 00000000`00000000 00000000`00000000
ffffe800`006fb420 00000000`00000101 80000000`00000000
ffffe800`006fb430 00000000`00000000 80000000`00000000
ffffe800`006fb440 00000000`00000000 00000000`00000000
ffffe800`006fb450 00000000`00000000 00000000`00000000
2: kd> dq ffffe800`015e9050+328
ffffe800`015e9378 ffffe800`015ea000 ffffe800`015ec000
//低地址指针是VTL0的struc1,高地址指针是VTL1的struc1
ffffe800`015e9388 00000000`00000000 ffffe800`015ea000
ffffe800`015e9398 00000000`00000001 00000000`00000000
ffffe800`015e93a8 00000000`00000002 00000000`00000006
ffffe800`015e93b8 00000000`890ced27 00000000`00000000
ffffe800`015e93c8 00000000`00000000 00000000`00000002
ffffe800`015e93d8 ffffe800`00001000 00000002`000000c0
ffffe800`015e93e8 ffffe800`015ee050 00000000`00000000
2: kd> dq poi(ffffe800`015ea000+10d0)+180
ffffe800`015eb558 ffffe800`015f3000 00000004`7a2c6000//VTL0下的VMCS VA和VMCS PA
ffffe800`015eb568 ffffe800`015f5000 00000000`00000000
ffffe800`015eb578 00000000`00000000 00000000`0000000f
ffffe800`015eb588 00000000`00000000 00000000`00000000
ffffe800`015eb598 00000000`00000000 00000000`00000000
ffffe800`015eb5a8 00000000`00000000 00000000`00000000
ffffe800`015eb5b8 00000000`00000000 00000000`00000000
ffffe800`015eb5c8 00000000`00000000 00000000`00000000
2: kd> dq poi(ffffe800`015ec000+10d0)+180
ffffe800`015ed558 ffffe800`015f6000 00000004`7a2c9000//VTL1下的VMCS VA和VMCS PA
ffffe800`015ed568 ffffe800`015f8000 00000000`00000000
ffffe800`015ed578 00000000`00000000 00000000`0000000f
ffffe800`015ed588 00000000`00000000 00000000`00000000
ffffe800`015ed598 00000000`00000000 00000000`00000000
ffffe800`015ed5a8 00000000`00000000 00000000`00000000
ffffe800`015ed5b8 00000000`00000000 00000000`00000000
ffffe800`015ed5c8 00000000`00000000 00000000`00000000
我们在发生VTL切换时下断点
Breakpoint 1 hit
hv+0x211248:
fffff83b`24f56248 0fc7b188010000 vmptrld qword ptr [rcx+188h] //切换VMCS
2: kd> bd 1
2: kd> !vmread 0x201a //EPT Pointer
@$vmread(0x201a) : 0x12382301e
2: kd> !vmread 0x6802 //CR3 Guest
@$vmread(0x6802) : 0x1aa002
2: kd> p
hv+0x21124f:
fffff83b`24f5624f e99cfeffff jmp hv+0x2110f0 (fffff83b`24f560f0)
2: kd> !vmread 0x201a //EPT Pointer
@$vmread(0x201a) : 0x12382501e
2: kd> !vmread 0x6802 //CR3 Guest
@$vmread(0x6802) : 0x4c00000
可以看到,VTL从0升为1时,并执行过vmptrld进行VMCS切换后,典型的NT内核的CR3从0x1aa002变为0x4c00000,并且EPT指针从0x12382301e变为0x12382501e。
这里用一个更直观的例子来验证VTL0 VTL1之间的差别。vmsp.exe进程也属于IUM进程,所以我们即使在内核调试环境下,都无法对vmsp.exe进程空间进行修改。
7: kd> !process 0n9012
Searching for Process with Cid == 2334
PROCESS ffffe10e0d4bb080
SessionId: 0 Cid: 2334 Peb: f5073af000 ParentCid: 01d8
DirBase: 84f417002 ObjectTable: ffff8004c6be6040 HandleCount: 51.
Image: vmsp.exe
VadRoot ffffe10e0a78fd30 Vads 40 Clone 0 Private 236. Modified 1. Locked 0.
DeviceMap ffff8004c9ea3120
Token ffff8004c6bc4060
ElapsedTime 00:00:12.882
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 25416
QuotaPoolUsage[NonPagedPool] 5704
Working Set Sizes (now,min,max) (1056, 50, 345) (4224KB, 200KB, 1380KB)
PeakWorkingSetSize 1062
VirtualSize 2101299 Mb
PeakVirtualSize 2101299 Mb
PageFaultCount 3029
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 333
THREAD ffffe10e0d57a080 Cid 2334.2ba4 Teb: 000000f5073b0000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
ffffe10e0d67a560 NotificationEvent
Not impersonating
DeviceMap ffff8004c9ea3120
Owning Process ffffe10e0d4bb080 Image: vmsp.exe
Attached Process N/A Image: N/A
Wait Start TickCount 382305 Ticks: 819 (0:00:00:12.796)
Context Switch Count 2 IdealProcessor: 14
UserTime 00:00:00.000
KernelTime 00:00:00.078
Win32 Start Address 0x00007ff6a26277d0
Stack Init fffff90c6e1afc70 Current fffff90c6e1af310
Base fffff90c6e1b0000 Limit fffff90c6e1aa000 Call 0000000000000000
Priority 9 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffff90c`6e1af350 fffff806`21eccdc5 nt!KiSwapContext+0x76
fffff90c`6e1af490 fffff806`21ecdc2a nt!KiSwapThread+0x545
fffff90c`6e1af530 fffff806`21ece886 nt!KiCommitThreadWait+0x15a
fffff90c`6e1af5d0 fffff806`223591ab nt!KeWaitForSingleObject+0x236
fffff90c`6e1af6c0 fffff806`223590da nt!ObWaitForSingleObject+0xbb
fffff90c`6e1af720 fffff806`2201dc04 nt!NtWaitForSingleObject+0x6a
fffff90c`6e1af760 fffff806`2204506a nt!VslpDispatchIumSyscall+0x34
fffff90c`6e1af7e0 fffff806`224914ea nt!VslpEnterIumSecureMode+0x1d447a
fffff90c`6e1af8b0 fffff806`22026cf8 nt!PspUserThreadStartup+0x1a65ba
fffff90c`6e1af9a0 fffff806`22026c60 nt!KiStartUserThread+0x28
fffff90c`6e1afae0 00007ff8`4cc1e880 nt!KiStartUserThreadReturn (TrapFrame @ fffff90c`6e1afae0)
000000f5`070ef8e8 00000000`00000000 0x00007ff8`4cc1e880
THREAD ffffe10e0cfb32c0 Cid 2334.2964 Teb: 000000f5073b2000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe10e0d32ec40 QueueObject
Not impersonating
DeviceMap ffff8004c9ea3120
Owning Process ffffe10e0d4bb080 Image: vmsp.exe
Attached Process N/A Image: N/A
Wait Start TickCount 382658 Ticks: 466 (0:00:00:07.281)
Context Switch Count 30 IdealProcessor: 16
UserTime 00:00:00.000
KernelTime 00:00:00.203
Win32 Start Address 0x1000000140000060
Stack Init fffff90c6e1bdc70 Current fffff90c6e1bd080
Base fffff90c6e1be000 Limit fffff90c6e1b8000 Call 0000000000000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffff90c`6e1bd0c0 fffff806`21eccdc5 nt!KiSwapContext+0x76
fffff90c`6e1bd200 fffff806`21ecdc2a nt!KiSwapThread+0x545
fffff90c`6e1bd2a0 fffff806`21f5eb70 nt!KiCommitThreadWait+0x15a
fffff90c`6e1bd340 fffff806`21f5dc08 nt!KeRemoveQueueEx+0xbc0
fffff90c`6e1bd400 fffff806`21f5d5b4 nt!IoRemoveIoCompletion+0x98
fffff90c`6e1bd530 fffff806`2201dc04 nt!NtWaitForWorkViaWorkerFactory+0xdf4
fffff90c`6e1bd760 fffff806`2204506a nt!VslpDispatchIumSyscall+0x34
fffff90c`6e1bd7e0 fffff806`224914ea nt!VslpEnterIumSecureMode+0x1d447a
fffff90c`6e1bd8b0 fffff806`22026cf8 nt!PspUserThreadStartup+0x1a65ba
fffff90c`6e1bd9a0 fffff806`22026c60 nt!KiStartUserThread+0x28
fffff90c`6e1bdae0 00007ff8`4cc1e880 nt!KiStartUserThreadReturn (TrapFrame @ fffff90c`6e1bdae0)
000000f5`0716fa18 00000000`00000000 0x00007ff8`4cc1e880
THREAD ffffe10e0d299080 Cid 2334.2470 Teb: 000000f5073b4000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe10e0d32ec40 QueueObject
Not impersonating
DeviceMap ffff8004c9ea3120
Owning Process ffffe10e0d4bb080 Image: vmsp.exe
Attached Process N/A Image: N/A
Wait Start TickCount 382670 Ticks: 454 (0:00:00:07.093)
Context Switch Count 116 IdealProcessor: 18
UserTime 00:00:00.000
KernelTime 00:00:00.859
Win32 Start Address 0x1000000140000060
Stack Init fffff90c6e1c4c70 Current fffff90c6e1c4080
Base fffff90c6e1c5000 Limit fffff90c6e1bf000 Call 0000000000000000
Priority 9 BasePriority 8 PriorityDecrement 16 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffff90c`6e1c40c0 fffff806`21eccdc5 nt!KiSwapContext+0x76
fffff90c`6e1c4200 fffff806`21ecdc2a nt!KiSwapThread+0x545
fffff90c`6e1c42a0 fffff806`21f5eb70 nt!KiCommitThreadWait+0x15a
fffff90c`6e1c4340 fffff806`21f5dc08 nt!KeRemoveQueueEx+0xbc0
fffff90c`6e1c4400 fffff806`21f5d5b4 nt!IoRemoveIoCompletion+0x98
fffff90c`6e1c4530 fffff806`2201dc04 nt!NtWaitForWorkViaWorkerFactory+0xdf4
fffff90c`6e1c4760 fffff806`2204506a nt!VslpDispatchIumSyscall+0x34
fffff90c`6e1c47e0 fffff806`224914ea nt!VslpEnterIumSecureMode+0x1d447a
fffff90c`6e1c48b0 fffff806`22026cf8 nt!PspUserThreadStartup+0x1a65ba
fffff90c`6e1c49a0 fffff806`22026c60 nt!KiStartUserThread+0x28
fffff90c`6e1c4ae0 00007ff8`4cc1e880 nt!KiStartUserThreadReturn (TrapFrame @ fffff90c`6e1c4ae0)
000000f5`071efba8 00000000`00000000 0x00007ff8`4cc1e880
7: kd> !thread ffffe10e0d57a080
THREAD ffffe10e0d57a080 Cid 2334.2ba4 Teb: 000000f5073b0000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
ffffe10e0d67a560 NotificationEvent
Not impersonating
DeviceMap ffff8004c9ea3120
Owning Process ffffe10e0d4bb080 Image: vmsp.exe
Attached Process N/A Image: N/A
Wait Start TickCount 382305 Ticks: 819 (0:00:00:12.796)
Context Switch Count 2 IdealProcessor: 14
UserTime 00:00:00.000
KernelTime 00:00:00.078
Win32 Start Address 0x00007ff6a26277d0
Stack Init fffff90c6e1afc70 Current fffff90c6e1af310
Base fffff90c6e1b0000 Limit fffff90c6e1aa000 Call 0000000000000000
Priority 9 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
fffff90c`6e1af350 fffff806`21eccdc5 : 00000000`00000000 fffff90c`00000000 ffff9a81`0ef00180 00000000`00000000 : nt!KiSwapContext+0x76
fffff90c`6e1af490 fffff806`21ecdc2a : ffffe10e`00000000 00000000`00000000 ffffe10e`0d57a100 ffff33a8`00000000 : nt!KiSwapThread+0x545
fffff90c`6e1af530 fffff806`21ece886 : ffffe10e`00000000 fffff806`00000000 fffff90c`6e1af600 00000000`00000000 : nt!KiCommitThreadWait+0x15a
fffff90c`6e1af5d0 fffff806`223591ab : ffffe10e`0d67a560 00000000`00000006 00000000`00000001 00000000`00000000 : nt!KeWaitForSingleObject+0x236
fffff90c`6e1af6c0 fffff806`223590da : fffff90c`6e1af900 ffffe10e`0d57a080 00000000`00000001 00000000`00000000 : nt!ObWaitForSingleObject+0xbb
fffff90c`6e1af720 fffff806`2201dc04 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!NtWaitForSingleObject+0x6a
fffff90c`6e1af760 fffff806`2204506a : 00000000`00000000 fffff90c`6e1af900 ffffe10e`0d57a080 00000000`00000001 : nt!VslpDispatchIumSyscall+0x34
fffff90c`6e1af7e0 fffff806`224914ea : ffff9a81`0f300180 ffffe10e`0d57a080 ffff9a81`0f30c640 00000000`00000298 : nt!VslpEnterIumSecureMode+0x1d447a
fffff90c`6e1af8b0 fffff806`22026cf8 : ffff9a81`0f300180 ffffe10e`0d57a080 ffff9a81`0f30c640 00000000`00000000 : nt!PspUserThreadStartup+0x1a65ba
fffff90c`6e1af9a0 fffff806`22026c60 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiStartUserThread+0x28
fffff90c`6e1afae0 00007ff8`4cc1e880 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiStartUserThreadReturn (TrapFrame @ fffff90c`6e1afae0)
000000f5`070ef8e8 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ff8`4cc1e880
7: kd> !vtop 84f417000 0x00007ff6a26277d0
Amd64VtoP: Virt 00007ff6a26277d0, pagedir 000000084f417000
Amd64VtoP: PML4E 000000084f4177f8
Amd64VtoP: PDPE 00000004be923ed0
Amd64VtoP: PDE 00000004b9b24898
Amd64VtoP: PTE 0000000845625138
Amd64VtoP: Mapped phys 000000049b36e7d0
Virtual address 7ff6a26277d0 translates to physical address 49b36e7d0.
7: kd> !db 49b36e7d0
#49b36e7d0 48 83 ec 28 e8 ff 04 00-00 48 83 c4 28 e9 5e fe H..(.....H..(.^.
#49b36e7e0 ff ff cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
#49b36e7f0 cc cc cc cc cc cc 66 66-0f 1f 84 00 00 00 00 00 ......ff........
#49b36e800 48 3b 0d 79 d9 00 00 f2-75 12 48 c1 c1 10 66 f7 H;.y....u.H...f.
#49b36e810 c1 ff ff f2 75 02 f2 c3-48 c1 c9 10 e9 4f 00 00 ....u...H....O..
#49b36e820 00 cc cc cc cc cc cc cc-40 53 48 83 ec 20 48 8b ........@SH.. H.
#49b36e830 d9 33 c9 ff 15 3f 2f 00-00 48 8b cb ff 15 2e 2f .3...?/..H...../
#49b36e840 00 00 ff 15 e0 2f 00 00-48 8b c8 ba 09 04 00 c0 ...../..H.......
7: kd> .process ffffe10e0d4bb080
Implicit process is now ffffe10e`0d4bb080
WARNING: .cache forcedecodeuser is not enabled
7: kd> db 0x00007ff6a26277d0
00007ff6`a26277d0 48 83 ec 28 e8 ff 04 00-00 48 83 c4 28 e9 5e fe H..(.....H..(.^.
00007ff6`a26277e0 ff ff cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00007ff6`a26277f0 cc cc cc cc cc cc 66 66-0f 1f 84 00 00 00 00 00 ......ff........
00007ff6`a2627800 48 3b 0d 79 d9 00 00 f2-75 12 48 c1 c1 10 66 f7 H;.y....u.H...f.
00007ff6`a2627810 c1 ff ff f2 75 02 f2 c3-48 c1 c9 10 e9 4f 00 00 ....u...H....O..
00007ff6`a2627820 00 cc cc cc cc cc cc cc-40 53 48 83 ec 20 48 8b ........@SH.. H.
00007ff6`a2627830 d9 33 c9 ff 15 3f 2f 00-00 48 8b cb ff 15 2e 2f .3...?/..H...../
00007ff6`a2627840 00 00 ff 15 e0 2f 00 00-48 8b c8 ba 09 04 00 c0 ...../..H.......
7: kd> eb 0x00007ff6a26277d0 11
^ Memory access error in 'eb 0x00007ff6a26277d0 11'
7: kd> !db 49b36e7d0
#49b36e7d0 48 83 ec 28 e8 ff 04 00-00 48 83 c4 28 e9 5e fe H..(.....H..(.^.
#49b36e7e0 ff ff cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
#49b36e7f0 cc cc cc cc cc cc 66 66-0f 1f 84 00 00 00 00 00 ......ff........
#49b36e800 48 3b 0d 79 d9 00 00 f2-75 12 48 c1 c1 10 66 f7 H;.y....u.H...f.
#49b36e810 c1 ff ff f2 75 02 f2 c3-48 c1 c9 10 e9 4f 00 00 ....u...H....O..
#49b36e820 00 cc cc cc cc cc cc cc-40 53 48 83 ec 20 48 8b ........@SH.. H.
#49b36e830 d9 33 c9 ff 15 3f 2f 00-00 48 8b cb ff 15 2e 2f .3...?/..H...../
#49b36e840 00 00 ff 15 e0 2f 00 00-48 8b c8 ba 09 04 00 c0 ...../..H.......
7: kd> !eb 49b36e7d0 11
Physical memory write at 49b36e7d0 failed
If you know the caching attributes used for the memory,
try specifying [c], [uc] or [wc], as in !dd [c] <params>.
WARNING: Incorrect use of these flags will cause unpredictable
processor corruption. This may immediately (or at any time in
the future until reboot) result in a system hang, incorrect data
being displayed or other strange crashes and corruption.
通过上面的调试发现,在VTL0环境下,即使拥有ring0权限,依然无法对物理地址0x49b36e7d0进行修改。
下面再回到hypervisor调试的场景,我们来看看到底为什么VTL0下Ring0无法访问到这片内存。
6: kd> !vtop 0x12382301e 49b36e7d0 //这个是VTL0环境的地址转换
Amd64VtoP: Virt 000000049b36e7d0, pagedir 0000000123823000
Amd64VtoP: PML4E 0000000123823000
Amd64VtoP: PDPE 0000000123829090
Amd64VtoP: PDE 00000001219876c8
Amd64VtoP: Large page mapped phys 000000049b36e7d0
Virtual address 49b36e7d0 translates to physical address 49b36e7d0.
6: kd> !vtop 0x12382501e 49b36e7d0 //这个是VTL1环境的地址转换
Amd64VtoP: Virt 000000049b36e7d0, pagedir 0000000123825000
Amd64VtoP: PML4E 0000000123825000
Amd64VtoP: PDPE 000000012382b090
Amd64VtoP: PDE 00000001219886c8
Amd64VtoP: Large page mapped phys 000000049b36e7d0
Virtual address 49b36e7d0 translates to physical address 49b36e7d0.
这里发现,root分区的物理地址0x49b36e7d0经过二级地址转换后依然还是0x49b36e7d0,因为所处的分区是root分区的原因,所以转换前后的物理地址都是一致的。但是差别出现在权限上。
根据Intel用户手册发现。
如果我们修改最后PDE的读写权限,就可以实现VTL的隔离操作了,下面调试验证一下。
7: kd> !dq 0000000121987000+80*d //VTL0 环境下
#121987680 00000004`9a0007b7 00000004`9a2007b7
#121987690 00000004`9a4007b7 00000004`9a6007b7
#1219876a0 00000004`9a8007b7 00000004`9aa007b7
#1219876b0 00000004`9ac007b7 00000004`9ae007b7
#1219876c0 00000004`9b0007b7 00000004`9b2005b5 //哦豁,盲生,你发现了华点!对应低三bits,就是没有写权限
#1219876d0 00000004`9b4007b7 00000004`9b6007b7
#1219876e0 00000004`9b8007b7 00000004`9ba007b7
#1219876f0 00000004`9bc007b7 00000004`9be007b7
19: kd> !dq 121988000 +80*d //VTL1 环境下
#121988680 00000004`9a0007b7 00000004`9a2007b7
#121988690 00000004`9a4007b7 00000004`9a6007b7
#1219886a0 00000004`9a8007b7 00000004`9aa007b7
#1219886b0 00000004`9ac007b7 00000004`9ae007b7
#1219886c0 00000004`9b0007b7 00000004`9b2007b7 //VTL1环境下就是读写执行权限全给
#1219886d0 00000004`9b4007b7 00000004`9b6007b7
#1219886e0 00000004`9b8007b7 00000004`9ba007b7
#1219886f0 00000004`9bc007b7 00000004`9be007b7
那么,我们照猫画虎,把VTL0环境下的PTE条目给他改成0x4`9b2007b7,看看VTL0环境下是否还会无法写入数据。
Hypervisor调试
7: kd> !ed 1219876c8 9b2007b7
7: kd> !dq 1219876c8
#1219876c8 00000004`9b2007b7 00000004`9b4007b7
#1219876d8 00000004`9b6007b7 00000004`9b8007b7
#1219876e8 00000004`9ba007b7 00000004`9bc007b7
#1219876f8 00000004`9be007b7 00000004`9c0007b7
#121987708 00000004`9c2007b7 00000004`9c4007b7
#121987718 00000004`9c6007b7 00000004`9c8007b7
#121987728 00000004`9ca007b7 00000004`9cc007b7
#121987738 00000004`9ce007b7 00000004`9d0007b7
Ring0调试
7: kd> .process ffffe10e0d4bb080
Implicit process is now ffffe10e`0d4bb080
WARNING: .cache forcedecodeuser is not enabled
7: kd> db 0x00007ff6a26277d0
00007ff6`a26277d0 48 83 ec 28 e8 ff 04 00-00 48 83 c4 28 e9 5e fe H..(.....H..(.^.
00007ff6`a26277e0 ff ff cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00007ff6`a26277f0 cc cc cc cc cc cc 66 66-0f 1f 84 00 00 00 00 00 ......ff........
00007ff6`a2627800 48 3b 0d 79 d9 00 00 f2-75 12 48 c1 c1 10 66 f7 H;.y....u.H...f.
00007ff6`a2627810 c1 ff ff f2 75 02 f2 c3-48 c1 c9 10 e9 4f 00 00 ....u...H....O..
00007ff6`a2627820 00 cc cc cc cc cc cc cc-40 53 48 83 ec 20 48 8b ........@SH.. H.
00007ff6`a2627830 d9 33 c9 ff 15 3f 2f 00-00 48 8b cb ff 15 2e 2f .3...?/..H...../
00007ff6`a2627840 00 00 ff 15 e0 2f 00 00-48 8b c8 ba 09 04 00 c0 ...../..H.......
7: kd> eb 0x00007ff6a26277d0 11
7: kd> db 0x00007ff6a26277d0
00007ff6`a26277d0 11 83 ec 28 e8 ff 04 00-00 48 83 c4 28 e9 5e fe ...(.....H..(.^.
00007ff6`a26277e0 ff ff cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00007ff6`a26277f0 cc cc cc cc cc cc 66 66-0f 1f 84 00 00 00 00 00 ......ff........
00007ff6`a2627800 48 3b 0d 79 d9 00 00 f2-75 12 48 c1 c1 10 66 f7 H;.y....u.H...f.
00007ff6`a2627810 c1 ff ff f2 75 02 f2 c3-48 c1 c9 10 e9 4f 00 00 ....u...H....O..
00007ff6`a2627820 00 cc cc cc cc cc cc cc-40 53 48 83 ec 20 48 8b ........@SH.. H.
00007ff6`a2627830 d9 33 c9 ff 15 3f 2f 00-00 48 8b cb ff 15 2e 2f .3...?/..H...../
00007ff6`a2627840 00 00 ff 15 e0 2f 00 00-48 8b c8 ba 09 04 00 c0 ...../..H.......
7: kd> eb 0x00007ff6a26277d0 48
7: kd> db 0x00007ff6a26277d0
00007ff6`a26277d0 48 83 ec 28 e8 ff 04 00-00 48 83 c4 28 e9 5e fe H..(.....H..(.^.
00007ff6`a26277e0 ff ff cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00007ff6`a26277f0 cc cc cc cc cc cc 66 66-0f 1f 84 00 00 00 00 00 ......ff........
00007ff6`a2627800 48 3b 0d 79 d9 00 00 f2-75 12 48 c1 c1 10 66 f7 H;.y....u.H...f.
00007ff6`a2627810 c1 ff ff f2 75 02 f2 c3-48 c1 c9 10 e9 4f 00 00 ....u...H....O..
00007ff6`a2627820 00 cc cc cc cc cc cc cc-40 53 48 83 ec 20 48 8b ........@SH.. H.
00007ff6`a2627830 d9 33 c9 ff 15 3f 2f 00-00 48 8b cb ff 15 2e 2f .3...?/..H...../
00007ff6`a2627840 00 00 ff 15 e0 2f 00 00-48 8b c8 ba 09 04 00 c0 ...../..H.......
内存修改的极度丝滑!vmsp进程的VTL内存隔离已经被打破,我们可以对IUM进程空间内存进行任意修改。
简单的总结下:
五、调试IUM进程
介绍了完了VTL机制,我们来介绍下比较实际的技巧,因为IUM进程无法在用户态被正常attach,这种情况无疑对安全研究造成了些许阻碍,下面我们就来介绍如何开启IUM进程的调试。
首先,我们要找到securekenel.exe的基址,然后patch掉securekernel!SkpsIsProcessDebuggingEnabled中关于调试attach判断的代码。
首先,进行hypervisor的调试
Breakpoint 1 hit
hv+0x211248:
fffff83b`24f56248 0fc7b188010000 vmptrld qword ptr [rcx+188h]
2: kd> bd 1
2: kd> !vmread 0x201a
@$vmread(0x201a) : 0x12382301e
2: kd> !vmread 0x6802
@$vmread(0x6802) : 0x1aa002
2: kd> !vmread 0x681c //VTL0 Guest RSP
@$vmread(0x681c) : 0xfffff90c646f28e8
2: kd> p
hv+0x21124f:
fffff83b`24f5624f e99cfeffff jmp hv+0x2110f0 (fffff83b`24f560f0)
2: kd> !vmread 0x201a
@$vmread(0x201a) : 0x12382501e
2: kd> !vmread 0x6802
@$vmread(0x6802) : 0x4c00000
2: kd> !vmread 0x681c //VTL1 Guest RSP
@$vmread(0x681c) : 0xffff9e003ef75ec8
2: kd> !vtop 0x4c00000 0xffff9e003ef75ec8
Amd64VtoP: Virt ffff9e003ef75ec8, pagedir 0000000004c00000
Amd64VtoP: PML4E 0000000004c009e0
Amd64VtoP: PDPE 0000000004c0b000
Amd64VtoP: PDE 0000000004c0cfb8
Amd64VtoP: PTE 0000000128dc1ba8
Amd64VtoP: Mapped phys 0000000128dacec8
Virtual address ffff9e003ef75ec8 translates to physical address 128dacec8.
2: kd> !dq 128dacec8
#128dacec8 fffff806`26d05b9a 00000000`00000001 //栈顶,返回地址0xfffff806`26d05b9a
#128daced8 fffff90c`00000000 00000000`00d10002
#128dacee8 ffff9e00`3ef75ed0 00000000`00000000
#128dacef8 00000000`00000000 00000000`00000000
#128dacf08 00000000`00000000 00000000`00000000
#128dacf18 00000000`00000000 00000000`00000000
#128dacf28 00000000`00000000 00000000`00000000
#128dacf38 00000000`00000000 00000000`00000000
2: kd> !vtop 0x4c00000 fffff80626d05b9a
Amd64VtoP: Virt fffff80626d05b9a, pagedir 0000000004c00000
Amd64VtoP: PML4E 0000000004c00f80
Amd64VtoP: PDPE 0000000004c030c0
Amd64VtoP: PDE 0000000004c029b0
Amd64VtoP: PTE 0000000004c01828
Amd64VtoP: Mapped phys 0000000002eaeb9a
Virtual address fffff80626d05b9a translates to physical address 2eaeb9a.
2: kd> !db 2eaeb9a
# 2eaeb9a 48 89 04 24 48 89 6c 24-08 48 89 54 24 10 65 48 H..$H.l$.H.T$.eH
# 2eaebaa 8b 04 25 10 00 00 00 48-8b ec 48 89 48 10 48 8b ..%....H..H.H.H.
# 2eaebba 0c 24 48 89 48 08 65 f6-04 25 b8 09 00 00 01 74 .$H.H.e..%.....t
# 2eaebca 4c 65 80 24 25 b8 09 00-00 f7 65 0f b6 04 25 ba Le.$%.....e...%.
# 2eaebda 09 00 00 65 38 04 25 c0-09 00 00 74 11 65 88 04 ...e8.%....t.e..
# 2eaebea 25 c0 09 00 00 b9 48 00-00 00 33 d2 0f 30 65 0f %.....H...3..0e.
# 2eaebfa b6 14 25 b8 09 00 00 f7-c2 04 00 00 00 74 0e b8 ..%..........t..
# 2eaec0a 01 00 00 00 33 d2 b9 49-00 00 00 0f 30 0f ae e8 ....3..I....0...
到了这里我们可以使用IDA的内存搜索,搜索48 89 04 24 48 89 6c 24
字节码,得到了如下地址:
知道了目前的函数偏移,我们可以轻松的推断出securekernel.exe的基物理地址是:0x2e40000
2: kd> !db 2eaeb9a-6EB9A
# 2e40000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
# 2e40010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
# 2e40020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# 2e40030 00 00 00 00 00 00 00 00-00 00 00 00 00 01 00 00 ................
# 2e40040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
# 2e40050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
# 2e40060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
# 2e40070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
我们要进行patch的函数SkpsIsProcessDebuggingEnabled偏移是0x9EDD3,这里把mov al, bl
patch成mov al, 1
。对应的字节码就是8a c3
–> B0 01
。
2: kd> !db 2e40000 +9EDD3
# 2ededd3 8a c3 48 8b 4c 24 58 48-33 cc e8 3e 5c fa ff 4c ..H.L$XH3..>\..L
# 2edede3 8d 5c 24 60 49 8b 5b 18-49 8b 73 20 49 8b e3 5f .\$`I.[.I.s I.._
# 2ededf3 c3 cc cc cc cc cc cc cc-cc 4c 8b dc 48 83 ec 38 .........L..H..8
# 2edee03 83 64 24 48 00 49 8d 43-18 ba 03 00 00 00 49 c7 .d$H.I.C......I.
# 2edee13 43 18 04 00 00 00 4d 8d-4b 10 49 89 43 e8 44 8d C.....M.K.I.C.D.
# 2edee23 42 04 e8 92 02 00 00 85-c0 78 0b b8 01 00 00 00 B........x......
# 2edee33 39 44 24 48 74 02 32 c0-48 83 c4 38 c3 cc cc cc 9D$Ht.2.H..8....
# 2edee43 cc cc cc cc cc 48 89 5c-24 20 89 4c 24 08 56 57 .....H.\$ .L$.VW
2: kd> !eb 2ededd3 b0
2: kd> !eb 2ededd4 1
2: kd> !db 2e40000 +9EDD3
# 2ededd3 b0 01 48 8b 4c 24 58 48-33 cc e8 3e 5c fa ff 4c ..H.L$XH3..>\..L
# 2edede3 8d 5c 24 60 49 8b 5b 18-49 8b 73 20 49 8b e3 5f .\$`I.[.I.s I.._
# 2ededf3 c3 cc cc cc cc cc cc cc-cc 4c 8b dc 48 83 ec 38 .........L..H..8
# 2edee03 83 64 24 48 00 49 8d 43-18 ba 03 00 00 00 49 c7 .d$H.I.C......I.
# 2edee13 43 18 04 00 00 00 4d 8d-4b 10 49 89 43 e8 44 8d C.....M.K.I.C.D.
# 2edee23 42 04 e8 92 02 00 00 85-c0 78 0b b8 01 00 00 00 B........x......
# 2edee33 39 44 24 48 74 02 32 c0-48 83 c4 38 c3 cc cc cc 9D$Ht.2.H..8....
# 2edee43 cc cc cc cc cc 48 89 5c-24 20 89 4c 24 08 56 57 .....H.\$ .L$.VW
最后回到用户态用windbg尝试attach一下。
秒杀!
六、总结
简单探索了Windows下的VTL机制和IUM进程的调试,介绍了一小部分的Intel硬件虚拟化技术。总的来说,虚拟化对于Windows不仅限于Hyper-V虚拟化软件,还渗透在Windows操作系统的安全措施中,足以看出微软对虚拟化的重视程度。在撰写本文时,主要参考了QuarksLab的这两篇文章(A virtual journey: From hardware virtualization to Hyper-V’s Virtual Trust Levels, Debugging Windows Isolated User Mode (IUM) Processes),感兴趣的小伙伴可以去了解一下。