前面文章里反复提到一个内核变量:
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,推它一把,剩下的它自己完成去吧~
2020-04-14-04:15:15.962 PROGRESS CODE: V03031006 I0
2020-04-14-04:15:15.962
2020-04-14-04:15:16.009 Transfer to 16bit OS waking vector - 1000
2020-04-14-04:15:16.009
2020-04-14-04:15:18.018 Froome: TaskSmmPanelPowerOnOffSequense set EC_CONF_DEV_En to 0x1
这段日志出自
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函数的地址(注意,每次重启函数地址都会变,但函数间偏移不会变,不要随意下函数断点!)
;查找内核导出函数hal!HalPerformEndOfInterrupt
0: kd> x hal!*Halp*
fffff800`26c0bad0 hal!HalPerformEndOfInterrupt (<no parameter info>)
fffff800`26c210b0 hal!HalProcessorIdle (<no parameter info>)
;hal!HalPerformEndOfInterrupt+偏移得到HalpSetupRealModeResume的地址
;验证一下得到的地址对应的反汇编代码是否和IDA分析的结果一致,下图为IDA HalpSetupRealModeResume的输出
;当然一致,要不然我就不贴上来了!
0: kd> u fffff800`26c210b0+2c35
hal!HalProcessorIdle+0x2c35:
fffff800`26c23ce5 488b0d64b00400 mov rcx,qword ptr [hal!HalHandleNMI+0x20c40 (fffff800`26c6ed50)]
fffff800`26c23cec 8b15f6ae0400 mov edx,dword ptr [hal!HalHandleNMI+0x20ad8 (fffff800`26c6ebe8)]
fffff800`26c23cf2 e829d8ffff call hal!HalProcessorIdle+0x470 (fffff800`26c21520)
fffff800`26c23cf7 84c0 test al,al
fffff800`26c23cf9 0f8546a1feff jne hal!HalFlushCommonBuffer+0x6f5 (fffff800`26c0de45)
fffff800`26c23cff e9dca0feff jmp hal!HalFlushCommonBuffer+0x690 (fffff800`26c0dde0)
fffff800`26c23d04 ffc3 inc ebx
fffff800`26c23d06 851dd0b20400 test dword ptr [hal!HalHandleNMI+0x20ecc (fffff800`26c6efdc)],ebx
b). 从
HalpSetupRealModeResume函数中得到内核变量HalpWakeVector的地址:
根据IDA的分析,
HalpWakeVector是全局变量,并且被
HalpSetupRealModeResume函数访问(即上图红框处),因此我从
HalpSetupRealModeResume函数中得到内核变量HalpWakeVector的地址:fffff800`26c6ebe8
fffff800`26c23cec 8b15f6ae0400 mov edx,dword ptr [hal!HalHandleNMI+0x20ad8 (fffff800`26c6ebe8)]
c).对比地址fffff800`26c6ebe8的值是否和
Intel Comet Lake RVP board输出的WakeVector相同:
0: kd> dd fffff800`26c6ebe8
fffff800`26c6ebe8 00001000 00000000 00000000 00000000
fffff800`26c6ebf8 ec50d00c ffff8600 00000000 00000000
两者值一致,但需要注意地址fffff800`26c6ebe8,也就是
HalpWakeVector存储的值是物理地址,因此要用windbg扩展命令!dd查看内容:
0: kd> !dd 00001000
# 1000 00064de9 00000001 00000001 1018003f
# 1010 00000000 00000000 00000000 00000000
# 1020 00000000 00000000 00000000 00209b00
# 1030 00000000 00000000 0000ffff 00cf9300
敏感的你看了0x1000处前4B马上能意识到这是JMP指令,是的没错,用OD试试
由此可见,UEFI代码从
Facs->FirmwareWakingVector跳转到
HalpWakeVector后,又会执行一次Jmp指令跳转到指定的payload。我们的实验驱动也可以这样实现,源码我就不提供了,太麻烦!