CVE-2025-33045
An attacker could exploit this vulnerability to elevate privileges from ring 0 to ring -2 and execute arbitrary code in System Management Mode, an environment more privileged than and completely isolated from the operating system (OS). Running arbitrary code in SMM also bypasses SMM-based SPI flash protections against modification, which can help an attacker to install a firmware backdoor/implant. Such malicious code in the firmware could persist through operating system reinstallations. In addition, this vulnerability could potentially be used by malicious actors to bypass security mechanisms provided by UEFI firmware, such as Secure Boot and some types of memory isolation for hypervisors.
Let's consider the module 66505b60fd0d7276658cd80fd680c829f05fb67bd5293572466db09e3e2b5059.
The pseudocode of the vulnerable function is shown below:
EFI_STATUS SwSmiHandler(
EFI_HANDLE DispatchHandle,
const void *Context,
EFI_SMM_SW_CONTEXT *CommBuffer,
UINTN *CommBufferSize)
{
UINTN CpuIndex;
SREDIR_INPUT_PARAMETER *SredirParam;
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS MMIOAddress;
UINT32 RcxValue;
UINT32 RbxValue;
SREDIR_INPUT_PARAMETER BufferCopy;
RcxValue = 0;
RbxValue = 0;
if ( !gAcpiDisTriggered && gMMIOAddress )
{
if ( !CommBuffer )
return 0;
if ( !CommBufferSize )
return 0;
CpuIndex = CommBuffer->SwSmiCpuIndex;
if ( CommBuffer->SwSmiCpuIndex == -1 )
return 0;
gEfiSmmCpuProtocol->ReadSaveState(
gEfiSmmCpuProtocol,
4,
EFI_SMM_SAVE_STATE_REGISTER_RBX,
CommBuffer->SwSmiCpuIndex,
&RbxValue);
gEfiSmmCpuProtocol->ReadSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RCX, CpuIndex, &RcxValue);
SredirParam = (RbxValue + (RcxValue << 32));
Status = BufferInSmram(SredirParam, 13);
if ( Status != 0 )
return Status;
if ( &BufferCopy != SredirParam )
CopyMem(&BufferCopy, SredirParam, 13);
MMIOAddress = gMMIOAddress;
Status = BufferInSmram(gMMIOAddress, 8);
if ( Status )
{
SredirParam->Value = 0;
return Status;
}
switch ( BufferCopy.Cmd )
{
case 1:
SredirParam->Value = *(BufferCopy.Offset + MMIOAddress);
break;
case 2:
// SMRAM write (TSEG)
//
// 8 bytes from MMIOAddress checked for overlap with SMRAM
// BufferCopy.Offset is from 0 to 255
*(BufferCopy.Offset + MMIOAddress) = BufferCopy.Value;
break;
case 3:
if ( BufferCopy.Size )
return WriteBufferSerialPort(&BufferCopy);
else
return 0;
default:
return EFI_INVALID_PARAMETER;
}
return Status;
}
return EFI_INVALID_PARAMETER;
}
As we can see from the pseudocode, 8 bytes from buffer at MMIOAddress is checked for overlap with SMRAM:
MMIOAddress = gMMIOAddress;
Status = BufferInSmram(gMMIOAddress, 8);
It then writes to the same buffer at an offset controlled by the attacker:
case 2:
// SMRAM write (TSEG)
//
// 8 bytes from MMIOAddress checked for overlap with SMRAM
// BufferCopy.Offset is from 0 to 255
*(BufferCopy.Offset + MMIOAddress) = BufferCopy.Value;
break;
BufferCopy.Offset
is UINT8
field and it's not validated, which means that attacker can corrupt any byte from (SMRAM
to SMRAM + 248
) by running the handler once. However, the attacker can execute the handler as many times as required.
Similar primitive is located in WriteBufferSerialPort
function:
EFI_STATUS WriteBufferSerialPort(SREDIR_INPUT_PARAMETER *SredirParam)
{
EFI_STATUS Status;
UINTN Size;
EFI_PHYSICAL_ADDRESS BufferAddress;
UINT8 *BufferCopy;
UINT8 *Copy;
UINT8 Index;
EFI_PHYSICAL_ADDRESS MMIOAddress;
UINTN i;
if ( SredirParam->Size > 0xF )
return 0;
if ( !SredirParam->BufferAddress )
return EFI_INVALID_PARAMETER;
Size = SredirParam->Size;
BufferAddress = SredirParam->BufferAddress;
Status = BufferInSmram(BufferAddress, Size);
if ( !Status )
{
BufferCopy = SmmAllocatePool(6, Size);
Copy = BufferCopy;
if ( BufferCopy )
{
if ( Size && BufferCopy != BufferAddress )
Copy = CopyMem(BufferCopy, BufferAddress, Size);
if ( Copy )
{
Index = 0;
if ( SredirParam->Size )
{
MMIOAddress = gMMIOAddress;
do
{
i = Index++;
// SMRAM write (TSEG)
//
// 8 bytes from MMIOAddress checked for overlap with SMRAM
// BufferCopy.Offset is from 0 to 255
*(SredirParam->Offset + MMIOAddress) = Copy[i];
}
while ( Index < SredirParam->Size );
}
if ( InSmram(Copy) )
gSmst->SmmFreePool(Copy);
else
gBS->FreePool(Copy);
return 0;
}
}
return EFI_OUT_OF_RESOURCES;
}
return Status;
}
MMIOAddress
can be controlled by ChildSwSmiHandler
with HandlerType
value 94eea196-43b8-00db-0066-8944244cb863
:
EFI_STATUS ChildSwSmiHandler(
EFI_HANDLE DispatchHandle,
const void *Context,
void *CommBuffer,
UINTN *CommBufferSize)
{
if ( !CommBuffer || !CommBufferSize )
return 0;
gMMIOAddress = *CommBuffer;
return gSmst->SmiHandlerUnRegister(DispatchHandle);
}
The fact that the handler is unregistered with gSmst->SmiHandlerUnRegister
after initialising the gMMIOAddress
variable makes it more difficult to exploit the vulnerability at runtime. However, this vulnerability can be exploited at the DXE stage if a vulnerability exists that allows arbitrary code to be executed in the DXE.
In the target firmware (SHA256: 009bcc7ac14257f3227d1b0a09751797506b52d952bcc129a71b6089cd514418
) this buffer will be set with the following function from LegacySredir
DXE module:
EFI_STATUS SetMMIOAddress(UINT64 MMIOAddress)
{
EFI_STATUS Status;
EFI_SMM_COMMUNICATE_HEADER Source;
UINTN Size;
UINT8 CommBuffer[16];
UINT64 MMIOAddressParam;
UINTN CommSize;
EFI_SMM_COMMUNICATION_PROTOCOL *EfiSmmCommunicationProtocol;
MMIOAddressParam = MMIOAddress;
Source.HeaderGuid.Data1 = 0x94EEA196;
Source.HeaderGuid.Data2 = 0xDB43;
Status = 0x4263;
Source.HeaderGuid.Data4[0] = 0x87;
Source.HeaderGuid.Data4[1] = 0x93;
Source.HeaderGuid.Data4[2] = 0x60;
Source.HeaderGuid.Data4[3] = 0xB;
Source.HeaderGuid.Data3 = 0x4263;
Source.HeaderGuid.Data4[4] = 0xC4;
Source.HeaderGuid.Data4[5] = 0xFF;
Source.HeaderGuid.Data4[6] = 0xE1;
Source.HeaderGuid.Data4[7] = 8;
if ( !gSmiHandlerTriggered )
{
Status = gBS->LocateProtocol(&EFI_SMM_COMMUNICATION_PROTOCOL_GUID, 0, &EfiSmmCommunicationProtocol);
if ( Status == EFI_SUCCESS )
{
gBS->CopyMem(&Source.MessageLength, &Source, 16);
Size = 8;
gBS->CopyMem(CommBuffer, &MMIOAddressParam, 8);
CommSize = 32;
Status = EfiSmmCommunicationProtocol->Communicate(EfiSmmCommunicationProtocol, &Source.MessageLength, &CommSize);
gSmiHandlerTriggered = 1;
}
}
return Status;
}
This code is only executed after the EFI_EVENT_READY_TO_BOOT_GUID
event (BDS). This event is triggered very late in the boot process, increasing the attack surface.
This vulnerability is subject to a 90 day disclosure period. After 90 days or when a patch has been made generally available (whichever comes first) the advisory will be publicly disclosed.
BINARLY REsearch team