首页
社区
课程
招聘
[原创]单机DMA劫持HyperV!调试+取证两种思路解决2026腾讯游戏安全技术竞赛决赛
发表于: 9小时前 616

[原创]单机DMA劫持HyperV!调试+取证两种思路解决2026腾讯游戏安全技术竞赛决赛

9小时前
616

去年考察了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) 需要是INTELAMD型号的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,但是在这里显然是不适用的,因为这里未受保护的副本没有被修改。

因此这里共以下三种方法

 

其中13源码在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"

r8r98轮混合运算,每轮4步,最终得到的r8r9按照"{r8:016X}{r9:016X}"拼接为3216进制字符串,即可得到密钥值。

keygen代码在Source/keygen中。

(6) 详细描述完整的解题过程和思路,提供所有编写的程序的源代码。做到清晰易懂,操作可以复现结果。编码工整风格优雅、注释详尽。相同实现方法下,提交时间靠前者得分更高,AI可用于辅助分析,其产出的内容(代码/文档等)需明确标注并提交完整提示词和聊天记录,赛事方针对此项的判断具有最终解释权。(满分1.5分)

环境分析思路

打开驱动就发现运行不起来,题目给了提示需要通过逆向出条件才能把程序跑起来,因此这里直接乖乖去逆向就可以了。

用户层分析思路

首先分析R3R3可能为了反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,例如0x1145140x1919810 等(恼),根据这些显著特征,直接尝试在全量内存中定位Shellcode显然是更快的。最终通过0x114514vmwrite指令特征,很快就成功在全量内存中定位到了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。这里得到raxFFFFFAF3F241DD90,通过调试器发现这个地址其实是hvvmexit函数所在地址。

 

 

由此就能得到种子值的生成逻辑了,也可以计算Key

计算Key的方法在下一节之后具体叙述。

接下来是不调试的情况下,通过内存取证的思维如何解题。

Shellcode分析思路 —— 内存取证版 ——

 

定位到Shellcode之后,发现其显著的具有无头PE文件特征,编写脚本提取,然后根据推测的结构修复其各节表,得到了一个修复后的PE文件,这里分析就非常舒服了。

 

 

分析后发现密钥生成算法很简单,但是有两个偏移:0x5000x508。对应的数据不知道怎么来的,这里因为没有拿到HostCR3,不知道虚拟地址的对应关系,因此只剩下三种选择:1. 调试 2. hook拿值 3. 得到CR3

这里选择了直接打hook拿值(调试方法后面补在了上一节),通过修改jcc跳过了上层的过滤判断,然后直接在接管函数里面把0x5000x508这两个值写到程序的.data节段(这里刚才修复节表的好处就体现出来了~),写了一个简单的程序调用对应的cpuid接口,调用后再次dump内存。

 

.data段中拿到对应的值:

seed0 = 0xE8C0B70F44CE8B4C

seed1 = 0x66D8B70F00022654

通过值再次在内存中搜索,发现地址就在hvix64里面!

再次定位,发现是VMEXIT所在页面+0x5000x508

因此之所以不同机器解密逻辑会有所不同,就是因为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编辑 ,原因:
上传的附件:
收藏
免费 4
支持
分享
最新回复 (7)
雪    币: 17354
活跃值: (8791)
能力值: ( LV15,RANK:793 )
在线值:
发帖
回帖
粉丝
2
无敌,哥
8小时前
0
雪    币: 1344
活跃值: (2016)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
917K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3j5h3u0s2N6i4V1&6y4q4)9J5c8V1c8A6M7$3E0B7j5h3y4C8k6i4t1`.
DDMA技术,用磁盘做DMA
8小时前
0
雪    币: 2488
活跃值: (1821)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
周旋久 1c3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3j5h3u0s2N6i4V1&6y4q4)9J5c8V1c8A6M7$3E0B7j5h3y4C8k6i4t1`. DDMA技术,用磁盘做DMA
是的
8小时前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
我逆出来了HYPERV劫持 虚拟硬盘, 类kdmapper LOADER,我甚至写了一个文件系统驱动,专门去拦截EXE运行的时候释放的驱动和VHD,VHD是空的,驱动带这个签名。 顺便提一下,那个loader的签名,是去年年中的一个rootkit用的,直接搜签名可以搜得到它的分析报告,MustangPanda做的病毒。我没想到还有虚拟DMA这个东西,还真是盲区了,而且我试过用自己的VT伪装HYPER-V,可以直接返回一个空白的绿色框,进一步注入一个DLL劫持CPUID返回值,可以伪造一个"成功"?  貌似R3那边CPUID的返回值只要不是那三个失败的值,就自动判定成功了
7小时前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6

这个题解我看下来,如果没有把驱动虚拟磁盘的导入表算出明文,想到是用虚拟硬盘做DMA读写(我觉得看到了这些函数名都很难想到),后面的就更不用说

最后于 7小时前 被XieCZ1337编辑 ,原因:
7小时前
0
雪    币: 1952
活跃值: (1898)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
7
大佬 你学内核多久了 我也想像你一样厉害 太强了
7小时前
0
雪    币: 3630
活跃值: (6452)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
8
太强了老铁。。。。。。
2小时前
0
游客
登录 | 注册 方可回帖
返回