xec412/NocturneLdr
GitHub: xec412/NocturneLdr
NocturneLdr 是一个兼容 Intel CET 的 Windows x64 shellcode 加载器,通过运行时函数表操纵和代码空洞注入生成完全有支撑的调用栈,使检测工具和调试器无法区分其与合法线程调用链。
Stars: 4 | Forks: 0
[](https://microsoft.com)
[](https://isocpp.org)
[](https://learn.microsoft.com/en-us/cpp/assembler/masm/masm-for-x64-ml64-exe)
[](LICENSE)
## 概述
Nocturne 是一个面向研究的 Windows x64 shellcode loader,其构建围绕着一个单一目标:生成**干净的、完全有支撑的调用栈**,这些调用栈与合法的 Windows 线程无法区分——即使在 WinDbg 的手动取证检查下也是如此。
现代 EDR 解决方案和手动分析人员依赖调用栈完整性作为主要的检测信号。无支撑的返回地址、未对齐的帧以及动态函数表工件都是恶意活动的强烈指标。传统的堆栈伪造技术解决了其中一些问题,但它们在 **Intel CET (Control-flow Enforcement Technology)** 影子堆栈验证下会失效,在该技术中,硬件维护着一个单独的、只读的返回地址副本,这些副本必须与软件堆栈相匹配。
Nocturne 通过采用一种根本不同的方法来解决这个问题:它不伪造假帧,而是**将代码注入到合法模块的 `.text` 节中**,并使用来自该模块的**真实的捐赠者 unwind 元数据**注册一个 `RUNTIME_FUNCTION` 条目。然后,Windows unwinder 使用真实的 unwind 信息遍历堆栈,生成指向已签名、有支撑的 DLL (`windows.storage.dll`) 的帧。由于代码物理上驻留在模块的地址范围内,并且 unwind 链在结构上是有效的,基于软件的堆栈遍历和 CET 硬件验证都能看到合法的调用链。
该 loader 完全**不依赖 CRT** (`/NODEFAULTLIB`) 构建,具有自定义入口点、自定义 `memset`/`memcpy` 内联函数,以及 `HeapAlloc`/`HeapFree` 运算符重写——最终二进制文件中没有 C 运行时依赖。所有 Win32 和 NT API 解析都在运行时通过**编译时 DJB2 哈希** (`constexpr`) 进行:哈希值在编译期间计算并作为立即数嵌入,而实际的函数指针是在运行时通过遍历 PEB loader 数据结构来解析的。这意味着二进制文件中不存在任何 API 名称字符串,并且 IAT 仅包含无害的伪装导入。
## 技术及其选择原因
### 🔧 API 哈希解析 (DJB2)
所有 Win32/NT API 都是在运行时通过遍历 PEB loader 数据结构并将导出名称与预计算的 DJB2 哈希进行匹配来解析的。这些哈希在**编译时**使用 `constexpr` 求值生成——编译器计算每个哈希并将其作为立即数嵌入到二进制文件中。在运行时,解析器遍历目标模块的导出表,并将每个导出名称的哈希与嵌入的常量进行比较。这消除了原本会向静态分析工具暴露 loader 真实能力的 IAT 条目,并确保二进制文件中不存在任何 API 名称字符串。
### 🎭 IAT 伪装
Import Address Table 中仅填充了放置在无法到达的代码分支内的**无害的 USER32.dll 导入** (`MessageBoxA`、`RegisterClassW`、`IsWindowVisible` 等)。静态分析工具看到的是一个无害的 GUI 应用程序,而不是 loader。
Clean IAT — only benign USER32.dll imports and heap management functions
### 🛡️ 兼容 CET 的堆栈伪造 (RTFI + JIT)
这是 Nocturne 的核心技术。堆栈伪造 pipeline:
1. **牺牲型 DLL 加载** — `windows.storage.dll` 作为捐赠者模块被加载
2. **Code Cave 注入** — Payload + ShadowGate stub 被注入到 `.text` 节的空闲空间中
3. **动态函数表注册** — `RtlAddFunctionTable` 注册一个指向**捐赠者 unwind 信息**的 `RUNTIME_FUNCTION` 条目,使注入的代码看起来像是模块内一个合法的函数
4. **反转函数表坍缩** — 捐赠者模块的内部 `RtlpInvertedFunctionTable` 条目被坍缩,以便动态表在 unwinding 期间优先级更高
5. **缓存失效** — 函数表缓存和函数条目缓存都被刷新,以强制 unwinder 通过被操纵的查找路径
6. **Type=0 WinDbg 绕过** — 动态函数表条目类型从 `RF_CALLBACK` 被修补为 `RF_SORTED`,防止 WinDbg 将其解析为动态
7. **`.pdata` 抑制** — 捐赠者模块的 `.pdata` 节保护被设置为 `PAGE_NOACCESS`,强制所有 unwind 查找通过受控的动态表进行
结果:调用栈中的每一帧都解析为 `windows_storage!
` —— 一个合法的、有支撑的模块。
Process Hacker call stack — all frames resolve to windows.storage.dll
WinDbg stack trace showing legitimate windows_storage frames
### 🔍 WinDbg 绕过验证
动态函数表条目对 WinDbg 的取证命令是不可见的。类型修补和 `.pdata` 抑制确保调试器分析无法区分伪造的帧和真实的帧。
WinDbg bypass — .fnent shows donor unwind info, no dynamic table artifacts
### 🔒 无 CRT 构建
整个项目使用 `/NODEFAULTLIB` 和自定义入口点进行编译。内存管理通过运算符重写使用 `HeapAlloc`/`HeapFree`。`memset` 和 `memcpy` 被实现为自定义内联函数。这消除了会使二进制文件膨胀并增加不必要攻击面的 CRT 依赖。
### 🐚 Payload 执行
Shellcode 在运行时进行 XOR 解密,并通过 **ShadowGate** 汇编 stub 执行,该 stub 在转移控制权之前设置好伪造的堆栈帧。
Reverse shell established with fully spoofed call stack
## 构建
在 Visual Studio 中打开 `Nocturne.sln` 并构建为 **x64 Release** 或 **x64 Debug**。
| 配置 | 描述 |
|---|---|
| **Debug x64** | 启用控制台输出,定义了 `DEBUG` 预处理器 |
| **Release x64** | 静默执行,无调试输出 |
## 项目结构
```
Nocturne/
├── include/
│ ├── Common.h # Global API struct & function declarations
│ ├── IatCamouflage.h # IAT camouflage with benign imports
│ ├── InitializeAPI.h # Runtime API resolution
│ ├── Primitives.h # Hash functions & utility macros
│ ├── Structs.h # NT structures & custom types
│ └── Debug.h # Debug console & print macros
├── src/
│ ├── Main.cpp # Entry point & orchestration
│ ├── StackSpoofing.cpp # Stack spoofing pipeline
│ ├── Unwind.cpp # Unwind info parsing & manipulation
│ ├── Context.cpp # Spoof context tracking & rollback
│ ├── Resolver.cpp # PEB walking & hash-based API resolution
│ ├── StackUtils.cpp # Stack origin & cookie detection
│ ├── Proxy.cpp # Thread pool proxied API calls
│ ├── Intrinsic.cpp # Custom memset/memcpy
│ ├── Debug.cpp # Debug console allocation
│ ├── ShadowGate.asm # Payload execution stub
│ ├── StackSearch.asm # Stack scanning primitives
│ ├── SetRegister.asm # Register manipulation
│ └── ApiStub.asm # Indirect syscall stubs
└── docs/
└── screenshots/
```
## 参考
- [klezVirus](https://github.com/klezVirus) — Nocturne 堆栈伪造所基于的 BYOUD 技术的原作者
- [klezVirus/BYOUD](https://github.com/klezVirus/BYOUD) — Bring Your Own Unwind Data — 运行时函数表操作背后的基础研究
- [BYOUD 博客文章](https://klezvirus.github.io/posts/Byoud/) — 关于 BYOUD 技术的详细技术文章
- [MalDev Academy](https://maldevacademy.com/) — 恶意软件开发技术和方法论
- [Microsoft x64 异常处理](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170) — 关于 x64 unwind 信息、`RUNTIME_FUNCTION` 和异常处理 ABI 的官方文档
- [堆栈伪造 — Black Hat 演讲](https://www.youtube.com/watch?v=tOVcScKuJvU&t=847s) — 关于高级堆栈伪造技术的演讲
- [Hiding in Plain Sight](https://0xdarkvortex.dev/hiding-in-plainsight/) — 线程堆栈伪造和调用栈操作研究
## 免责声明
本项目仅供**授权的安全研究和教育目的**使用。作者不对任何滥用行为负责。在对您不拥有的系统进行测试之前,请务必获得适当的授权。
如果您觉得这个项目有用或有趣,请考虑留下一个 ⭐
您的支持将激励进一步的研究和开发。
标签:C++20, Linux, Windows底层开发, 免杀加载器, 快速连接, 汇编语言