By Binarly REsearch
Software mitigations play a critical role in the quest to secure the digital world. Shortly after the discovery and the rise of buffer overflows in the 90s, mitigations were introduced in the software ecosystem and eventually made their way into virtually any piece of software we run on our devices: from browsers to web servers, from OS kernels to userspace applications.
Mitigations are typically designed to address one or more classes of vulnerabilities, making their exploitation more difficult. For example, while exploiting a stack overflow without any deployed mitigation is straightforward, the presence of properly implemented stack canaries requires chaining additional vulnerabilities or leveraging more complex techniques to bypass this protection.
To date, numerous mitigations have been proposed and implemented. For example, Control Flow Integrity (CFI) ensures that the execution flow follows legitimate paths and cannot be arbitrarily manipulated by attackers. Another example are randomization-based mitigations that load code and data at random addresses, making it difficult for attackers to target predictable memory layouts in their exploits. Over the past 20 years, mitigation techniques have steadily evolved, driven by efforts from both academia and industry. This progress has followed a familiar "cat-and-mouse" pattern in computer security: attackers develop ways to bypass defenses, and defenders respond by improving them.
Today, mitigations are very common in operating systems and software applications, but what about firmware and specifically the UEFI firmware ecosystem?
This research answers this question by setting out with two main goals:
The results of this research paints a worrisome picture of the UEFI ecosystem: only a few devices have basic mitigations enabled by default, while the vast majority lack even the most fundamental protections (e.g. stack canaries).
UEFI firmware is a critical component of modern computing. Given its privileged execution context and wide attack surface, securing UEFI firmware is essential to prevent compromise that could affect the entire system. To address these risks, a variety of mitigations have been developed over the years, reducing exposure to vulnerabilities at different layers of the firmware stack and throughout its lifecycle.
The following taxonomy organizes UEFI firmware mitigations according to the types of threats they address:
Given the broad scope of UEFI firmware security, this research focuses on three key categories: memory safety mitigations, microarchitectural-level mitigations, and UEFI boot security mitigations. For each category, we selected a representative subset of mitigations:
These categories capture some of the most critical aspects of firmware security that directly affect system integrity. The Binarly Transparency Platform (BTP) can detect and report the presence or absence of these mitigations, enabling assessment of potential security gaps in UEFI firmware.
Stack canaries were introduced in the late 1990s and are considered one of the earliest, if not the first, software mitigation. This mitigation protects against buffer overflow attacks by placing a value, known as a stack canary or stack cookie, between local variables and control data (such as, the return address) saved on the stack. During the function epilogue, the canary value is checked. If it has been altered, this indicates a buffer overflow has occurred, and execution is terminated to prevent further exploitation.
This mitigation is available virtually everywhere in the software ecosystem due to its implementation simplicity and minimal overhead. Despite this, stack canaries have been enabled in the reference implementation for most toolchains and architectures only very recently, in February 2025.
Before this recent change, stack canaries were disabled in the EDK2 reference implementation for all toolchains, with the only exception being the GCC toolchain when compiling for ARM targets. All other toolchain definitions explicitly disabled stack canaries by using the GCC option “-fno-stack-protector” or the Visual Studio option “/GS-”. The older ARM-based implementation used static canaries that don’t change at runtime, while the newer implementation uses randomized values.
The UEFI Stack Guard mitigation allocates a guard page at the bottom of the stack to prevent any linear access beyond the stack’s end. This guard page is marked as “not present” in the page table, so any read or write access triggers a page fault. The purpose of this mitigation is to prevent the stack from growing into adjacent memory regions and corrupting them. While Stack Guard was designed with debugging in mind, it can also be employed as mitigation.
The reference implementation includes support for Stack Guard during the PEI and DXE phases as well as for System Management Mode (SMM). Stack Guard for DXE phase is implemented by setting the memory attribute EFI_MEMORY_RP on the page at the bottom of the stack.
This mitigation can be enabled during PEI and DXE by setting the PcdCpuStackGuard option, which is disabled by default in the reference implementation. In contrast, Stack Guard for SMM is controlled by the flag PcdCpuSmmStackGuard and is enabled by default.
Control-flow integrity is another widely used mitigation designed to prevent attackers from hijacking the execution flow. Over the past decade, numerous designs and flavors of CFI have been proposed, each with unique strengths and limitations. A recent WOOT paper systematized actively used CFI schemes and explored their effectiveness. The core idea behind CFI is to validate backward and forward control flow transfers–such as indirect calls and function returns–to ensure that the execution follows only legitimate paths. When enabled, this mitigation can prevent exploits based on code injection or return-oriented programming.
EDK2 supports Intel’s Control-flow Enforcement Technology (CET) as CFI mitigation. CET can detect corruptions of the return address stored on the stack, since it stores a copy of the return address on a secondary stack, called shadow stack, which cannot be tampered with by an attacker. On function return, the return address stored on the shadow stack is compared with the address stored on the “normal” stack to ensure the integrity of the stack.
Shadow stacks are particularly useful in protecting against Return Oriented Programming (ROP) exploits. To protect against Jump-Oriented-Programming (JOP) and Call Oriented Programming (COP), the CET technology uses Indirect Branch Tracking (IBT). With support from the compiler, IBT marks all valid targets of indirect control flow transfers (i.e. calls and jumps) with the endbr instruction. At runtime, an exception is raised if a control-flow transfer does not land directly on an endbr instruction.
The reference implementation contains support for CET shadow stacks, which can be enabled by setting the PcdControlFlowEnforcementPropertyMask. However, the reference implementation is designed only for SMM modules, meaning that SEC, PEI or DXE modules are not safeguarded. This mitigation is disabled by default, even on systems supporting CET. Regarding IBT, the EDK2 toolchain definitions lack the necessary flags to instruct the compiler to insert “endbr” instructions, meaning that compiled modules will not contain the instructions needed by IBT.
ARM has a mitigation similar to IBT called Branch Target Identification (BTI). EDK2 has preprocessor macros for instrumenting hand-written ARM assembly functions with the BTI instruction (“bti c”), which marks targets of indirect function calls. However, the EDK2 toolchain definitions lack the flags for instrumenting compiled code with BTI instructions (i.e. -mbranch-protection).
The No eXecute (NX) protection, also known as Data Execution Prevention (DEP), is a security feature that prevents execution of code stored in memory regions intended for data. Any page that does not contain executable code is marked as non-executable in the page table, effectively stopping any attempt to execute malicious code, such as shellcode, stored there. This mitigation is often used in conjunction with other mitigations that prevents writes to memory regions containing code.
The reference implementation offers three different PCD values to fine-tune this mitigation: PcdImageProtectionPolicy, PcdDxeNxMemoryProtectionPolicy and PcdSetNxForStack. The first option, PcdImageProtectionPolicy, determines whether a DXE module should have its code section marked as read-only and its data section as non-executable. The determination is done depending on the module’s source, which can either be a firmware volume or an unknown source. The option PcdDxeNxMemoryProtectionPolicy allows instead to set certain memory regions types (such as EfiLoaderData) as non-executable. Finally, PcdSetNxForStack allows setting the stack as non-executable.
For X86, PcdImageProtectionPolicy enables protection only for images loaded from firmware volumes, excluding those from unknown devices (such as the EFI System Partition). Both PcdDxeNxMemoryProtectionPolicy and PcdSetNxForStack are disabled by default. As a result, the reference implementation prevents execution from the data section and modification of the code section only for PE images loaded from firmware volumes.
For ARM targets, PcdImageProtectionPolicy is enabled also for unknown sources and PcdDxeNxMemoryProtectionPolicy correctly enables NX for all non-code regions, including the stack.
UEFI firmware images also include updates for the CPU microcode. The microcode breaks down complex instructions into ones that are more easily implemented in hardware. From a security standpoint, the microcode includes mitigations for various microarchitectural weaknesses, such as transient execution attacks. As a result, it is essential for UEFI firmware to always be shipped with the latest revision of the microcode.
The reference implementation has support for microcode updates. This includes the definition of the structures related to microcode which are used for parsing and loading microcode updates.
The Return Stack Buffer is used by the CPU to predict the target of return addresses. Underflows on this buffer can be exploited to mount microarchitectural attacks. When the RSB stuffing mitigation is enabled, dummy return addresses are pushed into this CPU buffer to overwrite potentially malicious entries. This prevents attackers from manipulating the RSB to redirect control flow and achieve arbitrary speculative code execution.
EDK2 provides assembly macros for stuffing the RSB with dummy addresses. These macros are used to protect SMM code, and are inserted just before returning from SMM with the RSM instruction, to mitigate potential speculative attacks. The original patch implementing this mitigation was developed by Intel and contributed to the EDK2 repo in August 2018.
This mitigation is enabled by default.
Intel Boot Guard is a hardware-based security feature that ensures the integrity of the system's firmware by verifying several cryptographic signatures. It allows to establish the root of trust within the firmware, since it protects against unauthorized tampering.
There are several components used by Boot Guard that require verification, for example the Key Manifest (KM) or the Boot Policy Manifest (BPM). These components’ signatures are verified by the CPU using different cryptographic keys, some of which are fused into the platform hardware, while others are instead stored in the firmware itself.
The reference implementation doesn’t include any support for Intel Boot Guard, as this is a proprietary feature implemented by Intel.
The Forbidden Signature Database (dbx) is a crucial component of UEFI Secure Boot. This database contains the cryptographic signatures and certificates associated with compromised or vulnerable UEFI modules.
While each vendor can customize this database, Microsoft maintains a publicly available list of all known threats that should be included in every dbx. Because Secure Boot represents a last line of defense, It is essential to keep the dbx updated, as failing to do so can result in firmware-level threats to be deployed on a target system.
The reference implementation has complete support for parsing the dbx and for using this information to verify any UEFI images, such as bootloaders, before execution.
The uefiplat.cfg file is found in UEFI firmware based on Qualcomm reference code for ARM devices, such as the Microsoft Surface or the Lenovo Thinkpad X13s. As we previously documented, this file contains different firmware configuration items. Some of these items are simply related to the device, such as MemoryMap which stores a list of memory mappings. The SecurityFlag item is instead focused on security features like Secure Boot, measured boot and Trust Zone. Finding this component in a firmware image is straightforward, as it’s stored in a Raw Section with a known GUID.
The reference implementation doesn’t include any support for the uefiplat.cfg file, as this is a proprietary security feature implemented by Qualcomm in their eXtensible BootLoader (XBL).
In the previous section we explored some of the most common mitigations and their implementation within the EDK2 reference code. In summary, EDK2 contains a reasonable number of software mitigations and allows firmware developers to customize them. However, we identified two main areas of concern:
The most significant example of missing mitigation is Address Space Layout Randomization (ASLR), a low-overhead mitigation that is widely used in the software ecosystem. Although a prototype implementation for ASLR was developed in the SecurityEx repository, this standalone project was never ported to the main EDK2 repository.
Other mitigations, such as Heap Guard, are implemented in the reference implementation but discouraged to use. This mitigation is designed to detect heap overflows by placing guard pages between heap pages or heap pools.
Despite its effectiveness, this mitigation is marked as a debug-only feature, with a warning stating that it “should not be enabled in product BIOS”. As we previously reported, this mitigation alone would have been sufficient to prevent our PoC for LogoFAIL.
In this section, we look closer at the mitigations observed in the wild, with a particular focus on different implementations of stack canaries. We chose to focus on stack canaries because they represent one of the most basic yet effective security measures, and because they are the only mitigation present in the reference implementation that is also commonly customized by firmware vendors.
Following this, we present the results of scanning over 5,000 firmware images using our Binarly Transparency Platform. These results represent a first in the industry and provide a clear view of the current state of mitigation adoption within the UEFI ecosystem.
In this section, we will look at the different implementations of stack canaries that we found in the wild. This will provide a new perspective on how different vendors implemented a well-known security defense. An important note is that these implementations were all identified automatically by our platform, which is able to report (the lack of) mitigations in scanned firmware.
The current implementation of dynamic stack canaries present in EDK2 was only recently added in February 2025. Since this implementation is very new, it’s likely not present in real-world firmware. For this reason, we’ll focus on the previous implementation of stack canaries, which was introduced in 2014.
As previously discussed, this implementation is currently enabled only for ARM devices. Even after reviewing the related pull requests and discussions, the rationale behind this decision remains somewhat unclear to us. Additionally, we noticed that the canary value is set to a fixed, predictable value (0x0AFF), rather than being randomized, which raises some concerns from a security perspective.
This choice significantly limits the effectiveness of the implementation in preventing most buffer overflow attacks targeting firmware. Since the canary value is fixed and predictable, an attacker could easily include it in their payloads, thereby bypassing the intended protection. While this implementation does not offer meaningful security benefits, the patch discussion suggests that the implementation was intended as a security feature rather than a debugging one. The 0x0AFF constant was chosen because it contains “NULL (will terminate strings), LF, and -1” with a goal to “contain read based overruns”, an attack vector that is however not present in the UEFI firmware.
data:0000000000010000 __stack_chk_guard DCQ 0xAFF
int64 sub_3504() {
// Setting the canary
stack_canary = __stack_chk_guard;
...
// Checking the canary
if ( stack_canary != __stack_chk_guard)
__stack_chk_fail();
...
}
The reference implementation of stack canaries was found in many ARM devices from Gigabyte, Supermicro, HPE, Dell and Lenovo. However, a closer look at scan results from the Binarly Transparency Platform revealed that only a limited number of modules within these images are protected with stack canaries.
The decision to protect a module does not appear to be based on the module’s security relevance, such as whether it handles untrusted input, but rather seems to depend on the supply-chain partner responsible for compiling it. For instance, the Supermicro G1SMH firmware includes 19 modules protected by canaries (e.g., GPURecovery and RASBaseServices), all of which appear to be supplied by NVIDIA. The protected modules are mostly device drivers, while other security-critical modules, such as Ip6Dxe or TcpDxe, both of which were impacted by the PixieFail vulnerabilities, are left unprotected.
Another interesting finding is that some modules ship with a __stack_chk_fail function (i.e. the function that is called when a canary mismatch is detected) that only returns without taking any action. This behavior effectively nullifies the purpose of the stack canary mechanism, rendering the protection ineffective.
A second custom implementation of stack canaries, which we already covered in our cross-silicon exploitation research, is developed by Qualcomm for ARM devices. This implementation uses the UEFI Configuration Tables and is initialized at a module’s entry point. As shown in the next snippet, the entry point function checks for the presence of the custom configuration table with GUID B898D8DC-080A-40F7-99E3-31627B806A5A. If this table is found, the firmware retrieves the canary value stored within the table. When the table is instead not found, the function will generate a new canary by calling the UpdateCanary() function.
EFI_STATUS ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
UINTN NumberOfTableEntries; // x2
EFI_CONFIGURATION_TABLE *ConfigurationTable; // x3
__int64 v4; // t1
EFI_STATUS result; // x0
EFI_HANDLE ImageHandleCopy; // [xsp+38h] [xbp-18h]
if ( ImageHandle )
{
ImageHandleCopy = ImageHandle;
if ( SystemTable
&& LOBYTE(SystemTable->Hdr.Signature) == 73
&& BYTE1(SystemTable->Hdr.Signature) == 66
&& BYTE2(SystemTable->Hdr.Signature) == 73 )
{
NumberOfTableEntries = SystemTable->NumberOfTableEntries;
ConfigurationTable = SystemTable->ConfigurationTable;
while ( NumberOfTableEntries )
{
--NumberOfTableEntries;
v4 = *&ConfigurationTable->VendorGuid.Data1;
++ConfigurationTable;
if ( v4 == *&QCOM_CANARY_CONFIGURATION_TABLE_GUID_PTR->Data1 )
{
*gCanaryPtr = ConfigurationTable[-1].VendorTable;
goto LABEL_12;
}
}
UpdateCanary();
SystemTable->BootServices->InstallConfigurationTable(QCOM_CANARY_CONFIGURATION_TABLE_GUID_PTR_0, gCanary);
}
else
{
UpdateCanary();
}
LABEL_12:
ImageHandle = ImageHandleCopy;
}
Main(ImageHandle);
return result;
}
In our dataset we found two implementations of the UpdateCanary() function: one returning a hardcoded value (0xc0c0c0c0) and another that creates a random canary using the ARM Secure Monitor Call (SMC). While the first implementation suffers from the same problems discussed in the previous section, the second implementation is strong since it uses non-predictable canaries.
The Qualcomm implementation of stack canaries was found in several ARM-based firmware, such as the Dell XPS 9345, the Lenovo Yoga Slim 7 and the Microsoft Surface. Interestingly, some firmware contains both the “reference implementation” and the “Qualcomm implementation”, reinforcing the idea that modules were compiled by different supply-chain partners. This further highlights the fragmented nature of firmware development, where security features like stack canaries are applied inconsistently depending on the origin of each module.
Finally, our platform also identified a third implementation of stack canaries for x86. After some research we discovered that this implementation is the one used in the iPXE network bootloader. In this implementation the random canary value is generated by combining together the value of the ImageHandle function parameter, a stack address, a random static value (qword_35760) and the system clock (as returned by rdtsc instruction):
__int64 __fastcall efi_stack_cookie(__int64 ImageHandle)
{
__int64 StackAddress; // [rsp-8h] [rbp-8h] BYREF
return (qword_35760 ^ __ROL8__(__ROL8__(__ROL8__(ImageHandle, 16) ^ &StackAddress, 16) ^ __rdtsc(), 16)) << 8;
}
This implementation can be found in the iPXE module included in some HPE firmware images for x86 and ARM servers.
In this section we provide the first-ever overview on the usage of mitigations in the UEFI firmware ecosystem. To do so, we leverage a comprehensive dataset of 5,477 recently-released firmware images. The dataset includes firmware released by all major device vendors (Acer, Asus, Dell, Fujitsu, Gigabyte, HP, HPE, Intel, Lenovo, MSI, Samsung and Supermicro) and by all IBVs (AMI, Insyde, Phoenix) providing an accurate overview of the current state of the UEFI ecosystem. When unpacked, these firmware images contain over 2.2 million unique UEFI modules.
This analysis was done automatically by our Binarly Transparency Platform. The platform detects each mitigation by matching some specific artifacts that are present in the binary image under analysis, ensuring high-confidence results with extremely low false positives.
Despite being one of the lowest-overhead and easiest-to-implement mitigations, stack canaries are adopted at an alarmingly low rate in the UEFI ecosystem.
Out of more than 2.3 million analyzed modules, only 2,674 use stack canaries, which corresponds to an adoption rate of just 0.12%. At the firmware image level, canaries were detected in only 52 out of 5,477 images, resulting in a 0.94% adoption rate. When excluding the iPXE bootloader, which implements stack canaries but is only used in specific network-boot environments, this figure drops further to 0.36%.
In addition, except for the iPXE-related modules, all the firmware with stack canaries is intended for ARM-based devices. This means that not a single x86 firmware ships with this basic mitigation implemented.
The new reference implementation for dynamic stack canaries on x86 appears very promising. However, we anticipate that widespread adoption within the x86 firmware ecosystem may still take several months, if not years.
The analysis implemented in the Binarly Transparency Platform detects Stack Guard for DXE on the x86 architecture. As we previously reported, this mitigation is disabled by default in the reference implementation.
The effects of this decision can be clearly seen in our dataset: only 332 images have stack guard enabled, meaning that more than 94% of firmware ships with this protection disabled.
To detect the presence of CFI technology in UEFI firmware, the platform disassembled each UEFI module and searched for the X86 Indirect Branch Tracking (IBT) instructions (endbr32 and endbr64) and for ARM Branch Target Identification (BTI) instruction (bti).
The adoption of this mitigation remains limited: only 1140 modules contain X86’s IBT instructions, and just 336 are instrumented with ARM’s BTI protection. The protected modules are often of limited relevance from a security perspective, as they all appear to be device drivers (such as the SecureBIOCamera_Sonix module and the AX88179 controller found in Lenovo and Acer firmware) which are unlikely to be targeted by attackers.
Moreover, all of these modules operate in the DXE phase, whereas the EDK2’s integration of Control-flow Enforcement Technology (CET) currently applies only to SMM code, not DXE. For these reasons, we speculate that these instances are not deliberate attempts to apply CFI protections to UEFI firmware, but rather resulting from different build environments used by third-party entities within the firmware supply chain.
No eXecute is another mitigation that is very popular in the software ecosystem and that has been integrated on different platforms and systems. However, in the UEFI ecosystem it remains quite often disabled and only 9.38% of the analyzed firmware set a correct memory protection policy with the PcdDxeNxMemoryProtectionPolicy option.
The reference implementation properly sets this option only for ARM firmware. This choice had positive effects in terms of adaption: all ARM firmware in our dataset properly set this mitigation.
On the other hand, virtually every firmware in our dataset sets PcdImageProtectionPolicy to 2, meaning that all images loaded from a firmware volume will have their code and data section properly marked as read-only and non-executable, respectively. In addition, some vendors such as Dell, set this option to 3, so images loaded from unknown devices (such as bootloaders) are also protected in a similar way.
However, we discovered an important catch related to this mitigation that is only briefly noted in a configuration file: for this mitigation to work, the section alignment specified in the PE header must match the EFI page size (0x1000). In other words, if the alignment doesn’t match the page size, the mitigation is not applied.
We found that 68% of the analyzed DXE modules don’t respect this requirement. As a result, all these modules will have writable code sections and executable data sections. A possible explanation for this mismatch is that most tool definitions in the reference implementation lack the necessary directives to configure the compiler to set the section alignment to the required value.
Up-to-date microcode ensures that a system is not vulnerable to known transient execution attacks. Microcode updates are released by CPU vendors, such as Intel and AMD, and shared with firmware vendors so they can be included in firmware updates.
Our analysis shows that 88% of firmware images ship with an outdated version of the microcode and 71% of images ship with a microcode affected by a vulnerability.
Outdated microcode opens the doors to many common transient execution vulnerabilities, like the Gather Data Sampling attack (GDS) (also known as “Downfall”) and MMIO Device Register Partial Write (tracked by INTEL-SA-00615). It also leaves systems susceptible to newer threats, such as the recent Entrysign (CVE-2024-56161) which exploits flaws in the AMD microcode signature verification.
While microcode can be updated by other means, such as from the operating system, firmware updates should still ship with the latest available microcode to ensure that systems are always protected against transient execution attacks regardless of OS updates.
The mitigation against Return Stack Buffer underflows was added to the reference implementation in August 2018 and has been enabled by default since. RSB stuffing has a negligible impact on the return from SMM, done with the rsm instruction. Our analysis focuses on X86 firmware, and in particular on the PiSmmCpuDxeSmm module which contains both the entrypoint and the exit point for SMM.
Despite the negligible overhead of RSB stuffing, we found that 13% of these firmware images completely lack this mitigation, leaving every rsm instruction unguarded. The 37% of the firmware has only partial protection, with at least one RSM lacking RSB stuffing protection.
As we extensively documented in the past, several public data leaks affected the UEFI industry. These leaks, combined with poor key management, resulted in multiple Boot Guard private keys leaking to the public. Our analysis focuses specifically on this problem by checking whether the RSA keys used in the Key Manifest (KM) or in the Boot Policy Manifest (BPM) were leaked or not.
In total, we found 338 firmware using a leaked KM key and 342 firmware using a leaked BPM key. Overall, there are 345 firmware (6.3%) using at least one leaked key. This opens the door to attackers for firmware implantations, as by using these leaked keys they can modify, sign and flash a malicious firmware image.
We also checked for mismatches between the KM and BPM that occur when the RSA key hash contained in the KM signed area doesn’t verify the BPM public key. This represents a problem because it signals that Boot Guard is unverified and might be not enforced during boot.
In total, we found 189 firmware where this mismatch happens.
The Outdated Forbidden Signature Database ensures that UEFI firmware blocks the execution of untrusted components that can be abused to bypass Secure Boot and other firmware protection mechanisms. Microsoft tracks these untrusted components and offers a revocation list, which currently includes 430 signatures for X86-64.
We found that only ARM-based firmware consistently ships with the latest dbx, whereas among x86 firmware, Framework is the only vendor that includes the latest revocation list. The remaining firmware ships with an outdated dbx, which in most cases is missing hundreds of signatures.
This leaves these devices open to attacks against Secure Boot, including our recently disclosed CVE-2025-3052. Similar to microcode updates, the dbx can also be updated from the OS. However, including an up-to-date dbx database directly into the firmware ensures that all systems are resilient against firmware level threats.
In this section we present some examples of proof-of-concept developed against UEFI firmware that clearly illustrate how the absence of mitigations can significantly simplify exploitation.
The first and most recent example is related to Bootkitty, a proof-of-concept bootkit for Linux. Despite its nature, Bootkitty is fully-functional in its UEFI capabilities, and exploits one of the vulnerabilities disclosed as part of LogoFAIL (BRLY-LOGOFAIL-2023-002). We covered the entire infection chain of Bootkitty in a previous blogpost.
The high-level picture is that Bookitty stores a malformed logo on the ESP which gets parsed by a vulnerable DXE driver. The vulnerability results in an arbitrary write, which is used to patch the driver’s code in memory with a trampoline. When executed, this trampoline redirects the execution to a shellcode stored on the heap.
Although an arbitrary write often leads to code execution, the absence of mitigations significantly simplified exploitation.
The second example is related to the PoC developed during our LogoFAIL research. The underlying bug exploited in this PoC is an integer overflow that results in a fully controllable heap overflow, allowing the attacker to control both the size and content. This overflow leads to code execution by overwriting a function pointer stored on the heap with an address under the attacker's control.
The firmware used to develop the PoC didn’t have any mitigation enabled:
The third example of known PoCs is related to System Management Mode, which is also affected by the absence of mitigations. As we discussed at Offensive Con and Black Hat, the lack of mitigations in SMM allows mounting ROP and JOP attacks.
Finally, the lack of stack canaries allowed us to exploit stack-based buffer overflows and achieve code execution multiple times in the past. For example, when developing a working PoC for BRLY-2021-005 stack canaries were completely missing. On this device the stack was not executable, but we leveraged an improperly set NX policy to store the shellcode in the NVRAM area and redirect the execution flow there.
In the case of BRLY-2022-033, canaries were also missing in the vulnerable function, but this time NX was correctly initialized, so we had to resort to ROP to gain code execution. These last two examples clearly show how mitigations often need to be used together, compounding their protection: relying on only one mitigation may not increase the difficulty of carrying out a successful exploit.
In this report, we took a deep dive into the mitigations implemented in the EDK2 reference implementation, exploring how they work, their strengths, and their weaknesses, as well as their adoption within the UEFI firmware ecosystem.
The results from our analysis presents an ecosystem that is very fragmented in the way mitigations are adopted, where some devices implement essential mitigations, while others are left completely without defenses. This fragmentation can also be seen in the firmware images themselves: mitigations are applied only to a few modules, but not based on their security relevance of modules but rather on which entity compiled them. These are concrete examples of the UEFI supply chain complexity, where firmware image is composition of multiple sources without a single, authoritative component that provides and enforces mitigations.
A significant trend highlighted by this report is that vendors rarely tweak the default values provided in the reference implementation. This is evident in ARM firmware, the only platform where we consistently found stack canaries and properly configured NX, both of which are enabled by default in the reference implementation. In retrospect, ensuring robust mitigations’ implementations with secure default settings should have been a priority of the reference implementation.
We applaud the recent addition of dynamic stack canaries for X86 targets, and we view this as a step in the right direction. However, based on our experience, it may take months, if not years, for this new implementation to permeate into the industry and to be adopted in UEFI firmware. For this reason, we urge the UEFI ecosystem to prioritize the adoption of this new implementation.
Finally, we recognize that mitigations are often difficult to adopt as they often require trade offs between usability and security. However, in the UEFI firmware ecosystem, even basic mitigations with minimal side-effects are currently missing.
We hope this research will motivate the industry to develop and adopt better mitigations for the UEFI firmware ecosystem, ensuring that billions of devices can be better protected against firmware-level threats.