xec412/NocturneLdr

GitHub: xec412/NocturneLdr

NocturneLdr 是一个兼容 Intel CET 的 Windows x64 shellcode 加载器,通过运行时函数表操纵和代码空洞注入生成完全有支撑的调用栈,使检测工具和调试器无法区分其与合法线程调用链。

Stars: 4 | Forks: 0

Nocturne Banner
[![Windows](https://img.shields.io/badge/Platform-Windows%20x64-blue?style=flat-square&logo=windows)](https://microsoft.com) [![C++20](https://img.shields.io/badge/Language-C++20-purple?style=flat-square&logo=cplusplus)](https://isocpp.org) [![MASM](https://img.shields.io/badge/Assembly-MASM%20x64-red?style=flat-square)](https://learn.microsoft.com/en-us/cpp/assembler/masm/masm-for-x64-ml64-exe) [![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](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。
IAT Camouflage — Only benign imports visible
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 — Clean call stack with windows.storage frames
Process Hacker call stack — all frames resolve to windows.storage.dll

WinDbg — Spoofed stack trace
WinDbg stack trace showing legitimate windows_storage frames
### 🔍 WinDbg 绕过验证 动态函数表条目对 WinDbg 的取证命令是不可见的。类型修补和 `.pdata` 抑制确保调试器分析无法区分伪造的帧和真实的帧。
WinDbg Bypass — Dynamic table hidden from debugger
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 — Payload executing with spoofed stack
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底层开发, 免杀加载器, 快速连接, 汇编语言