本题的主要思路是用VT中的VMEXIT事件构建了VM(此处指加密逻辑)的Dispatch。
对于大部分逆向选手来讲,VT可能有点陌生,其实这也是十年前的技术了。
下面先简单介绍下VT。
Intel VT-x(简称VT) 是Intel芯片支持的硬件虚拟化(Hardware Enabled Virtualization)技术,
与之相对的,AMD芯片支持的HEV技术叫做AMD-V(or SVM)。
我们知道有Type-1和Type-2两类Hypervisor,
Type-1跑在硬件上,Type-2跑在OS上。
众所周知,日常使用的应用程序跑在Ring3上,驱动内核跑在Ring0,
而HEV中构建出一个新的特权层,它的权限要等于或者大于OS的权限,称为"Ring -1"层。
这个特权层我们称为VMX,而VMX又分为两种模式,各自跑着不同的逻辑:
VMX root: 此权限下跑着的逻辑代码我们命名为VMM(Virtual Machine Monitor),或者叫做Hypervisor。
VMX non-root: 此权限下跑着的逻辑代码我们称为客户机。
下图为VMX root层跑着的VMM:
那VMM的作用是什么呢,答:实现虚拟硬件与真实硬件的通信与一些事件处理。
当然这么说会很让人困惑,举个现实的例子:
Vmware中跑了一个Win7 x64,又跑了一个Win xp,这两个虚拟机应该是互相是不知道对方的,或者说是不应该干扰对方的。
如何实现这个功能,就需要VMM来进行调度。
这里其实就引出的新的名词,我们把上面提到的虚拟机称为VM(Virtual Machine)。
然后就引出了对立的概念,以及存在类似的名词替代。
以上的名词在列上都可替代使用,具体画个图是这样的(先不管VM指令与事件部分):
大体来看,就是VMM在管理着两个VM(Guest0 和 Guest 1)。
回到开始的问题:
既然叫做硬件虚拟化,那么是不是所有的VT都是Type-1呢?其实不一定
我们知道KVM,Xen都是Hypervisor,也就是VMM,但前者是Type-2的,后者是Type-1的,这又是为什么呢?
这其实是由于HEV启动Guests(即VM,客户机;后面不赘述了)的方式不同,一共有3种
简单来说,KVM这个Hypervisor是先启动HostOS(也就是我们熟知的Linux等OS,MBR最开始引导的OS),
后启动Hypervisor。
所以KVM是Type-2的Hypervisor。
其中这种很有意思,我们知道Hypervisor的权限要大于或等于OS,
在Type-2中,HostOS相当于创建了比自己权限更高的Hypervisor。
此处我认为权限虽然更高,但未必可以管理HostOS。
可以发现,MBR引导的是Hypervisor,而不是HostOS,
HostOS(即Domain0)是由Hypervisor引导的。
所以,Hypervisor在先,HostOS在后。
即Type-1的Hypervisor。
没有了HostOS,MBR只能引导Hypervisor来创建guests。
现在还没有这样的虚拟机,所以称为Unknown Virtual Machine。
如果出现了,估计就是Type-3了吧(X
所以,Hypervisor的两类,准确说是根据MBR的引导来决定的:
MBR先引导Hypervisor:Type-1
MBR先引导HostOS:Type-2
这样我们搞清了一些术语与VTx的初窥外观,知道了VTx在整个Ring中是什么样子的。
结合下实际,我们日常用的Vmware,在当前这个版本肯定是使用了Vtx技术,(没有Vtx技术时,Vmware都是用软件来模拟硬件)。
我们先启动Win10,在R3上启动了Vmware14,此时就启动了Type-2型的Hypervisor,来调度着我们打开的Ubuntu16.04和Win7 x64。对于Ubuntu和Win7,或者对于我们来说,VMM层是透明的,我们以为这两个虚拟机都跑在真实的硬件上。
还是这张图,这是VMM与VM交互的一个概述
其中,比较重要的有3个部分,进入VMM,#VMEXIT,退出VMM。
虽然没有图,但是当VMXOFF后,如果需要继续运行HostOS,必须人工还原OS运行环境。
很多时候是指人工将VMCS保存的虚拟机环境填充为当前环境。
上面我们在进入VMM中提到了,在VMXLAUNCH之前还需要填充VMCS结构,这个东西真的十分庞大,共有6个组成部分
现在稍微考虑详细一点,会发现一个问题:
回顾在学逆向,Win32时,我们知道每个进程独有一个4G的虚拟地址空间(or 线性地址空间),
以进程的视角来考虑,看起来自己把4G的内存全占了,实际上是因为在x86保护模式中:
虚拟地址(or 线性地址) ===[分页机制]===> 物理地址
那么在VTx中,这个分页机制就会变得更加复杂一些,
因为反过来想,如果每个VM都以为自己在真实的硬件上跑,肯定以为自己"独占了"硬件的物理地址。
所以VT中引入了EPT(Extended Page Table)机制,扩展页表翻译技术。
简单来说,需要EPT两个东西:CR3和EPTP,
CR3 指向---> gPT(guest Page Table),作用:VM线性地址 trans_to VM物理地址
EPTP 指向---> EPT页表,作用:VM物理地址 trans_to 真实物理地址
本题的所有代码已经开源:https://github.com/vvv-347/hctf2018_Rev_spiral
VT部分的代码参考了小宝,传送门:https://bbs.pediy.com/thread-211973.htm
本题分为两个部分,spiral.exe和spiral_core.sys。
spiral.exe主要目的是加载驱动,加密则是附带的功能。
spiral.exe检查输入第1部分成功后,会将输入的第2部分写到申请的内存中,同时计算checksum,将spiral_core.sys写到本地并加载。
spiral_core.sys则是有主要的加密逻辑。
首先在SetVMCS中,有关键的Guest与Host的入口地址
初次进入VMM时,会对大小为81的数组进行一次移位变换(实际上是个数独board),
之后根据VMEXIT事件分发到各自的Handle。
VM(Guest)的入口点是Asm_RunToVMCS返回地址,即之前所有的VMEXIT事件指令(cpuid, invd)都不会触发。
真正进入VM(Guest)后,执行第一次特权指令后会进入VMM执行上面提到的数独第一次decode。
本次一共涉及4个类型的Handle:
VM(Guest)继续执行:
执行Asm_SetupVMCS的后半段,来到主加密逻辑RegVM_in_VMX
再一次对数独进行移位变换,此时数独移位decode全部完成。
除了最后的Check的vm_call,因为含有正反输入,所以共54位。
54位分成3组,每组18个vm_call,且每组由Invd分割,Invd的目的是重新生成vm_opcode顺序。
VM部分就是逆向题中的日常了,
Reg4字节,分为4个部分
这样解题思路也很清晰了:
1.先解出数独的结果(手算,z3)。
2.4次Invd会将vm_opcode乱序,可动态或者手动的到vm_opcode每次变换后的顺序。
3.根据vm_opcode对应的Handle,计算出flag。
解密脚本详见赞助方bysec的hctf2018官方wp。
本人对于vt的理解只是知道浅显原理,如有失误请各位看雪大手子斧正。
代码有两处bug,第1个是check部分应该是i,不过没有影响逻辑。
第2个是flag的第11位因为正反的vmHandle都是<<4后xor,而且输出位又&0xff,事后发现太zz了:\
最后感谢参加hctf的各位,明年见!
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-2-2 09:59
被kanxue编辑
,原因: