最近看UEFI中关于S3 Resume的实现,在Resume的最后阶段,UEFI通过SwitchStack执行OS的S3代码:
UefiCpuPkg\Universal\Acpi\S3Resume2Pei\S3Resume.c:
VOID
EFIAPI
S3ResumeBootOs (
IN ACPI_S3_CONTEXT *AcpiS3Context,
IN PEI_S3_RESUME_STATE *PeiS3ResumeState
)
{
...
DEBUG ((DEBUG_INFO, "Transfer to 32bit OS waking vector - %x\r\n", (UINTN)Facs->XFirmwareWakingVector));
SwitchStack (
(SWITCH_STACK_ENTRY_POINT) (UINTN) Facs->XFirmwareWakingVector,
NULL,NULL,(VOID *)(UINTN)TempStackTop);
...
}
根据ACPI Spec,Facs->XFirmwareWakingVector是OS执行S3唤醒的函数:
X Firmware Waking Vector |
8 | 24 |
64-bit physical address of OSPM’s Waking Vector. Before transitioning the system into a global sleeping state, OSPM fills in this field and the OSPM Flags field to describe the waking vector. OSPM populates this field with the physical memory address of an OS-specific wake function. During POST, the platform firmware checks if the value of this field is non-zero and if so transfers control to OSPM by jumping to this address after creating the appropriate execution environment |
表格摘自ACPI 6.2 “5.2.10 Firmware ACPI Control Structure (FACS)”,FACS表用于firmware和OS传输数据,表格中提到的OSPM是指OS的ACPI.sys模块。如表格所述,Facs->XFirmwareWakingVector的值由Windows 负责填写。在Comet lake platform上Facs->XFirmwareWakingVector的物理地址值为:0x2000,所以,我决定在windows上查找具体设置的代码(
既然OS能设置,驱动模块也能设置,这是挂Hook的绝佳选择
)。
查看ACPI table当然少不了rw工具,不过很可惜,不知出于什么原因,在xp\win7\win10上Facs->XFirmwareWakingVector都不约而同的不可见(目前,我猜想:如ACPI spec如说,该值只有在进入S3 Sleep时才会被设置):
虽然不清楚Facs->XFirmwareWakingVector的值,但也不能阻挡我搜索的步伐。查找ReactOS 3.15源码后,我选择用windbg搜索WakeVector相关的符号:
drivers\bus\acpi\acpica\hardware\hwsleep.c
ACPI_STATUS
AcpiSetFirmwareWakingVector64 (
UINT64 PhysicalAddress)
{
ACPI_FUNCTION_TRACE (AcpiSetFirmwareWakingVector64);
/* Determine if the 64-bit vector actually exists */
if ((AcpiGbl_FACS->Length <= 32) || (AcpiGbl_FACS->Version < 1))
{
return_ACPI_STATUS (AE_NOT_EXIST);
}
/* Clear 32-bit vector, set the 64-bit X_ vector */
AcpiGbl_FACS->FirmwareWakingVector = 0;
AcpiGbl_FACS->XFirmwareWakingVector = PhysicalAddress;
return_ACPI_STATUS (AE_OK);
}
(跟各位得瑟一把,由于工作缘故,我有部分Checked Build OS,所以我逆向的工作量会减轻不少),并最终在hal模块中找到对应的(变量)符号,然而该变量的值依然是空:
kd> x *!*WakeVector*
82a21c98 hal!HalpWakeVector = <no type information>
kd> dd hal!HalpWakeVector
82a21c98 00000000 00000000 00000000 00000000
82a21ca8 00000000 00000000 00000000 00000000
82a21cb8 00000000 00000000 00000000 00000000
82a21cc8 00000000 00000000 00000000 00000000
很遗憾,Windows内核原理\Windows情景分析等经典书都不曾关注S3\S4的实现,没有直接的参考来源;而本文OS S3相关代码是在虚拟机上做的测试,虚拟机进入S3后系统被挂起,windbg也失去作用,因此,我对hal模块进行简单的逆向分析:
1.确定hal模块:
kd> lm m hal
Browse full module list
start end module name
82a04000 82a3d000 hal (pdb symbols) e:\symbols\win7x86sp1chk\halmacpi.pdb\0B89B9AEB8FB4822A25882B9A877C1031\halmacpi.pdb
kd> lmvm hal
Browse full module list
start end module name
82a04000 82a3d000 hal (pdb symbols) e:\symbols\win7x86sp1chk\halmacpi.pdb\0B89B9AEB8FB4822A25882B9A877C1031\halmacpi.pdb
Loaded symbol image file: halmacpi.dll
Image path: halmacpi.dll
Image name: halmacpi.dll
Browse all global symbols functions data
虚拟机上使用的hal模块对应c:\windows\system32\halmacpi.dll,用IDA加载该dll(附注,根据调试发现,下文将提到的内容在win7\win8.1\win10差别不大,因此对于这部分OS还是具有参考价值)。本文借助win10x64 TH2 Checked build的halmacpi.dll为参考,对win10 RS5 free build的halmacpi.dll进行分析(附注,微软提供的win10x64 TH2 Checked build无法正常安装使用,如果看官手头有该OS,需要用dism命令,从镜像中提取halmacpi.dll。此处要感谢我的同事lyly,告诉我dism提取镜像中模块的方法)
2.搜索对hal!HalpWakeVector的引用:
呵呵,很可惜,还真没找到。不过运气好,在IDA “Strings window”中看到一串重要的字符串以及函数HalpAcpiGetFacsMapping对它的引用:
01C003D8C0 aHalpacpigetfac db '**** HalpAcpiGetFacsMapping: No FADT found.',0Ah,0
.text:00000001C003D8C0 ; DATA XREF: HalpAcpiGetFacsMapping+54↑o
2.1.在HalpAcpiGetFacsMapping内部,有两段代码片:
01C0001E39 mov edx, 'PCAF'
.text:00000001C0001E3E call HalpAcpiGetTableWork
.text:00000001C0001E43 test rax, rax
.text:00000001C0001E46 jnz short loc_1C0001E83 ; Physical memory address of the FACS
这段代码是根据Acpi table的签名来查找Acpi table。Long字符数组’PCAF’其实就是FACP表的签名。HalpAcpiGetTableWork将返回FACP表的物理地址。然后根据FACP->FIRMWARE_CTRL和签名”SCAF”再次获得Facs地址:
01C0001E83 loc_1C0001E83: ; CODE XREF: HalpAcpiGetFacsMapping+52↑j
.text:00000001C0001E83 mov edx, [rax+FACP_20.FIRMWARE_CTRL] ; facs
.text:00000001C0001E86 mov r9d, 'SCAF' ; Physical memory address of the FACS
...
.text:00000001C0001EB2 call HalpAcpiCheckAndMapTable
.text:00000001C0001EB7 mov cs:HalpAcpiFacsMapping, rax ; cs:HalpAcpiFacsMapping: hold facs phyical address
...
.text:00000001C0001ED3 mov rax, cs:HalpAcpiFacsMapping
FIRMWARE_CTRL | 4 | 36 |
Physical memory address of the FACS, where OSPM and Firmware exchange control information. See Section 5.2.6, “Root System Description Table,” for a description of the FACS. If the X_FIRMWARE_CTRL field contains a non zero value which can be used by the OSPM, then this field must be ignored by the OSPM. If the HARDWARE_REDUCED_ACPI flag is set, and both this field and the X_FIRMWARE_CTRL field are zero, there is no FACS available. |
根据acpi spec,FIRMWARE_CTRL存放Facs表物理地址。各位看官需要从EDKII code中提取FACP和FACS表结构并创建头文件,然后在IDA-File-Load-“parse C header file”加载该头文件,并在IDA “Structures”窗口中引用头文件中定义的FACP/FACS结构,这样能提到代码的可阅读性:
2.2.HalpAcpiGetFacsMapping返回值
HalpAcpiGetFacsMapping返回Facs表的物理地址。
3.定位HalpWakeVector
获得Facs表的目的,八九不离十就是获得WakeVector的地址,所以,还要查找对HalpAcpiGetFacsMapping的引用:
.text:00000001C0001DF4 HalpAcpiGetFacsMapping proc near ; CODE XREF: HalAcpiGetFacsMappingDispatch+2↑j
.text:00000001C0001DF4 ; HaliInitPowerManagement+BA↓p
.text:00000001C0001DF4 ; DATA XREF: ...
果然,在HaliInitPowerManagement函数中找到对hal!HalpWakeVector的赋值:
PAGE:00000001C005F53A call HalpAcpiGetFacsMapping
PAGE:00000001C005F53F test rax, rax
PAGE:00000001C005F542 jz short loc_1C005F54F
PAGE:00000001C005F544 add rax, FACS_20.FwWakingVector
PAGE:00000001C005F548 mov cs:HalpWakeVector, rax
HaliInitPowerManagement首先调用HalpAcpiGetFacsMapping函数,如果Facs表非空,则从Facs->FwWakingVector取值赋给HalpWakeVector。现在终于定位到关键变量,它是开启windows中ACPI S3 Resume大门的钥匙,下一篇来看下对OS对hal!HalpWakeVector的存取。