- 前言:
不知道这个漏洞之前有没有人发现并上报,我上报国家信息安全漏洞共享平台,对方要求我提供POC。我掂量了一下自己能力,觉得在驱动里实现POC难度挺高的,所以我就放弃了(当然,我已经顺手报给微软MSRC)。于是我就移步至此,记录调试过程。实验结论基于我前两篇文章:探索Windows S3唤醒函数 (一) 探索Windows S3唤醒函数 (二)。 从Win7开始,x64位平台需要在测试模式下才能加载未认证的驱动,一般也没几个用户会开测试模式,所以限制了这个漏洞的利用。另外Win7可能不支持UEFI Bios,因此,这个漏洞可能在WinXp/Win7系统上不能奏效。
- 产生原理:
前面文章里反复提到一个内核变量: HalpLowStubPhysicalAddress,漏洞的产生全在于它:Step 1).OS在 HalpSetupAcpiPhase0阶段,调用HalpAllocPhysicalMemory分配(可执行)物理地址,并将起始地址写入 HalpLowStubPhysicalAddress;(下文会给出调试证明。随文附件中,我提供了Win10 RS5 x64 Free Build Hal.dll和对应的Hal.i64 )Step 1.5).在之后的某个阶段(暂时没有跟踪到),OS将初始化 HalpLowStubPhysicalAddress所指的物理内存,其起始4Byte为一个JMP跳转指令; (下文亦会给出调试证明)Step 2).进入S3 Sleep时,OS以 HalpLowStubPhysicalAddress 作为参数,调用HalpSetupRealModeResume函数,将 HalpLowStubPhysicalAddress的值写入内核变量HalpWakeVector。前面文章提过, HalpWakeVector对应UEFI S3 WakeVector;Step 3).外部事件触发S3 Resume,UEFI Bios执行 S3ResumeBootOs,通过SwitchStack函数跳转到OS指定的S3 WakeVector,也就是 HalpLowStubPhysicalAddress的值;(下文只能提供Intel Comet Lake RVP board bios log)
- 利用方式:
原理都写到这份上了,大家应该心知肚明了:创建驱动,仿造 Step 1)和Step 1.5)分配物理内存并在物理内存里构建JMP。然后覆盖 内核变量HalpLowStubPhysicalAddress的值为我们分配的物理地址!!!,完成后让OS进入S3,推它一把,剩下的它自己完成去吧~
- 证明:
首先证明Step 3):
这段日志出自 Intel Comet Lake RVP board的串口输出日志,对应实现可以定位到下列UEFI源码(UEFI源码,大家可以从UDK2015项目中获得。我用的是IBV基于UDK的CodeBase,涉密不便于贴出):
这段日志表明了WakeVector的物理内存位于0x1000,这个物理地址由OS填写(确切的说是ACPI子系统填写)。再证明Step 1):a).首先查找OS为导出函数HalpSetupRealModeResume,根据IDA分析,我知道:HalpSetupRealModeResume距离OS导出函数hal!HalPerformEndOfInterrupt的偏移为0x2c35,所以我先定位运行时hal!HalPerformEndOfInterrupt函数的地址(注意,每次重启函数地址都会变,但函数间偏移不会变,不要随意下函数断点!)
b). 从 HalpSetupRealModeResume函数中得到内核变量HalpWakeVector的地址:根据IDA的分析, HalpWakeVector是全局变量,并且被 HalpSetupRealModeResume函数访问(即上图红框处),因此我从 HalpSetupRealModeResume函数中得到内核变量HalpWakeVector的地址:fffff800`26c6ebe8
c).对比地址fffff800`26c6ebe8的值是否和 Intel Comet Lake RVP board输出的WakeVector相同:
两者值一致,但需要注意地址fffff800`26c6ebe8,也就是 HalpWakeVector存储的值是物理地址,因此要用windbg扩展命令!dd查看内容:
敏感的你看了0x1000处前4B马上能意识到这是JMP指令,是的没错,用OD试试
由此可见,UEFI代码从Facs->FirmwareWakingVector跳转到 HalpWakeVector后,又会执行一次Jmp指令跳转到指定的payload。我们的实验驱动也可以这样实现,源码我就不提供了,太麻烦!