An attacker with high local access can exploit this vulnerability to elevate privileges from ring 3 or ring 0 (depends on the operating system) to a DXE driver and execute arbitrary code. Malicious code installed as a result of this exploitation could survive operating system (OS) boot process and runtime, or modify NVRAM area on the SPI flash storage (to gain persistence). Additionally, threat actors could use this vulnerability to bypass OS security mechanisms (modify privileged memory or runtime variables), influence OS boot process, and in some cases allow an attacker to hook or modify EFI Runtime services.
Binarly REsearch Team has discovered a stack overflow vulnerability that allows a potential attacker to access UEFI DXE driver and execute arbitrary code.
An attacker with high local access can exploit this vulnerability to elevate privileges from ring 3 or ring 0 (depends on the operating system) to a DXE driver and execute arbitrary code. Malicious code installed as a result of this exploitation could survive operating system (OS) boot process and runtime, or modify NVRAM area on the SPI flash storage (to gain persistence). Additionally, threat actors could use this vulnerability to bypass OS security mechanisms (modify privileged memory or runtime variables), influence OS boot process, and in some cases allow an attacker to hook or modify EFI Runtime services.
Let's take Edge Gateway 3200's firmware (version: 103, module sha256: 99e00b3febba1763a7ff3f1eb043872506a00032ea2166b3611c041f8e56a3bb) as an example.
The following code in the module actually allows to overflow a stack buffer:
gRT->GetVariable()
offset: 0x10741
gRT->GetVariable()
offset: 0x10840
void *__fastcall sub_105AC(void *a1)
{
char v2; // di
unsigned int v3; // esi
void *result; // rax
void *v5; // r12
__int64 v6; // rdx
__int64 v7; // rdx
__int64 v8; // rdx
unsigned int v9; // ebx
char *v10; // rsi
__int64 v11; // r14
char v12; // al
char *v13; // rbx
const __int16 *v14; // rax
__int64 v15; // rbx
UINTN v16; // rbx
bool v17; // zf
__int64 v18; // rdi
const __int16 *v19; // rax
__int16 *v20; // rdx
const __int16 *v21; // rax
UINTN v22; // rdi
__int64 v23; // rdi
const __int16 *v24; // rax
__int16 *v25; // rdx
const __int16 *v26; // rax
UINTN v27; // rdi
__int64 v28; // rdi
const __int16 *v29; // rax
__int16 *v30; // rdx
const __int16 *v31; // rax
UINTN v32; // rdi
__int64 v33; // r8
__int16 *v34; // rax
__int16 *v35; // rdx
__int16 *v36; // rax
char v37; // al
char *v38; // rbx
char v39; // cl
const __int16 *v40; // rax
__int64 v41; // rbx
UINTN v42; // rbx
__int64 v43; // rdi
const __int16 *v44; // rax
__int16 *v45; // rdx
const __int16 *v46; // rax
UINTN v47; // rdi
__int64 v48; // rdi
const __int16 *v49; // rax
__int16 *v50; // rdx
const __int16 *v51; // rax
UINTN v52; // rdi
__int64 v53; // rdi
const __int16 *v54; // rax
__int16 *v55; // rdx
const __int16 *v56; // rax
UINTN v57; // rdi
__int64 v58; // r8
__int16 *v59; // rax
__int16 *v60; // rdx
__int16 *v61; // rax
unsigned int v62; // ebx
char *p_Destination; // rdi
__int64 v64; // rsi
__int64 v65; // [rsp+20h] [rbp-E0h]
__int64 v66; // [rsp+20h] [rbp-E0h]
char v67[4]; // [rsp+30h] [rbp-D0h] BYREF
char v68; // [rsp+34h] [rbp-CCh]
unsigned int v69; // [rsp+3Ch] [rbp-C4h]
unsigned int v70; // [rsp+40h] [rbp-C0h]
EFI_GUID VendorGuid; // [rsp+48h] [rbp-B8h] BYREF
UINTN v72; // [rsp+60h] [rbp-A0h] BYREF
UINT32 Attributes; // [rsp+68h] [rbp-98h] BYREF
UINTN DataSize; // [rsp+70h] [rbp-90h] BYREF
void *v75; // [rsp+78h] [rbp-88h]
char Buffer[6]; // [rsp+80h] [rbp-80h] BYREF
char Destination; // [rsp+86h] [rbp-7Ah] BYREF
char Data[2784]; // [rsp+180h] [rbp+80h] BYREF
__int16 Source; // [rsp+C80h] [rbp+B80h] BYREF
UINT32 v80; // [rsp+C88h] [rbp+B88h] BYREF
v72 = 20i64;
v80 = 0;
DataSize = 2736i64;
VendorGuid.Data1 = -326642109;
*&VendorGuid.Data2 = 1270213540;
*VendorGuid.Data4 = 1044374945;
v2 = 0;
*&VendorGuid.Data4[4] = -1458720202;
v3 = 0;
result = gRT_0->GetVariable(L"Setup", &VendorGuid, &Attributes, &DataSize, Data);
if ( result >= 0 )
{
if ( gEfiHiiStringProtocol_1
|| (result = gBS_0->LocateProtocol(&EFI_HII_STRING_PROTOCOL_GUID, 0i64, &gEfiHiiStringProtocol_1), result >= 0) )
{
result = sub_10348(a1);
v75 = result;
v5 = result;
if ( result )
{
if ( !Data[21] && !Data[17] )
{
v6 = 7417i64;
if ( Data[11] )
v6 = 7418i64;
sub_10408(a1, v6, 7430i64, result);
v7 = 7436i64;
if ( Data[12] != 1 )
v7 = 7435i64;
sub_10408(a1, v7, 7431i64, v5);
v8 = 7441i64;
if ( Data[13] != 1 )
v8 = 7442i64;
sub_10408(a1, v8, 7432i64, v5);
if ( (gRT_0->GetVariable(L"Tpm12VersionInfo", &PEI_TCG_INTERNAL_FLAGS_GUID, &v80, &v72, v67) & 0x8000000000000000ui64) == 0i64 )
{
gBS_0->SetMem(Buffer, 0x80ui64, 0);
LODWORD(v65) = v69;
sub_6DC(a1, 7493i64, L"%d.%d", BYTE1(v69), v65);
gBS_0->SetMem(Buffer, 0x80ui64, 0);
v9 = v70;
v10 = Buffer;
v11 = 4i64;
do
{
Source = v9;
if ( v9 )
gBS_0->CopyMem(v10, &Source, 2ui64);
v10 += 2;
v9 >>= 8;
--v11;
}
while ( v11 );
sub_6DC(a1, 7492i64, aS, Buffer);
v3 = 4;
}
}
result = gRT_0->GetVariable( // <= second call
L"PCRBitmap",
&PEI_TCG_INTERNAL_FLAGS_GUID,
&v80,
&v72,
v67);
if ( result >= 0 )
{
gBS_0->SetMem(Buffer, 0x80ui64, 0);
v12 = v67[0];
v13 = Buffer;
if ( (v67[0] & 1) != 0 )
{
v14 = L"SHA-1";
v15 = 0i64;
do
{
++v14;
++v15;
}
while ( *v14 );
v16 = 2 * v15;
gBS_0->CopyMem(Buffer, L"SHA-1", v16);
v12 = v67[0];
v13 = &Buffer[v16];
v2 = 1;
}
if ( (v12 & 2) != 0 )
{
v17 = v2 == 0;
v18 = 0i64;
if ( v17 )
{
v21 = L"SHA256";
do
{
++v21;
++v18;
}
while ( *v21 );
v20 = L"SHA256";
}
else
{
v19 = L",SHA256";
do
{
++v19;
++v18;
}
while ( *v19 );
v20 = L",SHA256";
}
v22 = 2 * v18;
gBS_0->CopyMem(v13, v20, v22);
v12 = v67[0];
v13 += v22;
v2 = 1;
}
if ( (v12 & 4) != 0 )
{
v17 = v2 == 0;
v23 = 0i64;
if ( v17 )
{
v26 = L"SHA384";
do
{
++v26;
++v23;
}
while ( *v26 );
v25 = L"SHA384";
}
else
{
v24 = L",SHA384";
do
{
++v24;
++v23;
}
while ( *v24 );
v25 = L",SHA384";
}
v27 = 2 * v23;
gBS_0->CopyMem(v13, v25, v27);
v12 = v67[0];
v13 += v27;
v2 = 1;
}
if ( (v12 & 8) != 0 )
{
v17 = v2 == 0;
v28 = 0i64;
if ( v17 )
{
v31 = L"SHA512";
do
{
++v31;
++v28;
}
while ( *v31 );
v30 = L"SHA512";
}
else
{
v29 = L",SHA512";
do
{
++v29;
++v28;
}
while ( *v29 );
v30 = L",SHA512";
}
v32 = 2 * v28;
gBS_0->CopyMem(v13, v30, v32);
v12 = v67[0];
v13 += v32;
v2 = 1;
}
if ( (v12 & 0x10) != 0 )
{
v33 = 0i64;
if ( v2 )
{
v34 = aSm3;
do
{
++v34;
++v33;
}
while ( *v34 );
v35 = aSm3;
}
else
{
v36 = aSm3_0;
do
{
++v36;
++v33;
}
while ( *v36 );
v35 = aSm3_0;
}
gBS_0->CopyMem(v13, v35, 2 * v33);
}
sub_6DC(a1, 7477i64, aS, Buffer);
gBS_0->SetMem(Buffer, 0x80ui64, 0);
v37 = v68;
v38 = Buffer;
v39 = 0;
if ( (v68 & 1) != 0 )
{
v40 = L"SHA-1";
v41 = 0i64;
do
{
++v40;
++v41;
}
while ( *v40 );
v42 = 2 * v41;
gBS_0->CopyMem(Buffer, L"SHA-1", v42);
v37 = v68;
v38 = &Buffer[v42];
v39 = 1;
}
if ( (v37 & 2) != 0 )
{
v43 = 0i64;
if ( v39 )
{
v44 = L",SHA256";
do
{
++v44;
++v43;
}
while ( *v44 );
v45 = L",SHA256";
}
else
{
v46 = L"SHA256";
do
{
++v46;
++v43;
}
while ( *v46 );
v45 = L"SHA256";
}
v47 = 2 * v43;
gBS_0->CopyMem(v38, v45, v47);
v37 = v68;
v38 += v47;
v39 = 1;
}
if ( (v37 & 4) != 0 )
{
v48 = 0i64;
if ( v39 )
{
v49 = L",SHA384";
do
{
++v49;
++v48;
}
while ( *v49 );
v50 = L",SHA384";
}
else
{
v51 = L"SHA384";
do
{
++v51;
++v48;
}
while ( *v51 );
v50 = L"SHA384";
}
v52 = 2 * v48;
gBS_0->CopyMem(v38, v50, v52);
v37 = v68;
v38 += v52;
v39 = 1;
}
if ( (v37 & 8) != 0 )
{
v53 = 0i64;
if ( v39 )
{
v54 = L",SHA512";
do
{
++v54;
++v53;
}
while ( *v54 );
v55 = L",SHA512";
}
else
{
v56 = L"SHA512";
do
{
++v56;
++v53;
}
while ( *v56 );
v55 = L"SHA512";
}
v57 = 2 * v53;
gBS_0->CopyMem(v38, v55, v57);
v37 = v68;
v38 += v57;
v39 = 1;
}
if ( (v37 & 0x10) != 0 )
{
v58 = 0i64;
if ( v39 )
{
v59 = aSm3;
do
{
++v59;
++v58;
}
while ( *v59 );
v60 = aSm3;
}
else
{
v61 = aSm3_0;
do
{
++v61;
++v58;
}
while ( *v61 );
v60 = aSm3_0;
}
gBS_0->CopyMem(v38, v60, 2 * v58);
}
sub_6DC(a1, 7474i64, aS, Buffer);
gBS_0->SetMem(Buffer, 0x80ui64, 0);
LODWORD(v66) = v69;
sub_6DC(a1, 7493i64, L"%d.%d", HIWORD(v69), v66);
gBS_0->SetMem(Buffer, 0x80ui64, 0);
v62 = v70;
p_Destination = &Destination;
if ( v3 < 4 )
{
v64 = 4 - v3;
do
{
Source = v62;
gBS_0->CopyMem(p_Destination, &Source, 2ui64);
p_Destination -= 2;
v62 >>= 8;
--v64;
}
while ( v64 );
v5 = v75;
}
sub_6DC(a1, 7492i64, aS, Buffer);
return gBS_0->FreePool(v5);
}
}
}
}
return result;
}
The DataSize
is initialized only once (before the first call to gRT->GetVariable()
service).
If the length of Tpm12VersionInfo
NVRAM variable is greater than 20
, the second call to gRT->GetVariable()
service will overflow a stack buffer, which in turn could lead to arbitrary code execution.
To fix this vulnerability the DataSize
must be re-initialized with the size of PCRBitmap
before calling gRT->GetVariable()
.
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