0

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?
zjeffer
  • 311
  • 4
  • 16
  • 1
    Are you using a self signed version of AfuEfix64.efi or are there signed versions available for download? – MiSimon Apr 13 '23 at 14:26
  • @MiSimon self-signed. The signing is not the problem anyway because it also doesn't work with secure boot disabled. – zjeffer Apr 13 '23 at 20:40
  • 1
    If it is self signed you wont be able to run it with secure boot (without enrolling your own keys). Why do you want to avoid the shell without secure boot? – MiSimon Apr 14 '23 at 05:27
  • @MiSimon We're enrolling our own keys. I want to avoid the shell without secure boot because if it doesn't work without the shell, it won't work with secure boot enabled (which is the end goal) – zjeffer Apr 14 '23 at 07:08
  • 1
    AfuEfiX64.efi seems to be a shell app, so you need the shell protocol in place to run it. Would it be possible to build a simple Level1 Shell (without the Debug1, Driver1, Install1 and Network1 commands), sign it, create a boot entry for it and run AfuEfiX64.efi from startup.nsh? – MiSimon Apr 14 '23 at 08:13
  • Can you explain what you mean by Level1 Shell (maybe in an answer)? I can't find anything online about how to build one. – zjeffer Apr 17 '23 at 07:24

1 Answers1

1

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...*
MiSimon
  • 1,225
  • 1
  • 8
  • 10
  • Thanks, I just managed to find it myself not realizing you already answered ;) I had to use -nostartup as an option as well. Now it automatically runs the signed EFI file. We'll probably hardcode the boot options to plug that security hole, and find a way to check whether the Afu efi file is the correct one by adding some kind of OpenSSL solution to the Shell.efi – zjeffer Apr 17 '23 at 09:59
  • 1
    Even with the parameters hardcoded, you should use a level 1 shell without additional commands. If you change the code you should also remove the code that reads and adds the options from "ShellOpt". – MiSimon Apr 17 '23 at 10:07