当前位置:   article > 正文

【UEFI实战】Secure Boot_shim验签

shim验签

说明

Secure Boot,顾名思义就是用来保证启动安全的一套措施。

Secure Boot是一个比较普通的说法,使用的场景也很多,所以这里要特别说明一下,这里指的是UEFI BIOS下的,用来启动诸如Windows、Mac OS之类系统的“Secure Boot”。

Secure Boot最早在《UEFI Spec》2.3.1版本中提出,关于它的具体说明,可以参考下面的网站:

Secure boot | Microsoft Docs

Frequently Asked Questions about Secure Boot

为什么要使用Secure Boot?

首先BIOS执行的过程中可能需要运行一些第三方的程序。比如有一张显卡,其上包含OPROM(就是BIOS下可运行的初始化程序),BIOS为了初始化这张显卡,就会从该显卡处获取到OPROM然后执行,这是一个例子,但是对于这种情况还好,即使不执行显卡的初始化,影响也不大。但是还存在另外的一个东西,是大部分系统启动所必须的,那就是GRUB。通常支持UEFI的系统在安装过程中都会将一个GRUB文件到磁盘的FAT32分区中,然后UEFI BIOS启动时会执行这个文件,并通过这个文件找到系统内核并启动操作系统。

这个GRUB也是一个第三方的程序,关于它可以参考下面的文章:

【GRUB】GRUB2基本操作

【GRUB】GRUB2编译与使用

【GRUB】增加自定义命令

【GRUB】GRUB2代码初步解析

另外,在UEFI Shell下也可以执行第三方的程序,具体可以参考【UEFI基础】UEFI Shell 

当我们要执行这些第三方程序时,如何保证它们的安全呢?以及当GRUB加载内核时,如何保证内核是安全的呢?

正是因为有了这些顾虑,才引入了Secure Boot。

当开启了Secure Boot功能之后,在执行第三方程序时,都会对它们进行验签,只有保证其数字签名正常,才会将控制权限交给它们。

关于验签、数字签名等概念,稍后会说明。

之后需要说明的是Secure Boot存在的问题。在搜索网站上实际上会看到很多对于“Disable Secure Boot”的咨询,这是因为Secure Boot在保证安全的同时也带来了很多麻烦。比如买了一张新的显卡,但是因为其OPROM没有有效签名,结果卡死在BIOS下面了;又比如想换个系统,结果因为该系统的GRUB没有签名,导致系统起不来了,等等。

关于如何打开和关闭Secure Boot,实际上在BIOS的配置菜单中都提供相应的开关,下面是惠普笔记本的一个例子:

从上图我们也可以看到之前遗留的一个问题,即签名的问题。

除了打开和关闭Secure Boot(Legacy不在我们的讨论中),这里还涉及到key的管理,包括使用微软的key,以及导入自定义的key,实际上在保护Windows启动过程 - Microsoft 365 Security | Microsoft Docs中也有说明:

  • 使用具有认证的启动加载程序的操作系统。 由于所有已认证的 Windows10 电脑必须信任 Microsoft 的证书,Microsoft 提供了一种服务来分析和签署任何非 Microsoft 的引导程序,以便所有认证的 Windows10 电脑能够获得信任。 事实上,已经有了能够加载 Linux 的开源启动加载程序。 若要开始获取证书的过程,请转到Sign in to your account
  • 将 UEFI 配置为信任自定义的启动加载程序。 所有经认证的 Windows10 电脑都允许你通过将签名添加到 UEFI 数据库来信任未验证的加载程序,从而允许你运行任何操作系统,包括 homemade 操作系统。
  • 关闭安全启动。 所有经认证的 Windows10 Pc 允许您关闭安全启动,以便您可以运行任何软件。 但是,这不会帮助保护你免受 bootkit 的攻击。

默认UEFI支持微软的key,即通过微软签名的第三方程序UEFI会认为是安全的。同时我们也可以自己给第三方程序签名,然后通过使用自己的key来对第三方程序进行验签。

最后的最后,再说明下对于启动Linux的GRUB,事实上现在已经有可以支持安全启动和Linux版本的GRUB,称为shim,可以在mjg59 | Secure Boot bootloader for distributions available now中看到具体的说明。

实现

对于第三方的应用如何进行验签,可以从UEFI Shell下执行UEFI应用开始。

对应的代码如下(位于Shell.c中,参考代码vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.):

  1. case Efi_Application:
  2. //
  3. // Get the device path of the application image
  4. //
  5. DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath);
  6. if (DevPath == NULL){
  7. Status = EFI_OUT_OF_RESOURCES;
  8. break;
  9. }
  10. //
  11. // Execute the device path
  12. //
  13. Status = InternalShellExecuteDevicePath(
  14. &gImageHandle,
  15. DevPath,
  16. CmdLine,
  17. NULL,
  18. &StartStatus
  19. );

该函数内部最重要的两步如下:

  1. //
  2. // Load the image with:
  3. // FALSE - not from boot manager and NULL, 0 being not already in memory
  4. //
  5. Status = gBS->LoadImage(
  6. FALSE,
  7. *ParentImageHandle,
  8. (EFI_DEVICE_PATH_PROTOCOL*)DevicePath,
  9. NULL,
  10. 0,
  11. &NewHandle);
  12. //
  13. // now start the image and if the caller wanted the return code pass it to them...
  14. //
  15. if (!EFI_ERROR(Status)) {
  16. StartStatus = gBS->StartImage(
  17. NewHandle,
  18. 0,
  19. NULL
  20. );

但其实这里并没有出现验签相关的代码。还需要追踪到LoadImage函数中,对应的实现在MdeModulePkg\Core\Dxe\Image\Image.c:

  1. EFI_STATUS
  2. EFIAPI
  3. CoreLoadImage (
  4. IN BOOLEAN BootPolicy,
  5. IN EFI_HANDLE ParentImageHandle,
  6. IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
  7. IN VOID *SourceBuffer OPTIONAL,
  8. IN UINTN SourceSize,
  9. OUT EFI_HANDLE *ImageHandle
  10. )

它的实现中有如下的代码:

  1. if (gSecurity2 != NULL) {
  2. //
  3. // Verify File Authentication through the Security2 Architectural Protocol
  4. //
  5. SecurityStatus = gSecurity2->FileAuthentication (
  6. gSecurity2,
  7. OriginalFilePath,
  8. FHand.Source,
  9. FHand.SourceSize,
  10. BootPolicy
  11. );
  12. if (!EFI_ERROR (SecurityStatus) && ImageIsFromFv) {
  13. //
  14. // When Security2 is installed, Security Architectural Protocol must be published.
  15. //
  16. ASSERT (gSecurity != NULL);
  17. //
  18. // Verify the Authentication Status through the Security Architectural Protocol
  19. // Only on images that have been read using Firmware Volume protocol.
  20. //
  21. SecurityStatus = gSecurity->FileAuthenticationState (
  22. gSecurity,
  23. AuthenticationStatus,
  24. OriginalFilePath
  25. );
  26. }
  27. } else if ((gSecurity != NULL) && (OriginalFilePath != NULL)) {
  28. //
  29. // Verify the Authentication Status through the Security Architectural Protocol
  30. //
  31. SecurityStatus = gSecurity->FileAuthenticationState (
  32. gSecurity,
  33. AuthenticationStatus,
  34. OriginalFilePath
  35. );
  36. }

这里涉及到两个Protocol,其实是一个Protocol的两个版本:

  1. EFI_SECURITY_ARCH_PROTOCOL *gSecurity = NULL;
  2. EFI_SECURITY2_ARCH_PROTOCOL *gSecurity2 = NULL;

其中版本1是必须要的,而版本2是可选的:

  1. //
  2. // DXE Core Global Variables for all of the Architectural Protocols.
  3. // If a protocol is installed mArchProtocols[].Present will be TRUE.
  4. //
  5. // CoreNotifyOnArchProtocolInstallation () fills in mArchProtocols[].Event
  6. // and mArchProtocols[].Registration as it creates events for every array
  7. // entry.
  8. //
  9. EFI_CORE_PROTOCOL_NOTIFY_ENTRY mArchProtocols[] = {
  10. { &gEfiSecurityArchProtocolGuid, (VOID **)&gSecurity, NULL, NULL, FALSE },
  11. //
  12. // Optional protocols that the DXE Core will use if they are present
  13. //
  14. EFI_CORE_PROTOCOL_NOTIFY_ENTRY mOptionalProtocols[] = {
  15. { &gEfiSecurity2ArchProtocolGuid, (VOID **)&gSecurity2, NULL, NULL, FALSE },

上述的Protocol通过若干个库、PCD和驱动等组件来实现,如下所示(以BeniPkg为例):

  1. PlatformSecureLib|OvmfPkg/Library/PlatformSecureLib/PlatformSecureLib.inf
  2. TpmMeasurementLib|SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.inf
  3. AuthVariableLib|SecurityPkg/Library/AuthVariableLib/AuthVariableLib.inf
  4. gUefiOvmfPkgTokenSpaceGuid.PcdSecureBootEnable|TRUE
  5. gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
  6. MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf {
  7. <LibraryClasses>
  8. NULL|SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.inf
  9. }
  10. SecurityPkg/VariableAuthenticated/SecureBootConfigDxe/SecureBootConfigDxe.inf

下面一一介绍这些组件。

组件介绍

本节主要介绍上面提到的支持Secure Boot的组件。

PlatformSecureLib

该库非常简单,只提供了一个接口:

  1. /**
  2. This function provides a platform-specific method to detect whether the platform
  3. is operating by a physically present user.
  4. Programmatic changing of platform security policy (such as disable Secure Boot,
  5. or switch between Standard/Custom Secure Boot mode) MUST NOT be possible during
  6. Boot Services or after exiting EFI Boot Services. Only a physically present user
  7. is allowed to perform these operations.
  8. NOTE THAT: This function cannot depend on any EFI Variable Service since they are
  9. not available when this function is called in AuthenticateVariable driver.
  10. @retval TRUE The platform is operated by a physically present user.
  11. @retval FALSE The platform is NOT operated by a physically present user.
  12. **/
  13. BOOLEAN
  14. EFIAPI
  15. UserPhysicalPresent (
  16. VOID
  17. );

如注释所说,这个函数保证对Secure Boot的相关修改必须有实际存在的人进行操作,比如说会弹出一个框,需要接键盘来选择确认,当然这个会根据不同的平台来实现,有些可能根本就不需要特别实现。

TpmMeasurementLib

它也提供了一个接口:

  1. /**
  2. Tpm measure and log data, and extend the measurement result into a specific PCR.
  3. @param[in] PcrIndex PCR Index.
  4. @param[in] EventType Event type.
  5. @param[in] EventLog Measurement event log.
  6. @param[in] LogLen Event log length in bytes.
  7. @param[in] HashData The start of the data buffer to be hashed, extended.
  8. @param[in] HashDataLen The length, in bytes, of the buffer referenced by HashData
  9. @retval EFI_SUCCESS Operation completed successfully.
  10. @retval EFI_UNSUPPORTED TPM device not available.
  11. @retval EFI_OUT_OF_RESOURCES Out of memory.
  12. @retval EFI_DEVICE_ERROR The operation was unsuccessful.
  13. **/
  14. EFI_STATUS
  15. EFIAPI
  16. TpmMeasureAndLogData (
  17. IN UINT32 PcrIndex,
  18. IN UINT32 EventType,
  19. IN VOID *EventLog,
  20. IN UINT32 LogLen,
  21. IN VOID *HashData,
  22. IN UINT64 HashDataLen
  23. );

这个是配合TPM使用的,应该并不是必需的。

AuthVariableLib

它是对变量的扩展,在MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf中使用到这个库。比如:

  1. if (mVariableModuleGlobal->VariableGlobal.AuthFormat) {
  2. //
  3. // Authenticated variable initialize.
  4. //
  5. mAuthContextIn.StructSize = sizeof (AUTH_VAR_LIB_CONTEXT_IN);
  6. mAuthContextIn.MaxAuthVariableSize = mVariableModuleGlobal->MaxAuthVariableSize - GetVariableHeaderSize ();
  7. Status = AuthVariableLibInitialize (&mAuthContextIn, &mAuthContextOut);
  8. if (!EFI_ERROR (Status)) {
  9. DEBUG ((EFI_D_INFO, "Variable driver will work with auth variable support!\n"));
  10. mVariableModuleGlobal->VariableGlobal.AuthSupport = TRUE;
  11. if (mAuthContextOut.AuthVarEntry != NULL) {
  12. for (Index = 0; Index < mAuthContextOut.AuthVarEntryCount; Index++) {
  13. VariableEntry = &mAuthContextOut.AuthVarEntry[Index];
  14. Status = VarCheckLibVariablePropertySet (
  15. VariableEntry->Name,
  16. VariableEntry->Guid,
  17. &VariableEntry->VariableProperty
  18. );
  19. ASSERT_EFI_ERROR (Status);
  20. }
  21. }
  22. } else if (Status == EFI_UNSUPPORTED) {
  23. DEBUG ((EFI_D_INFO, "NOTICE - AuthVariableLibInitialize() returns %r!\n", Status));
  24. DEBUG ((EFI_D_INFO, "Variable driver will continue to work without auth variable support!\n"));
  25. mVariableModuleGlobal->VariableGlobal.AuthSupport = FALSE;
  26. Status = EFI_SUCCESS;
  27. }
  28. }

AuthVariableLib是用来增加变量的安全性的。

gUefiOvmfPkgTokenSpaceGuid.PcdSecureBootEnable

这个没有特别好介绍的,因为是OVMF独有的,参考意义不大。

gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy

Policy是用来配置Secure Boot的。实际上这样的Policy有多个:

  1. [PcdsFixedAtBuild, PcdsPatchableInModule]
  2. ## Image verification policy for OptionRom. Only following values are valid:<BR><BR>
  3. # NOTE: Do NOT use 0x5 and 0x2 since it violates the UEFI specification and has been removed.<BR>
  4. # 0x00000000 Always trust the image.<BR>
  5. # 0x00000001 Never trust the image.<BR>
  6. # 0x00000002 Allow execution when there is security violation.<BR>
  7. # 0x00000003 Defer execution when there is security violation.<BR>
  8. # 0x00000004 Deny execution when there is security violation.<BR>
  9. # 0x00000005 Query user when there is security violation.<BR>
  10. # @Prompt Set policy for the image from OptionRom.
  11. # @ValidRange 0x80000001 | 0x00000000 - 0x00000005
  12. gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x04|UINT32|0x00000001
  13. ## Image verification policy for removable media which includes CD-ROM, Floppy, USB and network.
  14. # Only following values are valid:<BR><BR>
  15. # NOTE: Do NOT use 0x5 and 0x2 since it violates the UEFI specification and has been removed.<BR>
  16. # 0x00000000 Always trust the image.<BR>
  17. # 0x00000001 Never trust the image.<BR>
  18. # 0x00000002 Allow execution when there is security violation.<BR>
  19. # 0x00000003 Defer execution when there is security violation.<BR>
  20. # 0x00000004 Deny execution when there is security violation.<BR>
  21. # 0x00000005 Query user when there is security violation.<BR>
  22. # @Prompt Set policy for the image from removable media.
  23. # @ValidRange 0x80000001 | 0x00000000 - 0x00000005
  24. gEfiSecurityPkgTokenSpaceGuid.PcdRemovableMediaImageVerificationPolicy|0x04|UINT32|0x00000002
  25. ## Image verification policy for fixed media which includes hard disk.
  26. # Only following values are valid:<BR><BR>
  27. # NOTE: Do NOT use 0x5 and 0x2 since it violates the UEFI specification and has been removed.<BR>
  28. # 0x00000000 Always trust the image.<BR>
  29. # 0x00000001 Never trust the image.<BR>
  30. # 0x00000002 Allow execution when there is security violation.<BR>
  31. # 0x00000003 Defer execution when there is security violation.<BR>
  32. # 0x00000004 Deny execution when there is security violation.<BR>
  33. # 0x00000005 Query user when there is security violation.<BR>
  34. # @Prompt Set policy for the image from fixed media.
  35. # @ValidRange 0x80000001 | 0x00000000 - 0x00000005
  36. gEfiSecurityPkgTokenSpaceGuid.PcdFixedMediaImageVerificationPolicy|0x04|UINT32|0x00000003

它会在后续模块的具体实现中使用的,用来定义如何处理各类不同的二进制文件。可以从下面的代码中看出来:

  1. //
  2. // Check the image type and get policy setting.
  3. //
  4. switch (GetImageType (File)) {
  5. case IMAGE_FROM_FV:
  6. Policy = ALWAYS_EXECUTE;
  7. break;
  8. case IMAGE_FROM_OPTION_ROM:
  9. Policy = PcdGet32 (PcdOptionRomImageVerificationPolicy);
  10. break;
  11. case IMAGE_FROM_REMOVABLE_MEDIA:
  12. Policy = PcdGet32 (PcdRemovableMediaImageVerificationPolicy);
  13. break;
  14. case IMAGE_FROM_FIXED_MEDIA:
  15. Policy = PcdGet32 (PcdFixedMediaImageVerificationPolicy);
  16. break;
  17. default:
  18. Policy = DENY_EXECUTE_ON_SECURITY_VIOLATION;
  19. break;
  20. }

可以看到对于BIOS本身的二进制FV都是默认执行的;而对于其他的比如OPROM,USB中的Grub等,都需要根据不同的配置来确定如何处理。

SecurityStubDxe.inf

这个是实现Secure Boot的主体驱动,但是其实它也是依赖的一个库的:

  1. !if $(SECURE_BOOT_ENABLE) == TRUE
  2. MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf {
  3. <LibraryClasses>
  4. NULL|SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.inf
  5. }
  6. !else
  7. MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf
  8. !endif

所以这里的DxeImageVerificationLib.inf才是重点。后面还会进一步介绍。

SecureBootConfigDxe.inf

这个驱动主要用来提供Setup界面,可以对Secure Boot进行一些必要的配置,正如我们在前面的惠普笔记本的Setup下看到的那样。

下面是vUDK2017这份代码下的Setup界面:

可以看到这里Secure Boot是关闭的。

主体实现

讲到主体实现,就需要回到前面介绍的EFI_SECURITY_ARCH_PROTOCOL和EFI_SECURITY2_ARCH_PROTOCOL的实现。

这里以EFI_SECURITY2_ARCH_PROTOCOL为例,下面是其成员的实现:

  1. EFI_STATUS
  2. EFIAPI
  3. Security2StubAuthenticate (
  4. IN CONST EFI_SECURITY2_ARCH_PROTOCOL *This,
  5. IN CONST EFI_DEVICE_PATH_PROTOCOL *File,
  6. IN VOID *FileBuffer,
  7. IN UINTN FileSize,
  8. IN BOOLEAN BootPolicy
  9. )
  10. {
  11. EFI_STATUS Status;
  12. if (FileBuffer != NULL) {
  13. Status = Defer3rdPartyImageLoad (File, BootPolicy);
  14. if (EFI_ERROR (Status)) {
  15. return Status;
  16. }
  17. }
  18. return ExecuteSecurity2Handlers (EFI_AUTH_OPERATION_VERIFY_IMAGE |
  19. EFI_AUTH_OPERATION_DEFER_IMAGE_LOAD |
  20. EFI_AUTH_OPERATION_MEASURE_IMAGE |
  21. EFI_AUTH_OPERATION_CONNECT_POLICY,
  22. 0,
  23. File,
  24. FileBuffer,
  25. FileSize,
  26. BootPolicy
  27. );
  28. }

这个实现就是在前面提到的SecurityStubDxe.inf。

ExecuteSecurity2Handlers()的实现中,最重要的是如下的部分:

  1. Status = mSecurity2Table[Index].Security2Handler (
  2. AuthenticationStatus,
  3. File,
  4. FileBuffer,
  5. FileSize,
  6. BootPolicy
  7. );

这里的mSecurity2Table是一个数组,对应的处理函数就来自DxeImageVerificationLib的注册:

  1. return RegisterSecurity2Handler (
  2. DxeImageVerificationHandler,
  3. EFI_AUTH_OPERATION_VERIFY_IMAGE | EFI_AUTH_OPERATION_IMAGE_REQUIRED
  4. );

到这里我看可以看到真正做验签的函数就是:

  1. /**
  2. Provide verification service for signed images, which include both signature validation
  3. and platform policy control. For signature types, both UEFI WIN_CERTIFICATE_UEFI_GUID and
  4. MSFT Authenticode type signatures are supported.
  5. In this implementation, only verify external executables when in USER MODE.
  6. Executables from FV is bypass, so pass in AuthenticationStatus is ignored.
  7. The image verification policy is:
  8. If the image is signed,
  9. At least one valid signature or at least one hash value of the image must match a record
  10. in the security database "db", and no valid signature nor any hash value of the image may
  11. be reflected in the security database "dbx".
  12. Otherwise, the image is not signed,
  13. The SHA256 hash value of the image must match a record in the security database "db", and
  14. not be reflected in the security data base "dbx".
  15. Caution: This function may receive untrusted input.
  16. PE/COFF image is external input, so this function will validate its data structure
  17. within this image buffer before use.
  18. @param[in] AuthenticationStatus
  19. This is the authentication status returned from the security
  20. measurement services for the input file.
  21. @param[in] File This is a pointer to the device path of the file that is
  22. being dispatched. This will optionally be used for logging.
  23. @param[in] FileBuffer File buffer matches the input file device path.
  24. @param[in] FileSize Size of File buffer matches the input file device path.
  25. @param[in] BootPolicy A boot policy that was used to call LoadImage() UEFI service.
  26. @retval EFI_SUCCESS The file specified by DevicePath and non-NULL
  27. FileBuffer did authenticate, and the platform policy dictates
  28. that the DXE Foundation may use the file.
  29. @retval EFI_SUCCESS The device path specified by NULL device path DevicePath
  30. and non-NULL FileBuffer did authenticate, and the platform
  31. policy dictates that the DXE Foundation may execute the image in
  32. FileBuffer.
  33. @retval EFI_OUT_RESOURCE Fail to allocate memory.
  34. @retval EFI_SECURITY_VIOLATION The file specified by File did not authenticate, and
  35. the platform policy dictates that File should be placed
  36. in the untrusted state. The image has been added to the file
  37. execution table.
  38. @retval EFI_ACCESS_DENIED The file specified by File and FileBuffer did not
  39. authenticate, and the platform policy dictates that the DXE
  40. Foundation many not use File.
  41. **/
  42. EFI_STATUS
  43. EFIAPI
  44. DxeImageVerificationHandler (
  45. IN UINT32 AuthenticationStatus,
  46. IN CONST EFI_DEVICE_PATH_PROTOCOL *File,
  47. IN VOID *FileBuffer,
  48. IN UINTN FileSize,
  49. IN BOOLEAN BootPolicy
  50. )

至于该函数的实现,就可以直接查看代码来了解,这里面涉及一些HASH算法,所以对于这方面也需要有一定的基础,否则可能代码看上去比较困难。

以上,就是对Secure Boot的介绍。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/887537
推荐阅读
相关标签
  

闽ICP备14008679号