Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
lacuna-rs — Rust crate pour le spoofing de pile d'appels ghost-frame, les appels système indirects à l'exécution, et l'injection APC sur Windows x64. Fournit la résolution SSN, l'émission de stubs JIT, et le chiffrement de paramètres basé sur VEH pour l'évasion EDR. | Kitploit
Outils/GitHubGitHub/karkas66/lacuna-rs
ShellcodePost-ExploitationRed TeamingDéveloppement de Charges UtilesExploitation de Binaires
GitHubkarkas66/lacuna-rs

lacuna-rs

Rust crate pour le spoofing de pile d'appels ghost-frame, les appels système indirects à l'exécution, et l'injection APC sur Windows x64. Fournit la résolution SSN, l'émission de stubs JIT, et le chiffrement de paramètres basé sur VEH pour l'évasion EDR.

Voir le dépôt
181il y a 1 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

lacuna-rs

Usurpation de pile d'appels par ghost-frame + appels système indirects à l'exécution pour Windows x64 — porté en Rust.

Porté depuis LACUNA Chain (lacuna_chain.c) par Mohamed Alzhrani (0xmaz).

lacuna-rs est une crate Rust réutilisable qui fournit les mêmes primitives que la TTP C originale, structurée pour être intégrée dans n'importe quel projet Rust — comme wsyscall-rs ou syscalls-rs, mais avec résolution SSN à l'exécution, ciblage syscall;ret par fonction et usurpation de pile par ghost-frame.


Critique : Exigence de pointeur de trame

La fonctionnalité stack-spoof échoue silencieusement si la crate consommatrice n'est pas compilée avec des pointeurs de trame.

Les primitives d'écrasement de pile dans chain.rs localisent l'emplacement de l'adresse de retour de l'appelant via un assembleur inline mov rbp, {x}. Cela nécessite que la chaîne RBP soit intacte. Rust (et la plupart des compilateurs en mode release) omettent les pointeurs de trame par défaut.

build.rs définit force-frame-pointers=yes pour la génération de code de cette crate, mais Cargo ne peut pas propager les flags du compilateur aux crates en aval. Vous devez ajouter ceci à votre propre projet :

root@kitploit:~
# .cargo/config.toml  (dans VOTRE crate, pas dans lacuna-rs)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]

Sans cela, stomp_plant() lira des valeurs erronées depuis RBP et soit ne fera rien (meilleur cas), soit corrompra la pile (pire cas). La crate n'a aucun moyen de détecter à l'exécution si les pointeurs de trame sont activés — cela ne fonctionnera tout simplement pas.

Si vous n'avez besoin que des primitives d'analyse, de résolution SSN ou d'injection (sans usurpation de pile), vous pouvez omettre la fonctionnalité stack-spoof et cette exigence ne s'applique pas.


Ce qu'il fait


Drapeaux de fonctionnalités

root@kitploit:~
[dependencies]
lacuna-rs = { version = "0.1", features = ["inject", "stack-spoof", "veh"] }

Lorsqu'aucune fonctionnalité n'est activée, seules les couches d'analyse/PE/NT sont disponibles.


Démarrage rapide

Analyser les régions fantômes

root@kitploit:~
cargo run --example scan

Construire + vérifier la chaîne ghost-frame

root@kitploit:~
cargo run --example verify --features stack-spoof

Injecter du shellcode via section + APC

Preuve d'injection Exemple d'injection avec un implant réel - C2 hors ligne mais le Shellcode s'est exécuté

root@kitploit:~
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin>

Ajoutez --verbose pour activer les diagnostics VEH (dumps de pile, impressions de registres) :

root@kitploit:~
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose

Algorithme de notation des threads

Au lieu de mettre en file d'attente des APC à chaque thread du processus cible (ce qui peut planter le processus lorsque trop de threads sont alertés simultanément), inject_sapc utilise un algorithme de notation pour sélectionner les meilleurs MAX_APC_THREADS (5) candidats :

  • Temps de cycle — NtQueryInformationThread(ThreadCycleTime) — indicateur principal d'activité
  • Temps CPU — NtQueryInformationThread(ThreadTimes) — temps noyau + utilisateur
  • Compteur de suspension — NtQueryInformationThread(ThreadSuspendCount) — les threads non suspendus reçoivent un bonus de +300 ; les threads suspendus reçoivent -150 par suspension
  • Priorité — NtQueryInformationThread(ThreadBasicInformation) — les threads avec une priorité de 8 à 10 reçoivent un bonus de +150 ; les priorités hors plage reçoivent une pénalité de -100

Les threads totalement inactifs (zéro cycles et zéro temps CPU) sont complètement ignorés. Les candidats restants sont triés par score (le plus élevé en premier, ex-æquo sur les cycles) et tronqués aux 5 premiers.


Utilisation en tant que bibliothèque

Basique : analyse + résolution SSN

root@kitploit:~
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);

Émettre un stub d'appel système indirect pour n'importe quelle API NT

root@kitploit:~
use lacuna::{nt, stub, win::{get_module, HMODULE}};

let ntdll: HMODULE = get_module(b"ntdll.dll\0");

// Resolve SSN + the function's own syscall;ret address
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtAllocateVirtualMemory\0");
assert!(ssn != nt::SSN_INVALID && syscall_ret != 0);

// JIT-emit a stub: mov r10,rcx; mov eax,SSN; jmp [syscall;ret]
// (or jmp [ghost_gadget] -> JMP [RBX] -> syscall;ret if build_chain was called)
let stub = stub::make_stub(ssn, syscall_ret).expect("stub alloc failed");

// Cast to the matching function pointer type and call
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()) };

Construire la chaîne ghost-frame + enregistrer VEH

root@kitploit:~
// Scan ntdll/kernelbase/wow64/win32u for ghost regions and construct
// the six-layer fake call stack. Sets G_GHOST_GADGET so stubs route
// through JMP [RBX] in a signed DLL.
lacuna::chain::build_chain();

// Register VEH handlers (param encryption + chain guard)
let _veh = lacuna::veh::VehGuard::register().expect("VEH registration failed");

// Optional: enable verbose diagnostics at runtime
lacuna::veh::set_verbose(true);

Envelopper un appel système sensible avec usurpation de pile + chiffrement des paramètres

root@kitploit:~
use lacuna::win::HANDLE;
use core::ptr;

let mut h_proc: HANDLE = ptr::null_mut();
let key: u64 = 0xCAFE_1337;

// Plant ghost frames (replaces return addresses with signed-DLL ghosts)
lacuna::chain::stomp_plant();

// Arm DR0 on the syscall;ret -- VEH will XOR-decrypt params at the boundary
lacuna::veh::pcrypt_arm(key, syscall_ret, true);

// Call through the indirect stub -- RIP lands inside ntdll at kernel entry
let _status = unsafe { open_proc(/* XOR-encrypted params */) };

// Always disarm before any non-protected call
lacuna::veh::pcrypt_disarm();
lacuna::chain::stomp_restore();

OPSEC : Que cacher derrière les ghost-frames

Tous les appels API n'ont pas besoin du traitement complet LACUNA. Le principe clé est : cacher les appels qu'un EDR corrélerait comme activité d'injection ou de post-exploitation.

Doivent être derrière des appels système indirects + ghost-frames + chiffrement des paramètres

Ce sont les appels système NT « bijoux de la couronne » que les EDR accrochent et corrèlent :

Pour chacun de ces appels :

  1. Résoudre avec nt::resolve(ntdll, b"NtXxx\0")
  2. Émettre un stub avec stub::make_stub(ssn, syscall_ret)
  3. Chiffrer les paramètres sensibles avec veh::pcrypt_arm(key, syscall_ret, true)
  4. Envelopper l'appel entre chain::stomp_plant() et chain::stomp_restore()

Peuvent être directs (pas besoin de ghost-frames)

Principes clés

  1. Résoudre tout appel système NT avec nt::resolve(ntdll, b"NtXxx\0") — retourne (SSN, syscall_ret_VA).
  2. Émettre un stub JIT avec stub::make_stub(ssn, syscall_ret) — retourne un pointeur de fonction appelable.
  3. Caster le stub vers le type extern "system" fn(...) approprié via core::mem::transmute.
  4. (Pour les appels sensibles uniquement) Construire la chaîne ghost-frame avec chain::build_chain() et appeler chain::stomp_plant() / chain::stomp_restore() autour de l'appel.
  5. (Pour les appels sensibles uniquement) Armer le chiffrement des paramètres avec veh::pcrypt_arm(key, syscall_ret, true) avant l'appel, et veh::pcrypt_disarm() après.
  6. Toujours désarmer le VEH avant d'effectuer des appels non protégés — un accès DR0 intempestif sur un appel système non armé plantera.
  7. doivent être enveloppées dans — la crate l'impose, et les diagnostics VEH sont verrouillés derrière qui est désactivé par défaut.

Le stub gère automatiquement la séquence mov r10, rcx / mov eax, SSN / jmp [syscall;ret]. Si un gadget fantôme a été enregistré, le stub achemine via JMP [RBX] pour une redirection d'exécution à double usage + trame de pont sans artefact.


Vérification de la couverture de l'article

Les 9 contributions clés de l'article LACUNA Chain sont représentées dans le code :

Couverture de la surface de détection


Pourquoi ne pas simplement utiliser syscalls-rs ?

syscalls-rs et wsyscall-rs génèrent une table SSN au moment de la compilation à partir du ntdll d'une build Windows spécifique. Si la machine cible exécute une build différente, les SSN sont incorrects et les appels système échouent (ou déclenchent des heuristiques EDR).

lacuna-rs résout les SSN à l'exécution en lisant directement les stubs de ntdll, et cible la propre instruction syscall;ret de la fonction pour que RIP soit à l'intérieur de ntdll à l'entrée du noyau — contournant les heuristiques EDR « inadéquation SSN » et « appel système indirect depuis une mémoire non adossée ».

De plus, lacuna-rs fournit la chaîne d'usurpation de pile par ghost-frame que syscalls-rs ne propose pas.


Configuration requise pour la compilation

  • Cible : x86_64-pc-windows-msvc (ou x86_64-pc-windows-gnu)
  • Édition Rust : 2021
  • Dépendances : litcrypt2 (obfuscation de chaînes à la compilation)

Configuration des pointeurs de trame (uniquement pour stack-spoof)

La fonctionnalité stack-spoof nécessite des pointeurs de trame. build.rs définit force-frame-pointers=yes automatiquement pour cette crate, mais les crates consommatrices doivent également le définir dans leur .cargo/config.toml :

root@kitploit:~
# .cargo/config.toml (dans la crate consommatrice)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]

Obfuscation de chaînes avec litcrypt2

Tous les littéraux de chaîne dans la crate et les exemples sont enveloppés dans des macros lc!(), qui les chiffrent à la compilation et les déchiffrent à l'exécution. La clé de chiffrement est lue depuis la variable d'environnement LITCRYPT_ENCRYPT_KEY ; si elle n'est pas définie, litcrypt2 génère automatiquement une clé aléatoire.

Pour fixer une clé pour des builds reproductibles :

root@kitploit:~
set LITCRYPT_ENCRYPT_KEY=votre-clé-secrète
cargo build

Architecture

root@kitploit:~
lacuna-rs/
├── Cargo.toml
├── build.rs              # Définit force-frame-pointers pour stack-spoof
├── src/
│   ├── lib.rs            # Racine de la crate + réexportations + scan_all() + configuration litcrypt2
│   ├── win.rs            # Liaisons FFI Win32/NT (compatible no_std)
│   ├── pe.rs             # Analyse de section PE + export
│   ├── scan.rs           # Analyse de région fantôme .pdata + gadgets
│   ├── nt.rs             # Résolution SSN + ciblage syscall;ret
│   ├── stub.rs           # Émission de stub d'appel système indirect JIT
│   ├── veh.rs            # Chiffrement des paramètres VEH + point d'arrêt matériel
│   ├── chain.rs          # Chaîne ghost-frame LACUNA + écrasement
│   └── inject.rs         # Injection APC basée sur les sections
├── examples/
│   ├── scan.rs           # lacuna.exe scan
│   ├── verify.rs         # lacuna.exe verify
│   └── inject.rs         # lacuna.exe inject <pid> <sc.bin>
└── .cargo/
    └── config.toml       # force-frame-pointers + cible windows-msvc

Crédits

  • Recherche originale et implémentation C : Mohamed Alzhrani (0xmaz) — LACUNA Chain
  • Technique : Ghost Frames: Forging Plausible Call Stacks from .pdata Lacunae
  • Logique de recherche du meilleur thread APC : Adapté depuis FrankensteinAPCInjection par S12cybersecurity
  • Obfuscation de chaînes : litcrypt2 v0.1.3

Divulgation complète

Ce projet a été activement assisté par un chatbot IA. J'ai eu la chance de tester un modèle Open Source plus récent et très performant (GLM-5.2 de Z.ai (Zhipu AI)) et j'ai voulu lui confier une tâche difficile. Leçons apprises : cela a bien fonctionné lorsque l'humain savait à quoi devait ressembler le résultat. Sinon... que du mauvais.

Licence

MIT

Télécharger l’outil
PrimitiveFonction CModule Rust
Analyse de section PE + exportpe_section(), pe_export()pe
Analyse de région fantôme .pdatascan_ghosts(), best_ghost()scan
Découverte de gadget fantôme (jmp [rbx])scan_ghost_gadgets()scan
Chercheur d'espace NOP de win32uwin32u_nop_gap()scan
Chercheur d'ancrage BYOUD-MFfind_mf_target()scan
Résolution SSN (Hell's Gate / Halo's Gate)resolve_ssn()nt
Localisateur syscall;ret par fonctionfind_func_syscall()nt
Émission de stub d'appel système indirect JITalloc_stub()stub
Redirection de stub vers gadget fantôme(dans alloc_stub())stub
Chiffrement des paramètres VEH + point d'arrêt matérielparam_encrypt_veh(), pcrypt_arm()veh
VEH de protection de chaînechain_veh()veh
Construction de chaîne LACUNAbuild_chain()chain
Écrasement de pile (BYOUD-RT)stomp_plant(), stomp_restore()chain
Parcours de chaîne (vérification)lacuna_walk_chain()chain
Injection APC basée sur les sectionsdo_inject_sapc()inject
FonctionnalitéDescriptionNécessite des pointeurs de trame ?
syscalls (défaut)Résolution SSN + émission de stub JITNon
inject (défaut)Injection APC basée sur les sections (inject::inject_sapc)Non
vehChiffrement des paramètres VEH + point d'arrêt matérielNon
stack-spoofChaîne ghost-frame LACUNA + écrasement de pileOui
no-stdMode no_std (expérimental)Non
AppelPourquoi c'est sensible
NtOpenProcessOuvre un handle vers un autre processus — première étape de l'injection
NtCreateSection + NtMapViewOfSection (distant)Signature d'injection basée sur les sections
NtWriteVirtualMemoryÉcriture inter-processus — indicateur classique d'injection
NtCreateThreadExCréation de thread distant — primitive d'injection au plus haut signal
NtQueueApcThreadInjection APC — signal élevé
NtProtectVirtualMemoryChangements de permissions RWX — indicateur de préparation de shellcode
NtAllocateVirtualMemory (distant)Allocation distante — prélude à l'injection
NtSetInformationThreadMasquage de thread (HideFromDebugger) — indicateur de contournement
AppelPourquoi c'est sûr
NtQueryInformationThreadUniquement requête, rarement accroché, pas d'écriture inter-processus
NtDelayExecutionSommeil — bénin, utilisé par toutes les applications
NtCloseFermeture de handle — bénin, extrêmement courant
GetModuleHandleA / GetProcAddressRésolution de module — pas un appel système, non accrochable par les hooks utilisateur EDR
CreateToolhelp32Snapshot / Thread32First / Thread32NextÉnumération de threads — kernel32, pas un appel système ntdll
OpenThread / CloseHandleOpérations standard de handle — kernel32
GetThreadContext / SetThreadContextNécessaire pour DR0 — kernel32, uniquement dans le processus lui-même
Toutes les chaînes de sortie
lc!()
set_verbose(true)
#Concept de l'articleEmplacement dans le codeDescription
1BYOUD-Gap (aucune modification .pdata)chain.rs — construction de la chaîne ghost-frameExploite les écarts entre les entrées RUNTIME_FUNCTION ; le dérouleur les traite comme des trames feuilles (RSP += 8)
2Attaque par fenêtre APC ETW-Tiinject.rs — vidange alertable NtDelayExecutionContrôle le moment où l'instantané de pile APC ETW-Ti se déclenche en manipulant l'état alertable du thread
3Chiffrement des paramètres en contexte BYOUDveh.rs — pcrypt_arm(), param_encrypt_veh()Chiffre en XOR les paramètres d'appel système au moment de la mise en place ; les déchiffre dans un VEH par point d'arrêt matériel à l'instruction syscall
4Chaîne d'espace NOP Win32u + Gadget fantômescan.rs — win32u_nop_gap(), scan_ghost_gadgets()1 242 espaces NOP dans win32u.dll fournissent des trames feuilles autorisées ; gadget fantôme JMP [RBX] à ntdll+0xFC47B
5Proximité sémantique ghost kernelbasechain.rs — couche L2_kbase près de VirtualProtectFantôme de 238 octets se terminant au point d'entrée de VirtualProtect — impossible à distinguer d'un site de retour VP réel
6BYOUD-MF (Téléportation RSP de trame machine)chain.rs — struct MachFrame, scan.rs — find_mf_target()Exploite UWOP_PUSH_MACHFRAME (opcode 10) dans les dispatchers KiUser* pour une téléportation RSP arbitraire en une seule trame
7BYOUD-RT (Calcul RSP à l'exécution)chain.rs — teb_stack_base(), teb_stack_limit()Lit TEB.StackBase (GS:[0x08]) au moment de l'appel pour calculer la distance exacte de la trame — pas besoin de pré-étalonnage
8Proximité ghost wow64.dllchain.rs — couche L1_wow64, scan.rs — cible Wow64PrepareForExceptionFantôme de 91 octets se terminant à l'entrée de Wow64PrepareForException — quatrième couche sémantique de la chaîne
9Chaîne LACUNA à six coucheschain.rs — struct LacunaStack (L1-L5 + MachFrame)Chaîne complète : KiUserExceptionDispatcher -> wow64 -> kernelbase -> ntdll -> win32u -> RtlUserThreadStart
Couche de détectionStatutImplémentation
Vérification du module d'origineContournéToutes les trames dans ntdll / kernelbase / wow64 / win32u
Exactitude du parcours de déroulementContournéToutes les trames LACUNA sont feuilles -> RSP+8 valide
Analyse forensique .pdataContournéAucune modification ; les écarts préexistent
Pile d'ombre CETContournéChaîne purement feuille ; la pile d'ombre n'est pas consultée
Analyse sémantique des tramesContournéAdjacence exception WoW64 + VirtualProtect
Exemption de règle Win32uContournéCouche 4 explicitement exclue par toutes les règles
STACKWALK ETW-TiContournéL'attaque par fenêtre APC contrôle le timing de l'instantané
Inspection des paramètresContournéDéchiffrement par VEH de point d'arrêt matériel
Callbacks noyauPARTIELLes opérations de handle déclenchent encore ObRegisterCallbacks