BIOS实战之cureboot功能的实现⽅法
危险期怎么算什么是Secure boot
Secure boot,顾名思义,就是安全启动,安全启动是由 PC ⾏业成员开发的安全标准,旨在帮助确保设备仅使⽤原始设备制造商(OEM) 信任的软件启动。当 PC 启动时,固件会检查每个启动软件的签名,包括 UEFI 固件驱动程序(也称为 Option ROM)、EFI 应⽤程序和操作系统。如果签名有效,则 PC 将启动,固件将控制权交给操作系统。
O EM 可以使⽤固件制造商的说明创建安全启动密钥并将其存储在电脑固件中。添加 UEFI 驱动程序时,还需要确保这些驱动程序已签名并包含在安全启动数据库中。
⾸先我们看下证书有哪些:
以及他们放置的地⽅,这些放置在fdf⽂件中,通过guid进⾏索引:
Key都是成对的,即公钥与私钥,公钥放置在了BIOS中,私钥⽤来给需要在主板上运⾏的bootloader、驱动以及程序进⾏签名使⽤。
怎么验证安全启动是有效的
使⽤U盘,制作⼀个shell盘,在安全启动开启安装key的前提下,是不可以进⼊shell的,⽽经过签名的shell程序,是能正常进⼊的。
安全引导的密钥类型
数据库密钥(db)——它⽤于对运⾏的⼆进制⽂件(引导加载程序、引导管理器、shell、驱动程序等)进⾏签名或验证。db可以保存多个KEY值——对于某些⽬的来说,这是⼀个重要的事实。注意,db可以包含公钥(与可⽤于对多个⼆进制⽂件签名的私钥相匹配)和hash值(⽤于描述单个⼆进制⽂件)。
数据库⿊名单(dbx)——dbx是⼀种反数据库;它包含与已知恶意软件或其他不受欢迎的软件相对应的键和hash值。可以像安装db⼀样安装键或hash值。如果⼆进制⽂件匹配同时存在于db和dbx中的键或hash值,则dbx应该优先。
密钥交换密钥(KEK)——KEK⽤于对密钥(公钥)进⾏签名,以便固件在将它们输⼊数据库(db或dbx)时将它们视为有效的。如果没有KEK,固件将⽆法知道新密钥是有效的还是由恶意软件提供的。因此,在没有KEK的情况下,安全引导将是⼀个笑话,或者要求数据库保持静态(更新db数据库时验证)。由于安全启动的临界点是dbx,因此静态数据库将不可⽤。电脑通常有两个kek,⼀个来⾃系统⼚商,⼀个来⾃主板制造商。这使得任何⼀⽅都可以发布更新。
平台密钥(PK)——PK是安全启动中的顶级密钥,它与KEK相关的功能类似于KEK与db和dbx的功能。UEFI安全启动⽀持单个PK,通常由主板制造商提供。因此,只有主板制造商可以完全控制计算机。⾃⼰控制安全启动过程的⼀个重要部分是⽤⾃⼰⽣成的PK放在主板flash中,递级验证来保证安全启动的可靠性。
代码梳理
安全启动前的准备:
主要准备条件在dsc、fdf⽂件中,也就是我们需要哪些⽂件去进⾏⽀持
离别的车站1、需要⽀持的库
IntrinsicLib|CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf
AuthVariableLib|SecurityPkg/Security/Library/AuthVariableLib26/AuthVariableLib.inf
BaCryptLib|CryptoPkg/Library/BaCryptLib/BaCryptLib.inf
PlatformSecureLib|SecurityPkg/Library/PlatformSecureLib/PlatformSecureLib.inf
2、Policy的定义选择,也就是哪些⽂件需要什么样的验证⽅式
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x04
gEfiSecurityPkgTokenSpaceGuid.PcdFixedMediaImageVerificationPolicy|0x04
gEfiSecurityPkgTokenSpaceGuid.PcdRemovableMediaImageVerificationPolicy|0x04
2、安全启动的配置⽂件,这⾥⾯包含了界⾯的显⽰,安全启动下Setup mode(⼯⼚模式)Ur mode(⽤户模式)的⽂件加载、数据库的建⽴以及variable的⽣成,这⾥⾯涉及到怎么把前⾯提到的证书通过索引导⼊固件建⽴数据库,并存放在nvrom区域。
Security/SecureBootConfigDxe/SecureBootConfigDxe.inf
3、验签⽂件,也就是验证执⾏的⽂件是否是经过验签的:
MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf {
天津高考作文<LibraryClass>
NULL|SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.inf
!if $(TPM_SUPPORT) == TRUE
NULL|SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf
!endif
}
4、除了正常的botloader和驱动程序外,机器还可以通过⽹络进⾏启动,这⼀部分同样需要进⾏验签才能运⾏,这就需要⽤到下列的⽂件。
NetworkPkg/TlsDxe/TlsDxe.inf
分数通分
NetworkPkg/TlsAuthConfigDxe/TlsAuthConfigDxe.inf
5、nvrom开辟区域供安全启动使⽤
gEfiMdeModulePkgTokenSpaceGuid.PcdMaxVariableSize|0x10000
NorFlashDxe/NorFlashAuthenticatedDxe.inf
如何进⾏验签:
在加载⽣成DB数据库前,需要先了解⽂件是如何进⾏验签,先看⼀张图,后续根据代码了解图⽚中的内容:
咱们以option ROM⽂件验签为例⼦,上⽂中我们看到PcdOptionRomImageVerificationPolicy,在代码中查询,找到位置:DxeImageVerificationLib.C下的DxeImageVerificationHandler函数:
//
// Check the image type and get policy tting.
//
switch (GetImageType (File)) {
ca IMAGE_FROM_FV:
Policy = ALWAYS_EXECUTE;
break;
ca IMAGE_FROM_OPTION_ROM:
Policy = PcdGet32 (PcdOptionRomImageVerificationPolicy);
break;
ca IMAGE_FROM_REMOVABLE_MEDIA:
Policy = PcdGet32 (PcdRemovableMediaImageVerificationPolicy);
break;
ca IMAGE_FROM_FIXED_MEDIA:
Policy = PcdGet32 (PcdFixedMediaImageVerificationPolicy);
break;
default:
Policy = DENY_EXECUTE_ON_SECURITY_VIOLATION;
break;
疲劳反义词}
看下Policy值的定义:
//
// Image type definitions.
/
/
#define IMAGE_UNKNOWN 0x00000001
#define IMAGE_FROM_FV 0x00000002
#define IMAGE_FROM_OPTION_ROM 0x00000004
#define IMAGE_FROM_REMOVABLE_MEDIA 0x00000008
#define IMAGE_FROM_FIXED_MEDIA 0x00000010
//
// Authorization policy bit definition
//
#define ALWAYS_EXECUTE 0x00000000
北京的冬天吉他谱
#define NEVER_EXECUTE 0x00000001
#define ALLOW_EXECUTE_ON_SECURITY_VIOLATION 0x00000002
#define DEFER_EXECUTE_ON_SECURITY_VIOLATION 0x00000003
#define DENY_EXECUTE_ON_SECURITY_VIOLATION 0x00000004
#define QUERY_USER_ON_SECURITY_VIOLATION 0x00000005
通过判断,如果总是执⾏或者从不执⾏,则直接返回,然后我们再去找cureboot的variable,判断它是开还是关的,是关的那么直接不验证,也就是正常启动,如果是开的,咱们再继续进⾏下⼀步,获取DOS的header
//
// Read the Dos header.
//
if (FileBuffer == NULL) {
return EFI_INVALID_PARAMETER;
}
mImageBa = (UINT8 *) FileBuffer;
mImageSize = FileSize;
ZeroMem (&ImageContext, sizeof (ImageContext));
ImageContext.Handle = (VOID *) FileBuffer;
ImageContext.ImageRead = (PE_COFF_LOADER_READ_FILE) DxeImageVerificationLibImageRead;
判断是否为有效的PE image:
//
// Get information about the image being loaded
//
Status = PeCoffLoaderGetImageInfo (&ImageContext);
彩蛋简笔画
if (EFI_ERROR (Status)) {
//
// The information can't be got from the invalid PeImage
//
DEBUG ((DEBUG_INFO, "DxeImageVerificationLib: PeImage invalid. Cannot retrieve image information.\n"));
goto Done;
}
如果是有效的,再继续获取PE header,然后检查PE/COFF image---> Get the magic value from the PE/COFF Optional Header-->U PE32 offt.-->U PE32+ offt-->Start Image Validation.
前⾯的直接略过了,最重要的image验证就此开始,⾸先将image的hash值进⾏匹配,查看其是否是验签的,如果没有验签,则查看hash 值数据是否在DB的数据库中存在,且不存在DBX的数据库中,
如果符合的话,验证通过,如果是验签的,⾥⾯包含验签的信息,则遍历DB 数据库中的证书,查看是否有符合的,如果有符合的且不再DBX数据库中,验证通过,如果都不满⾜,则说明这个验签的image不符合。具体代码就不贴了,代码有点多,逐句分析也有些晦涩难懂,不过意思⼤概就是这么个意思。下⾯来说下SecureBootConfigDxe配置⽂件
SecureBootConfigDxe
这⾥⾯会有UNI、VFR⽂件等,也就是会在BIOS设置界⾯⽣成⼀个选项,供⽤户选择,当启动安全模式的时候,会有两个模式,⼯⼚模式和⽤户模式,entrypoint什么的就不介绍了,还有些variable相关的设置,主要说下这⾥⾯是怎么将证书导⼊⽣成DB数据库以及DBX数据库的。
供后续理解:
typedef struct{
EFI_GUID *VarGuid;
CHAR16 *VarName;
} AUTHVAR_KEY_NAME;
星巴克中杯
STATIC AUTHVAR_KEY_NAME gSecureKeyVariableList[] = {
{&gEfiGlobalVariableGuid, EFI_PLATFORM_KEY_NAME}, // PK 0
{&gEfiGlobalVariableGuid, EFI_KEY_EXCHANGE_KEY_NAME}, // KEK 1
{&gEfiImageSecurityDatabaGuid, EFI_IMAGE_SECURITY_DATABASE}, // db 2
{&gEfiImageSecurityDatabaGuid, EFI_IMAGE_SECURITY_DATABASE1}, // dbx 3
};
STATIC
struct{
EFI_GUID *File;
CHAR16 *UiName;
AUTHVAR_KEY_NAME *KeyName;
UINT8 *Data;
UINTN DataSize;
EFI_GUID *SignatureOwner;
}gSecureKeyTable[] = {
{(EFI_GUID*)PcdGetPtr(PcdSecureKeyDBFile), L"DB", &gSecureKeyVariableList[2], NULL, 0, &gMySignatureOwnerGuid},
{(EFI_GUID*)PcdGetPtr(PcdSecureKeyMSKEKFile), L"MSKEK", &gSecureKeyVariableList[1], NULL, 0, &gMicrosoftSignatureOwnerGuid},
{(EFI_GUID*)PcdGetPtr(PcdSecureKeyMSProFile), L"MSPro", &gSecureKeyVariableList[2], NULL, 0, &gMicrosoftSignatureOwnerGuid},
{(EFI_GUID*)PcdGetPtr(PcdSecureKeyMSUEFFile), L"MSUEF", &gSecureKeyVariableList[2], NULL, 0, &gMicrosoftSignatureOwnerGuid},
{(EFI_GUID*)PcdGetPtr(PcdSecureKeyPKFile), L"PK", &gSecureKeyVariableList[0], NULL, 0, &gMySignatureOwnerGuid},
};
以增加DBXkey为例⼦:
STATIC
EFI_STATUS
AddDbxInitKey (
VOID
)
{
EFI_STATUS Status;
Status = AppendX509FromFV(
(EFI_GUID*)PcdGetPtr(PcdSecureKeyMSDBXFile),
gSecureKeyVariableList[3].VarName,
gSecureKeyVariableList[3].VarGuid,
&gMicrosoftSignatureOwnerGuid
);
DEBUG((EFI_D_INFO, "%a() %r\n", __FUNCTION__, Status));
return Status;
}
STATIC
EFI_STATUS
AppendX509FromFV(
IN EFI_GUID *CertificateGuid,
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,