
Windows x64 पर ghost-frame call-stack spoofing, runtime indirect syscalls और APC injection के लिए Rust crate। यह EDR evasion के लिए SSN resolution, JIT stub emission और VEH-आधारित parameter encryption प्रदान करता है।
Windows x64 के लिए घोस्ट-फ्रेम कॉल-स्टैक स्पूफिंग + रनटाइम इनडायरेक्ट सिसकॉल्स — Rust में पोर्ट किया गया।
Mohamed Alzhrani (0xmaz) द्वारा लिखित LACUNA Chain (lacuna_chain.c) से पोर्ट किया गया।
lacuna-rs एक पुन: प्रयोज्य Rust क्रेट है जो मूल C TTP के समान प्रिमिटिव प्रदान करता है, इस प्रकार संरचित कि इसे किसी भी Rust प्रोजेक्ट में डाला जा सके — जैसे wsyscall-rs या syscalls-rs, लेकिन रनटाइम SSN रिज़ॉल्यूशन, प्रति-फ़ंक्शन syscall;ret टार्गेटिंग, और घोस्ट-फ्रेम स्टैक स्पूफिंग के साथ।
यदि उपभोक्ता क्रेट को फ्रेम पॉइंटर्स के साथ कंपाइल नहीं किया गया है तो
stack-spoofफ़ीचर चुपचाप विफल हो जाएगा।
chain.rs में स्टैक-स्टॉम्पिंग प्रिमिटिव mov rbp, {x} इनलाइन असेंबली के माध्यम से कॉलर के रिटर्न-एड्रेस स्लॉट का पता लगाते हैं। इसके लिए RBP चेन का बरकरार होना आवश्यक है। Rust (और अधिकांश रिलीज़-मोड कंपाइलर) डिफ़ॉल्ट रूप से फ्रेम पॉइंटर्स को छोड़ देते हैं।
build.rs इस क्रेट के अपने कोडजेन के लिए force-frame-pointers=yes सेट करता है, लेकिन Cargo डाउनस्ट्रीम क्रेट्स तक कंपाइलर फ़्लैग्स प्रसारित नहीं कर सकता। आपको इसे अपने प्रोजेक्ट में जोड़ना होगा:```toml
[build] rustflags = ["-C", "force-frame-pointers=yes"]
इसके बिना, `stomp_plant()` RBP से कचरा पढ़ेगा और या तो no-op करेगा (सबसे अच्छी स्थिति में) या स्टैक को खराब करेगा (सबसे खराब स्थिति में)। क्रेट के पास रनटाइम पर यह पता लगाने का कोई तरीका नहीं है कि फ्रेम पॉइंटर्स सक्षम हैं या नहीं — यह बस काम नहीं करेगा।
यदि आपको केवल स्कैनिंग, 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` |
---
## फीचर फ्लैग```toml
[dependencies]
lacuna-rs = { version = "0.4", 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
### घोस्ट-फ़्रेम श्रृंखला को बनाएँ + सत्यापित करें```sh
cargo run --example verify --features stack-spoof
इंजेक्शन प्रमाण -- पेलोड लक्ष्य प्रक्रिया के अंदर निष्पादित हुआ।```sh
cargo run --example inject --features inject,stack-spoof,veh -- <sc.bin>
`--verbose` जोड़ें ताकि VEH डायग्नोस्टिक आउटपुट (स्टैक डंप, रजिस्टर प्रिंट) सक्षम हो सके:```sh
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose
inject_sapc लक्ष्य के थ्रेड्स को रैंक करता है। सबसे मजबूत रैंकिंग संकेत यह है
कि कोई थ्रेड किसी विंडो का स्वामी है या नहीं:
EnumWindows +
GetWindowThreadProcessId। एक GUI थ्रेड अपने
मैसेज लूप में एक alertable प्रतीक्षा में पार्क रहता है, इसलिए कतारबद्ध APC वहीं पहले चलता है। +1000।NtQueryInformationThread(ThreadCycleTime); चालू बिल्ड पर जब यह क्लास उपलब्ध न हो तो
ThreadTimes पर वापस चला जाता है (देखें
examples/tprobe.rs)। सामान्यीकृत करके जोड़ा जाता है।NtQueryInformationThread(ThreadTimes) — कर्नेल + उपयोगकर्ता समय।NtQueryInformationThread(ThreadSuspendCount)। एक सस्पेंडेड
थ्रेड APC नहीं चला सकता: प्रति सस्पेंड गणना −2000।NtQueryInformationThread(ThreadBasicInformation)। प्राथमिकता
8–10 को +150 मिलता है; सीमा से बाहर की प्राथमिकता को −300 मिलता है।फ़िल्टरिंग। सस्पेंडेड थ्रेड्स और गैर-alertable कर्नेल
प्रतीक्षा में पार्क थ्रेड्स — WrDelayExecution (Sleep/NtDelayExecution) या WrSuspended, जो
NtQuerySystemInformation(SystemProcessInformation) से पढ़े जाते हैं — को सीधे हटा दिया जाता है (वे
कभी APC नहीं ले सकते)। प्रतीक्षा कारण अन्यथा केवल नैदानिक है: इसे
प्रति उम्मीदवार State: / Reason: / Alert: के रूप में लॉग किया जाता है, लेकिन यह
स्कोर को प्रभावित नहीं करता, क्योंकि KWAIT_REASON alertability को एन्कोड नहीं करता (एक
alertable और एक गैर-alertable ऑब्जेक्ट प्रतीक्षा समान मान रिपोर्ट करते हैं — examples/tprobe.rs-शैली की प्रोबिंग से सत्यापित)।
उम्मीदवारों को क्रमबद्ध किया जाता है (उच्चतम स्कोर, टाई-ब्रेक GUI फिर साइकल) और
MAX_APC_CANDIDATES (10) तक सीमित किया जाता है गणना/लॉगिंग के लिए।
गणना ≠ इंजेक्ट। सूचीबद्ध थ्रेड्स की संख्या इंजेक्ट की गई संख्या से अलग है।
MAX_APC_CANDIDATES(10) नियंत्रित करता है कि कितने रैंक और लॉग किए जाएँ;APC_INJECT_THREADS(5) नियंत्रित करता है कि वास्तव में कितने को APC मिलता है।कई को इंजेक्ट क्यों करें, फिर पुनः प्रयास क्यों: उपयोगकर्ता मोड यह नहीं बता सकता कि कौन सा थ्रेड alertable प्रतीक्षा में है, और एक चुना गया थ्रेड अक्सर उसमें कभी प्रवेश ही नहीं करता, इसलिए APC कभी फायर ही नहीं होगा। इसलिए
inject_sapcAPC को कई थ्रेड्स पर कतारबद्ध करता है और फिर एक छोटे लूप (~80 ms) में पुनः कतारबद्ध करता है जब तक कि साझा गेट शब्द 0 से 1 में न बदल जाए — यानी जब तक कोई लक्ष्य थ्रेड वास्तव में पेलोड न चला दे। यह लॉग करता है कि ऐसा हुआ या नहीं:payload claimed the gate(चला) बनामgate NOT claimed।एक बार क्यों चलाएँ: वही शेलकोड/कॉन्टेक्स्ट प्रत्येक थ्रेड पर कतारबद्ध किया जाता है, इसलिए सुरक्षा के बिना एक गैर-रीएंट्रेंट पेलोड समवर्ती रूप से चलेगा और होस्ट को क्रैश कर देगा (मापा गया: 3 समकालिक निष्पादन)। रीएंट्रेंसी गेट (
USE_REENTRANCY_GATE, डिफ़ॉल्ट रूप से चालू) पेलोड बॉडी को ठीक एक बार चलाता है चाहे कितने भी थ्रेड्स (या कितने भी पुनः प्रयास राउंड) APC लें (मापा गया: ठीक 1)। गेट को चालू रखें जब तक कि आपका पेलोड स्पष्ट रूप से समवर्ती-सुरक्षित न हो।
use lacuna::{scan, nt, win::get_module};
let ntdll = get_module(b"ntdll.dll\0");
// Scan for ghost regions let mut ghosts = [scan::Ghost::default(); 512]; let n = scan::scan_ghosts(ntdll, &[b"NtAllocateVirtualMemory\0"], &mut ghosts); println!("{} ghost regions in ntdll", n);
// Resolve SSN + syscall;ret for a specific function let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtOpenProcess\0"); println!("NtOpenProcess: ssn={:#x}, syscall;ret={:#x}", ssn, syscall_ret);
### किसी भी NT API के लिए एक अप्रत्यक्ष सिसकॉल स्टब उत्सर्जित करें```rust
use lacuna::{nt, stub, win::{get_module, HMODULE}};