Search code examples
cbiosuefiedk2

Run AfuEfiX64.efi BIOS upgrader as a boot entry instead of through UEFI shell, with secure boot enabled


Usually, to use the AfuEfiX64.efi BIOS upgrader from AMI, you have to run the .efi file through the UEFI shell. This works with secure boot disabled, but the UEFI shell is not accessible when secure boot is enabled, so I have to run this EFI file through a boot entry instead by dynamically creating a boot entry with efibootmgr, like so:

# this creates the boot entry with the correct parameters to upgrade the BIOS
efibootmgr -C --disk /dev/nvme0n1 --part 1 --label "BiosUpgrader" --loader "\EFI\AfuEfix64Signed.efi" --unicode "fs:0/EFI/BIOS_UPDATE_FILE.BIN  /p /b /n /x /k /RLC:F"
# and then make sure it boots next time we restart
efibootmgr -n 000X # (X=number of created boot entry)

This works and creates the boot entry which I can run as if I start the boot loader for an operating system. When I try to boot into this created boot entry from the BIOS firmware interface, even with secure boot disabled, the screen goes dark for a second and then goes back to the firmware interface.

I believe this might happen because maybe the EFI application needs to be run inside of a UEFI shell? To fix this, I created my own EFI application that opens an EFI shell which then calls a .nsh script that runs the AfuEfiX64.efi program with the correct parameters:

#include <Guid/FileInfo.h>
#include <Library/FileHandleLib.h>
#include <Library/PcdLib.h>
#include <Library/ShellLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiShellLib/UefiShellLib.h>
#include <Protocol/EfiShellEnvironment2.h>
#include <Protocol/EfiShellInterface.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/Shell.h>
#include <Protocol/ShellParameters.h>
#include <Uefi.h>

EFI_STATUS
EFIAPI
UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
  // Get the loaded image protocol for the current image
  EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
  EFI_STATUS Status = gBS->HandleProtocol(
      ImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage);
  if (EFI_ERROR(Status)) {
    Print(L"Failed to get loaded image protocol: %r\n", Status);
    gBS->Stall(1000000);
    return Status;
  }

  // initialize the shell
  Status = ShellInitialize();
  if (EFI_ERROR(Status)) {
    Print(L"Failed to initialize the shell: %r\n", Status);
    gBS->Stall(1000000);
    // return Status;
  }

  // Check whether the UEFI Shell protocol is present
  Status = gBS->LocateProtocol(&gEfiShellProtocolGuid, NULL,
                               (VOID **)&gEfiShellProtocol);
  if (EFI_ERROR(Status)) {
    Print(L"The UEFI Shell is not installed on this system: %r\n", Status);
    gBS->Stall(1000000);
    // return Status;
  }

  // The UEFI Shell is installed on this system, run the script
  ShellExecute(ImageHandle, L"bios-update.nsh", FALSE, NULL, &Status);
  if (EFI_ERROR(Status)) {
    Print(L"Failed to run script: %r\n", Status);
    // wait 5 seconds
    gBS->Stall(5000000);
    return Status;
  }

  return EFI_SUCCESS;
}

This doesn't seem to work:

  • I'm getting the error "The UEFI Shell is not installed on this system: Not found"
  • When proceeding, the ShellExecute function returns "Failed to run script: Not found", no matter what I put into the L"<command>" parameter
  • If I try to execute the above EFI application through the UEFI shell, nothing happens and it doesn't output anything.

So here are my questions:

  1. Is there a better way to directly run the AfuEfiX64.efi program, without creating my own EFI file to wrap it inside of a UEFI shell?
  2. Is there a way to get more verbose information for why the EFI program fails to run as a boot entry?
  3. Is it even possible to use ShellExecute when booting from a boot entry (instead of through the UEFI shell)? Or is there something wrong with my script?

Solution

  • A word about security first, having a signed shell on your system can allow an attacker to bypass secure boot, to avoid this risk you must make sure that you are using a scripting shell without any command that allows memory or system manipulation.

    You can build a scripting shell (level 1), create a boot entry with load options and use it as a wrapper for AfuEfiX64.efi.

    • Clone edk2

    • Follow the setup steps

    • Create a new shell build config in ShellPkg\ShellPkg.dsc, there are already 2 build configs, create the new one after them

       ShellPkg/Application/Shell/Shell.inf {
       <Defines>
           FILE_GUID = 9105952F-D171-4F1A-812E-F32B055668C5
       <PcdsFixedAtBuild>
           gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
           gEfiShellPkgTokenSpaceGuid.PcdShellSupportLevel|1
           gEfiShellPkgTokenSpaceGuid.PcdShellProfileMask|0
       <LibraryClasses>
           NULL|ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf
      

      }

    • Build the shell package

    • Create a new boot entry for the Level 1 shell with the required boot options

      -exit \EFI\AfuEfix64Signed.efi [Parameter]

    An example boot entry from a test on my machine, shell_echo.efi is the ShellCTestApp from the ShellPkg:

    FS0:\efi\uoe\> bcfg boot dump -v
    ...
    Option: 08. Variable: Boot0002
      Desc    - Shell (L1)
      DevPath - HD(2,GPT,88B7633D-4441-4F23-A535-3AE7FB88B9F6,0x109000,0x32000)/\EFI\UOE\shell_l1.efi
      Optional- Y
      00000000: 2D 00 65 00 78 00 69 00-74 00 20 00 2D 00 6E 00  *-.e.x.i.t. .-.n.*
      00000010: 6F 00 6D 00 61 00 70 00-20 00 5C 00 65 00 66 00  *o.m.a.p. .\.e.f.*
      00000020: 69 00 5C 00 75 00 6F 00-65 00 5C 00 73 00 68 00  *i.\.u.o.e.\.s.h.*
      00000030: 65 00 6C 00 6C 00 5F 00-65 00 63 00 68 00 6F 00  *e.l.l._.e.c.h.o.*
      00000040: 2E 00 65 00 66 00 69 00-20 00 54 00 65 00 73 00  *..e.f.i. .T.e.s.*
      00000050: 74 00 20 00 31 00 20 00-32 00 20 00 48 00 65 00  *t. .1. .2. .H.e.*
      00000060: 6C 00 6C 00 6F 00 00 00-                         *l.l.o...*