去年考察了Hypervisor,今年按理应该考察DMA技术,选手手中不一定有DMA怎么办呢?那就利用硬盘作为已有的DMA设备!
(1) 「影」核心系统「根」需要在一些开启了特殊特性的机器上才能部署成功,逆向找到成功部署条件!成功部署「影」核心系统,即成功运行shadow_panel.exe,控制台程序成功运行进入至输入终止密码的终端。(满分0.5分)
系统版本为:Windows 10 1909 x64 + Intel。
首次尝试直接打开shadow_panel.exe,其返回加载失败。

发现程序通过_beginthreadex创建线程于函数0x1449FA120,经分析此处为页面渲染循环,自此开始进入程序主逻辑。
从创建线程处继续向后分析:发现要让程序成功部署,共有以下条件:
(1) 需要UAC权限。
(2) 需要开启VT虚拟化。
(3) 需要开启HyperV虚拟化平台。
cpuid(1) 检查 ECX.bit31 + cpuid(0x40000000) 验证 vendor "Microsoft Hv"。
(4) 需要关闭IOMMU。
通过 NtQuerySystemInformation 枚举 ACPI 表, 查找 DMAR 或 IVRS 签名。
(5) 需要是INTEL或AMD型号的CPU(否则驱动蓝屏)。
驱动调用函数 KeBugCheckEx(0xDEADC0DE)。
满足上述条件后,可以成功启动程序,进入登陆页面。
(2) 「根」使用特殊方法,对操作系统底层进行了攻击,并借此将关键核心代码隐藏了起来,分析其完整实现流程。(满分2.5分)
R3在运行中于C:\Windows\Temp\WdiServiceHost\{%08lX-%04X-...}.vhd释放了一个虚拟磁盘文件映像,然后将其挂载为虚拟磁盘。
R3程序在运行中释放驱动到\\SystemRoot\\system32\\drivers\\<name>.sys,其中<name>为随机10字节,从驱动中可以提取出将被释放的完整驱动程序,其大小为33,433,648 字节,带有Guangzhou Kingteller Technology Co.,Ltd.过期签名。
对驱动进行分析,可以发现其没有任何导入函数,所有导入函数通过FNV1A算法手动导入,编写脚本自动化扫描,可以发现以下导入表。
驱动中所有函数被OLLVM混淆,部分函数被TVM虚拟化,共尝试分析了以下函数。
从R3中可以分析出通过GetProcAddress手动导入了一系列关于虚拟磁盘的函数。
通过分析,还原攻击链条如下:
1. 用户层通过*VirtualDisk系列函数,创建SCSI设备,防止用户环境无对应设备协议栈可用。
2. 驱动层通过0x4D014 = IOCTL_SCSI_PASS_THROUGH_DIRECT向用户层创建的SCSI设备发送请求,从而通过下图中的路径直接DMA读写物理内存。因此需要关闭IOMMU。
这里完整复现了相同的攻击手法,源码在Source/scsi_verify_hv中。
3. 驱动层从不受管理的物理内存中寻找hvix64.exe,然后通过类似开源项目Voyager的攻击手法,找到HyperV中存在的Vmexit路径,通过搜索特征码来劫持HyperV驱动的Vmexit,从而接管虚拟化。
4. 驱动将Shellcode解密,然后映射到不受管理的内存中(通过MmMapIoSpace验证),从而将口令的解密路径代码隐藏到Host内存映射中,通过接管指定的cpuid请求来允许用户层访问。
最终成功DUMP出关键核心代码:
为保持题解结构清晰明确,完整的分析流程将放到第六问答案中。
(3) 编写检测代码,检测(2)中「影」核心系统攻击操作系统底层的特殊方法。(满分2分)
分为两种层面的检测,首先说用户层:
R3一共写了6种检测方式,在shadow_panel.exe运行时打开即可。
所有源码在Souce/detect_r3中,这里没有写cpuid查询这种比较局限的检测。
内核层可以通过挂接到磁盘IRP设备栈,来检测这种恶意的SCSI请求,这里提出两种方法,一种是检测对虚拟设备的READ&WRITE请求,另一种是检测SCSI请求目标缓冲区内容通过MMIO映射后是否全为0xFF(受保护的内存映射后内容为全0xFF)。
也可以通过相同的方式直接校验HyperV的完整性,这里复现了SCSI_DMA的驱动层攻击手法,然后在物理内存中定位到HyperV的代码段,对其与磁盘校验完整性。
这里成功找到了对应的hook和代码洞穴。
把代码放到了Source/scsi_verify_hv。
通常会用内存布局,物理内存中映射时留下的副本两种方式来检测Voyager,但是在这里显然是不适用的,因为这里未受保护的副本没有被修改。
因此这里共以下三种方法:
其中1、3源码在Source/detect-r0中,2的源码在Source/scsi_verify_hv。
(4) 计算出正确的终止密码,输入到shadow_panel.exe中,使得其返回成功。(满分1.0分)
在测试的1909版本中,对应的正确终止密码为:
3CCBC52C0A3769F0B92BFA3ECBA472A2
输入后显示成功。
(5) 编写keygen,使得在任意机器,任何一次运行shadow_panel.exe,都可以正确计算出终止密码。(满分2.5分)
首先根据cpuid查询得到的CPU厂商,从hvix64.exe/hvax64.exe 中根据7套特征码搜索将被接管的vmexit所在页面。
然后根据偏移解析对应call的目标地址,从该地址向前对齐到0x1000后+0x500,+0x508的位置读取两段SEED值,与两个常量值进行异或,得到中间值。
r8 = seed0 ^ 0x5348414430574E54 "SHADOWNT"
r9 = seed1 ^ 0x4859504552564D58 "HYPERVMX"
对r8,r9做8轮混合运算,每轮4步,最终得到的r8,r9按照"{r8:016X}{r9:016X}"拼接为32位16进制字符串,即可得到密钥值。
keygen代码在Source/keygen中。
(6) 详细描述完整的解题过程和思路,提供所有编写的程序的源代码。做到清晰易懂,操作可以复现结果。编码工整风格优雅、注释详尽。相同实现方法下,提交时间靠前者得分更高,AI可用于辅助分析,其产出的内容(代码/文档等)需明确标注并提交完整提示词和聊天记录,赛事方针对此项的判断具有最终解释权。(满分1.5分)
环境分析思路
打开驱动就发现运行不起来,题目给了提示需要通过逆向出条件才能把程序跑起来,因此这里直接乖乖去逆向就可以了。
用户层分析思路
首先分析R3,R3可能为了反AI直接静态分析,把一个函数复制了很多份。因为之前编写了模拟执行工具,所以直接跑一下模拟,找一下真实的执行逻辑。
看到创建线程基本上就是主逻辑开始了,向后分析发现其实这里混淆反而没有多重,基本上都能直接看出来在做什么。
首先关注到GetProcAddress,调用了很多导入表不存在的函数,这样的函数基本上都是分析的重点,在这里发现创建了虚拟磁盘,还觉得可能是要隐藏驱动之类的。
后面就进入了条件判断,通过静态分析很容易就能总结出要加载程序需要的条件了。
然后激活系统,安装HyperV,关闭IOMMU,就可以进入程序了。
进入程序后首先关注到的是右侧展示的系统信息,然后这里提示了密码的长度。
最开始发现程序没有直接重启虚拟机,就几乎排除了HyperV劫持的猜测,因为常规的HyperV劫持一般都是需要重启一下。首先想到的是Enclave保护,但是查询文档发现Enclave版本要求比较高,题目这里要求的环境对不上。这里只能先往后做了,逆向R3的过程中很容易可以看到通信是通过cpuid进行的,因此可以确定是已经进入虚拟化了。
这里通过OpenArk的已卸载驱动枚举首先看到了程序加载了驱动。
还没来得及自己提取驱动,MCP这边就已经把驱动从R3提出来了:)。
内核层分析思路
驱动也很大,OLLVM+TVM加壳,用户层还有一些函数是TVM变异的,驱动层这边就全是虚拟化了;遇到虚拟化基本上先放在一边。
到了驱动这里很难分析了,通过经验结合AI找了几个重点函数分析一下,发现逻辑还是比较清楚的,逆向发现了通信码:0x4D014 = IOCTL_SCSI_PASS_THROUGH_DIRECT,这里就和之前R3发现的创建虚拟磁盘设备对上了。最开始猜测这里是一种和初赛类似隐秘通信,后面发现并不是。
后面最开始是从代码中解出了这个特征码:
E8 ?? ?? ?? ?? 48 89 04 24 E9
去Github精确搜索,只搜到一个源码,竟然就是Voyager,基本上到这里就确定是攻击了HyperV了。
#define INTEL_VMEXIT_HANDLER_SIG "65 C6 04 25 6D ?? ?? ?? ?? 48 8B 4C 24 ?? 48 8B 54 24 ?? E8 ?? ?? ?? ?? E9"
#define AMD_VMEXIT_HANDLER_SIG "E8 ?? ?? ?? ?? 48 89 04 24 E9"
后面找到了驱动的导入模式,解出了导入表,导入表也印证了这个猜测。
这里就开始找是怎么攻击的HyperV了,竟然不用重启。(后面写分析的时候发现了btbd/ddma,其实真相就在github:()。
最开始认为可能是通过Invlpg刷页,但是自己写了个驱动来遍历物理内存(放到Source/FindVmExit中),发现Invlpg是不能刷出HyperV所在的物理页的(全是0xFF)。
那就只能是这个奇怪的SCSI了,到这里确实觉得这届题出法很天才了。
不管怎样,得想办法分析到被劫持的Vmexit,这里开始就用内存取证的思维做了,如果用常规调试的话不知道要在这里跟dbg耗多久。
Vmware虚拟机在暂停后,会在硬盘中留下虚拟机内存的全量镜像,未加密的,用于在恢复时再次还原。通过利用这个特性,可以对虚拟机内存进行取证分析。
先暂停,然后从驱动中确认了在1909版本下用的特征码:
48 8B 4C 24 ?? EB 07 E8 ?? ?? ?? ?? EB F2 48 8B 54 24 ?? E8 ?? ?? ?? ?? E9
在虚拟机内存中定位到对应位置,然后运行赛题程序,对比前后差异,就可以发现hook模式了。
可以看到这里修补了一个jmp+0x22,跳到后面的代码洞里。
最终的跳转目标地址是0x327FFFE01720,显然不是Guest页表中的内存了,因为物理内存中还没拿到页表,这里没继续跟踪0x327FFFE01720这个线索了。
这里找到Shellcode有几种思路,1. HyperV调试vmexit,从hook点跟进去就是shellcode 2. 通过MAGIC_NUMBER特征暴力搜索定位 3. irp hook scsi来抓出所有写入请求
实际分析中我先使用了第二种方法,在写检测方案后想到了第三种方法。
先讲第二种方法:
要接管Vmexit,一定会有对应的指令,题目中又提供了很多MAGIC_NUMBER,例如0x114514,0x1919810 等(恼),根据这些显著特征,直接尝试在全量内存中定位Shellcode显然是更快的。最终通过0x114514和vmwrite指令特征,很快就成功在全量内存中定位到了Shellcode所在位置。
第三种方法:
用和第二问检测方法相同的IRP Hook,抓出所有写受保护内存请求的原始缓冲区,这里选择dump到磁盘中,然后从磁盘中分析数据。
DUMP出数据后,发现这里从1000D4000开始就是Shellcode内容了。
在HXD中把他们拼接在一起,还原内存布局,即可得到对应Shellcode内容了。
这里源码放到Source/dump_scsi_write中。
最开始为了做的快些,没有尝试部署HV双机调试,选择的是直接在物理内存中解题。
后面部署了一下HyperV双机调试的环境,再补充一下调试解法。
调试解法(第一种方法):
这里选择在Windows 10 1909 Intel平台部署Windbg+Vmware+Hypervisor串口调试。
首先打开赛题程序,在hvix64.exe中通过特征码定位到hook点,然后在调试器中可以看到hook跳转的目标位置即Shellcode所在处。
直接通过调试器命令把shellcode内容dump到磁盘中,即可得到shellcode内容。
接下来开始分析Shellcode。
Shellcode分析思路 —— 调试版 ——
抓出shellcode内容后,因为调试中我们可以访问虚拟地址上下文,所以可以直接分析代码逻辑。
可以看到通过一个目标地址对齐到0x1000后,对其+0x500和+0x508得到两个值作为SEED。这里得到rax为FFFFFAF3F241DD90,通过调试器发现这个地址其实是hv的vmexit函数所在地址。
由此就能得到种子值的生成逻辑了,也可以计算Key。
计算Key的方法在下一节之后具体叙述。
接下来是不调试的情况下,通过内存取证的思维如何解题。
Shellcode分析思路 —— 内存取证版 ——
定位到Shellcode之后,发现其显著的具有无头PE文件特征,编写脚本提取,然后根据推测的结构修复其各节表,得到了一个修复后的PE文件,这里分析就非常舒服了。
分析后发现密钥生成算法很简单,但是有两个偏移:0x500,0x508。对应的数据不知道怎么来的,这里因为没有拿到HostCR3,不知道虚拟地址的对应关系,因此只剩下三种选择:1. 调试 2. 打hook拿值 3. 得到CR3。
这里选择了直接打hook拿值(调试方法后面补在了上一节),通过修改jcc跳过了上层的过滤判断,然后直接在接管函数里面把0x500,0x508这两个值写到程序的.data节段(这里刚才修复节表的好处就体现出来了~),写了一个简单的程序调用对应的cpuid接口,调用后再次dump内存。
从.data段中拿到对应的值:
seed0 = 0xE8C0B70F44CE8B4C
seed1 = 0x66D8B70F00022654
通过值再次在内存中搜索,发现地址就在hvix64里面!
再次定位,发现是VMEXIT所在页面+0x500和0x508。
因此之所以不同机器解密逻辑会有所不同,就是因为hvix64/hvax64的代码不同。
到这里不仅可以计算出自己的终止密码,也可以编写注册机了。
定位被hook位置这里通过提取出驱动中使用的7套特征码的方式来定位,7套特征码需要从驱动中对应函数解密一下。
命中后,定位命中点,按照Offset解析对应call的地址即VMEXIT所在地址。
然后向前取整页面,找到其+0x500和+0x508处的数据。


测试了Windows 10 1909 Intel + Windows 11 23h2 Intel + Windows10 1909 AMD 环境,在测试环境上计算的密码完全正确,Writeup!
结语:
1. 按照以上操作完全可以复现我的分析结果。
2. 所有源码已添加详尽的注释。
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 8小时前
被Saileaxh编辑
,原因: