An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, execute arbitrary code in System Management Mode - an environment more privileged than operating system (OS) and completely isolated from it. Running arbitrary code in SMM additionally bypasses SMM-based SPI flash protections against modifications, which can help an attacker to install a firmware backdoor/implant into BIOS. Such a malicious firmware code in BIOS could persist across operating system re-installs. Additionally, this vulnerability potentially could be used by threat actors to bypass security mechanisms provided by UEFI firmware (for example, Secure Boot and some types of memory isolation for hypervisors).
Binarly REsearch Team identified a SMM callout in a Fujitsu device, which allows an attacker to access the System Management Mode and execute arbitrary code.
An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, execute arbitrary code in System Management Mode - an evironment more privileged than operating system (OS) and completely isolated from it. Running arbitrary code in SMM additionally bypasses SMM-based SPI flash protections against modifications, which can help an attacker to install a firmware backdoor/implant into BIOS. Such a malicious firmware code in BIOS could persist across operating system re-installs. Additionally, this vulnerability potentially could be used by threat actors to bypass security mechanisms provided by UEFI firmware (for example, Secure Boot and some types of memory isolation for hypervisors).
The vulnerability exists in software System Management Interrupt (SWSMI) handler located at offset 0x474
in module AsfSecureBootSmm
.SWSMI handler with number 0x56
dereferences gRT (EFI_RUNTIME_SERVICES) pointer to call a SetVariable()
service, which is located outside of SMRAM.Hence, this can result in code execution in SMM (escalating privilege from ring 0 to ring -2).
Below is the decompiled code of the vulnerable handler:
EFI_STATUS __fastcall SwSmiHandler_474(__int64 a1, __int64 a2, UINTN *CpuIndex)
{
EFI_STATUS Status; // rax
UINTN CurrentCpuIndex; // rbx
bool NotFound; // zf
UINTN CpuNum; // r9
char AsfSecureBootValue[4]; // [rsp+30h] [rbp-10h] BYREF
void *RegisterValue; // [rsp+34h] [rbp-Ch] BYREF
LODWORD(Status) = 0;
if ( !g_ReadyToBootRegistered )
{
LODWORD(Status) = (gSmmCpu_10A0->ReadSaveState)(
gSmmCpu_10A0,
4i64,
EFI_SMM_SAVE_STATE_REGISTER_RBX,
*CpuIndex,
&RegisterValue);
// compare RBX value with "$H2O" for each CPU
for ( CurrentCpuIndex = 0i64; ; ++CurrentCpuIndex )
{
NotFound = CurrentCpuIndex == gSmst_1068->NumberOfCpus;
if ( CurrentCpuIndex >= gSmst_1068->NumberOfCpus )
break;
Status = (gSmmCpu_10A0->ReadSaveState)(
gSmmCpu_10A0,
4i64,
EFI_SMM_SAVE_STATE_REGISTER_RBX,
CurrentCpuIndex,
&RegisterValue);
if ( !Status && RegisterValue == '$H2O' )
{
NotFound = CurrentCpuIndex == gSmst_1068->NumberOfCpus;
break;
}
}
// if RBX value is "$H2O" for some CPU
if ( !NotFound )
{
Status = (gSmmCpu_10A0->ReadSaveState)(
gSmmCpu_10A0,
4i64,
EFI_SMM_SAVE_STATE_REGISTER_RAX,
*CpuIndex,
&RegisterValue);
if ( (Status & 0x8000000000000000ui64) == 0i64 )
{
AsfSecureBootValue[0] = BYTE1(RegisterValue);
// Set "AsfSecureBoot" to dword from RAX
Status = gRT_1018->SetVariable(L"AsfSecureBoot", &VendorGuid, 7u, 1ui64, AsfSecureBootValue);// vulnerability
if ( (Status & 0x8000000000000000ui64) == 0i64 )
LODWORD(Status) = x_GetSetVariablePtr();
}
}
}
CpuNum = *CpuIndex;
LODWORD(RegisterValue) = Status;
// WriteSaveState instead of ReadSafeState (to return the result)
(gSmmCpu_10A0->ReadSaveState)(gSmmCpu_10A0, 4i64, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuNum, &RegisterValue);
return 0i64;
}
The SetVariable()
service will be called if two conditions are met:
g_ReadyToBootRegistered
flag not set 0x2448324F
($H20
) for some CPUThe handler is registered in the function located at offset 0x5E0
.Below is the decompiled code of this function:
__int64 RegisterHandler()
{
EFI_HANDLE DispatchHandle; // [rsp+20h] [rbp-18h] BYREF
void *Registration; // [rsp+28h] [rbp-10h] BYREF
EFI_SMM_SW_REGISTER_CONTEXT RegisterContext; // [rsp+50h] [rbp+18h] BYREF
EFI_SMM_SW_DISPATCH2_PROTOCOL *EfiSmmSwDispatch2Protocol; // [rsp+58h] [rbp+20h] BYREF
gSmst_1068->SmmLocateProtocol(&EFI_SMM_CPU_PROTOCOL_GUID_E00, 0i64, &gSmmCpu_10A0);
gSmst_1068->SmmLocateProtocol(&EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID_DF0, 0i64, &EfiSmmSwDispatch2Protocol);
RegisterContext.SwSmiInputValue = 0x56i64;
(EfiSmmSwDispatch2Protocol->Register)(EfiSmmSwDispatch2Protocol, SwSmiHandler_474, &RegisterContext, &DispatchHandle);
gSmst_1068->SmmRegisterProtocolNotify(
&EDKII_SMM_READY_TO_BOOT_PROTOCOL_GUID_E10,
SetReadyToBootRegisteredFlag,
&Registration);
return 0i64;
}
The g_ReadyToBootRegistered
flag will be set (by the SetReadyToBootRegisteredFlag()
callback) after the SmmReadyToBoot
event is signaled. Due to this fact the vulnerability cannot be exploited from the operating system. However, using EFI_BOOT_SERVICES
and EFI_RUNTIME_SERVICES
services such as SetVariable()
is unsafe inside code intended to run in SMM (from SMRAM) because an attacker capable of executing code in DXE phase could exploit this vulnerability to escalate privileges to SMM (ring -2).
To exploit this vulnerability from DXE it is enough to:
SetVariable()
service address in the EFI_RUNTIME_SERVICES
table with the shellcode address0x56
To fix this vulnerability, it is essential to replace the usage of SetVariable()
service from EFI_RUNTIME_SERVICES
with EfiSmmVariableProtocol->SmmSetVariable()
.
This bug is subject to a 90 day disclosure deadline. After 90 days elapsed or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.
Binarly REsearch Team