Zuigetzu/Donut-CustomHost

GitHub: Zuigetzu/Donut-CustomHost

Donut 的安全研究增强分支,通过自定义 CLR 宿主、架构感知 ETW 绕过和零补丁 AMSI 规避,生成具有高度隐蔽性的内存执行 shellcode。

Stars: 1 | Forks: 0

[![Issues](https://img.shields.io/github/issues/thewover/donut)](https://github.com/Zuigetzu/Donut-CustomHost/issues) [![Contributors](https://img.shields.io/github/contributors/thewover/donut)](https://github.com/Zuigetzu/Donut-CustomHost/graphs/contributors) [![Stars](https://img.shields.io/github/stars/thewover/donut)](https://github.com/Zuigetzu/Donut-CustomHost/stargazers) [![Forks](https://img.shields.io/github/forks/thewover/donut)](https://github.com/Zuigetzu/Donut-CustomHost/network/members) [![License](https://img.shields.io/github/license/thewover/donut)](https://github.com/Zuigetzu/Donut-CustomHost/blob/master/LICENSE) [![Chat](https://img.shields.io/badge/chat-%23donut-orange)](https://bloodhoundgang.herokuapp.com/) [![Github All Releases](https://img.shields.io/github/downloads/thewover/donut/total.svg)](http://www.somsubhra.com/github-release-stats/?username=thewover&repository=donut) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?original_referer=https://github.com/Zuigetzu/Donut-CustomHost&text=%23Donut+An+open-source+shellcode+generator+that+supports+in%2Dmemory+execution+of+VBS%2FJS%2FEXE%2FDLL+files:+https://github.com/Zuigetzu/Donut-CustomHost) ![替代文本](https://github.com/Zuigetzu/Donut-CustomHost/blob/master/img/donut_logo_white.jpg?raw=true "Donut Logo")

当前版本:v1.1

目录

  1. 介绍
  2. 工作原理
  3. 构建
  4. 使用方法
  5. 子项目
  6. 使用 Donut 进行开发
  7. 问题与讨论
  8. 免责声明

1. 介绍

Donut 是一段位置无关代码,支持在内存中执行 VBScript、JScript、EXE、DLL 文件以及 .NET 程序集。由 Donut 创建的模块既可以通过 HTTP 服务器进行托管,也可以直接嵌入到加载器本身中。该模块可以选择使用 Chaskey 分组密码和随机生成的 128 位密钥进行加密。文件在内存中加载并执行后,原始引用会被擦除,以防止内存扫描器检测。生成器和加载器支持以下功能:

  • 使用 aPLib、LZNT1、Xpress 和 Xpress Huffman(通过 RtlCompressBuffer)压缩输入文件。
  • 使用熵为 API 哈希生成字符串。
  • 对文件进行 128 位对称加密。
  • 覆盖原生 PE 头。
  • 将原生 PE 存储在 MEM_IMAGE 内存中。
  • 修补反恶意软件扫描接口 (AMSI) 和 Windows 锁定策略 (WLDP)。
  • 修补 Windows 事件跟踪 (ETW)。
  • 为 EXE 文件修补命令行。
  • 修补与退出相关的 API,以避免终止宿主进程。
  • 多种输出格式:C、Ruby、Python、PowerShell、Base64、C#、十六进制和 UUID 字符串。

针对 Linux 和 Windows 平台,提供了可集成到您自己项目中的动态和静态库。此外还有一个 Python 模块,您可以通过阅读 构建并使用 Python 扩展 来了解更多信息。

2. 工作原理

Donut 为每种支持的文件类型提供了独立的加载器。对于 .NET EXE/DLL 程序集,Donut 使用非托管 CLR 托管 API 来加载公共语言运行时。

高级 OPSEC 功能(分支独有)

为了最大化隐蔽性并规避现代 EDR,此特定分支实现了原版分支中没有的几种高级技术:

  • 自定义 CLR 宿主: 通过提供 IHostAssemblyManagerIHostAssemblyStoreIHostMemoryManager 的自定义内存实现,Donut 成功拦截了程序集加载过程(AppDomain.Load_2)。我们的自定义 Assembly Store 不再依赖默认的 Fusion 加载器(它可能会留下跟踪痕迹并将文件释放到磁盘),而是以 IStream 的形式从内存中直接提供匹配完全限定名(FQN)的 payload。
  • 内存跟踪规避: 自定义 Memory Manager 就像一层伪装,向那些试图寻找恶意内存跟踪事件的 EDR 启发式检测返回 S_OK,使其致盲。
  • 架构感知的 ETW 绕过: 我们实现了一种高级的跳转技术。我们没有盲目地用标准的 RET 指令修补 NtTraceEvent(这可能会在 x86/WoW64 环境中破坏堆栈,因为 stdcall 实际上需要 RET 10h),加载器会动态扫描该函数以找到其合法的返回指令。然后,它使用相对 JMP0xE9)修补入口点,直接指向那个原生的返回指令。这确保了在 x64 和 x86 架构上都能实现完美的堆栈对齐和稳定性,在不触发进程崩溃的情况下静默丢弃所有 ETW 事件。
  • 移除 AMSI/WLDP 修补: 传统的 AMSI 修补需要修改原生 DLL 的内存保护(VirtualProtect),这是现代 EDR 高度监控的操作。由于我们的自定义 CLR 宿主从本质上规避了触发这些扫描的遥测,我们刻意移除了默认的 AMSI 补丁,以保持原始、未被篡改的内存足迹。
  • Shellcode 大小优化(内存分配 Bug 修复): 原始生成器在 donut.c 中存在一个内存分配缺陷。它通过将 sizeof(DONUT_INSTANCE) 添加到 c->mod_len 来计算 payload 大小。由于 DONUT_INSTANCE 包含一个 DONUT_MODULE 联合体,模块结构(约 1.3 KB)被分配了两次,导致在最终生成的 .bin 末尾留下了死内存(空字节)。我们通过使用 offsetof(DONUT_INSTANCE, module) 动态计算基础大小修复了这个问题,从而生成空间利用率 100% 且零冗余的 payload。

3. 构建

有两种类型的构建。如果您想调试 Donut,请参考此处的文档。如果不需要,请继续阅读关于发布版本的构建说明。

克隆

在 Windows 命令提示符或 Linux 终端中,克隆此仓库。

 

  git clone http://github.com/thewover/donut.git

下一步取决于您的操作系统以及您决定使用的编译器。目前,Donut 的生成器和加载器模板可以使用 Microsoft Visual Studio 2019 和 MingGW-64 成功编译。要在您自己的 C/C++ 项目中使用这些库,请参考此处提供的示例。

Windows

要生成加载器模板、动态库 donut.dll、静态库 donut.lib 以及生成器 donut.exe,请启动 x64 Microsoft Visual Studio 开发人员命令提示符,切换到您克隆 Donut 仓库的目录,并输入以下内容:


  nmake -f Makefile.msvc

要在 Windows 或 Linux 上使用 MinGW-64 完成相同的操作,请切换到您克隆 Donut 仓库的目录,并输入以下内容:


  make -f Makefile.mingw

Linux

要生成动态库 donut.so、静态库 donut.a 以及生成器 donut,请切换到您克隆 Donut 仓库的目录并直接输入 make。

Python 模块

Donut 可以作为 Python 模块安装和使用。要从源代码安装,需要 Python3 的 pip。首先,在 Linux 终端或 Microsoft Visual Studio 命令提示符下执行以下命令,确保未安装旧版本的 donut-shellcode。


  pip3 uninstall donut-shellcode

确认不再安装旧版本后,执行以下命令。


  pip3 install .

您也可以从 PyPi 仓库获取 Donut,将其作为 Python 模块安装。


  pip3 install donut-shellcode

获取更多信息,请参阅 构建并使用 Python 扩展

Docker

构建 docker 容器。


  docker build -t donut .

运行 donut。


  docker run -it --rm -v "${PWD}:/workdir" donut -h

辅助工具

Donut 包含其他几个可以单独构建的可执行文件。包括 "hash.exe"、"encrypt.exe"、"inject.exe" 和 "inject_local.exe"。前两个用于生成 shellcode。后两个用于协助测试 donut shellcode。"inject.exe" 将通过 PID 或进程名将原始二进制文件注入到进程中。"inject_local.exe" 将原始二进制文件注入到其自身的进程中。

要单独构建这些辅助可执行文件,您可以使用 MSVC makefile。例如,构建 "inject_local.exe" 来测试您的 donut shellcode,您可以运行:


  nmake inject_local -f Makefile.msvc

发布

Donut 的每个发布版本都提供了包含已编译可执行文件的标签。

目前,还有其他两个可用的生成器。

4. 使用方法

下表列出了命令行版本生成器支持的开关。

开关 参数 说明
-a arch 加载器的目标架构:1=x86, 2=amd64, 3=x86+amd64(默认)。
-b level 绕过 AMSI/WLDP 的行为:1=None, 2=失败时中止, 3=失败时继续(默认)。
-k headers 保留 PE 头。1=覆盖 (默认), 2=全部保留
-j decoy 用于模块重载的诱饵模块的可选路径。
-c class 可选的类名。(.NET DLL 必需)也可以包含命名空间:例如 namespace.class
-d name 为 .NET 创建的 AppDomain 名称。如果启用了熵,将随机生成一个。
-e level 熵级别。1=None, 2=生成随机名称, 3=生成随机名称 + 使用对称加密 (默认)
-f format 保存到文件的加载器输出格式。1=二进制 (默认), 2=Base64, 3=C, 4=Ruby, 5=Python, 6=PowerShell, 7=C#, 8=十六进制
-m name DLL 可选的方法或函数。(.NET DLL 必需一个方法)
-n name 用于 HTTP 托管的模块名称。如果启用了熵,将随机生成一个。
-o path 指定 Donut 应将加载器保存到的位置。默认为当前目录下的 "loader.bin"。
-p parameters 为 DLL 方法/函数或 EXE 在引号内提供的可选参数/命令行。
-r version CLR 运行时版本。默认使用 MetaHeader,如果没有则使用 v4.0.30319。
-s server 用于托管 Donut 模块的 HTTP 服务器的 URL。可按以下格式提供凭据:
https://username:password@192.168.0.1/
-t 将非托管/原生 EXE 的入口点作为线程运行,并等待线程结束。
-w 以 UNICODE 格式将命令行传递给非托管 DLL 函数。(默认为 ANSI)
-x option 确定加载器应如何退出。1=退出线程 (默认), 2=退出进程, 3=不退出也不清理并无限期阻塞
-y addr 为加载器创建一个新线程,并在相对于宿主进程可执行文件的偏移地址处继续执行。提供的值为偏移量。此选项支持希望在 donut 完成执行后恢复宿主进程执行的加载器。
-z engine 打包/压缩输入文件。1=None, 2=aPLib, 3=LZNT1, 4=Xpress, 5=Xpress Huffman。目前,后三种仅在 Windows 上受支持。

Payload 要求

为了让 Donut 成功加载您的 payload,它必须满足一些特定要求。

.NET 程序集

  • 入口点方法只能接受字符串作为参数,或不接受任何参数。
  • 入口点方法必须标记为 public 和 static。
  • 包含入口点方法的类必须标记为 public。
  • 该程序集绝对不能是混合程序集(同时包含托管代码和原生代码)。
  • 因此,该程序集绝对不能包含任何非托管导出。

原生 EXE/DLL

  • 不支持使用 Cygwin 构建的二进制文件。

Cygwin 可执行文件使用的初始化例程要求宿主进程从磁盘上运行。如果从内存中执行,宿主进程可能会崩溃。

非托管 DLL

  • 用户指定的入口点方法只能接受字符串作为参数,或不接受任何参数。我们提供了一个 示例

将 Donut 作为库使用(已修复并提供即用型示例)

在原始仓库中,将 Donut 作为库(donut.lib / donut.dll / donut.a)使用通常会导致编译和链接错误。此分支明确修复了这些问题,允许无缝集成到您的自定义 dropper 和 C2 工具中。

我们在该仓库的 lib/ 目录中提供了适用于 C、Go 和 C# 的即用型示例。

1. C/C++ 示例 (lib/main.c)

lib 文件夹中,您将找到 main.c 和一个专用的 Makefile。要使用静态/动态库构建此示例工具,只需导航到 lib/ 并运行 nmake(或 make):

``` // Snippet from lib/main.c #include #include #include #include "donut.h" int main(int argc, char *argv[]) { DONUT_CONFIG config; int err; // ... [Initialization] ... // Configure parameters config.inst_type = DONUT_INSTANCE_EMBED; config.arch = DONUT_ARCH_X84; config.bypass = DONUT_BYPASS_CONTINUE; config.headers = DONUT_HEADERS_OVERWRITE; config.format = DONUT_FORMAT_BINARY; config.compress = DONUT_COMPRESS_NONE; config.entropy = DONUT_ENTROPY_DEFAULT; config.exit_opt = DONUT_OPT_EXIT_THREAD; config.unicode = 0; err = DonutCreate(&config); if (err == DONUT_ERROR_OK) { printf("[+] Shellcode successfully generated at: %s\n", config.output); } else { printf("[-] Error generating shellcode: %s\n", DonutError(err)); } DonutDelete(&config); return 0; } ```

2. Golang 示例 (lib/main.go)

Go 可以通过 CGO 与 Donut 进行原生互操作。lib/ 中的 main.go 文件已完全配置了相应的 CFLAGSLDFLAGS 以链接到修复后的库。

``` // Snippet from lib/main.go package main /* // We tell CGO where to look for the headers (donut.h) #cgo CFLAGS: -I../include // We tell CGO which static library to link #cgo LDFLAGS: -L${SRCDIR} -ldonut #include #include #include "donut.h" */ import "C" import ( "fmt" "os" "unsafe" ) func main() { // ... [Initialization & String conversions] ... config.arch = C.DONUT_ARCH_X84 config.bypass = C.DONUT_BYPASS_CONTINUE config.format = C.DONUT_FORMAT_BINARY // ... [Other Configs] ... err := C.DonutCreate(&config) if err == C.DONUT_ERROR_OK { outName := C.GoString((*C.char)(unsafe.Pointer(&config.output[0]))) fmt.Printf("[+] Shellcode successfully generated at: %s\n", outName) } else { errMsg := C.GoString(C.DonutError(err)) fmt.Printf("[-] Donut Error. Code: %d - %s\n", err, errMsg) } C.DonutDelete(&config) } ```

3. 通过 P/Invoke 使用 C# 示例 (lib/DonutCsharp/)

在编译通过 P/Invoke 与 donut.dll 交互的 C# 工具时,常见的一个问题是必须将非托管 DLL 与您的 .exe 一起分发。

lib/DonutCsharp/ 中,您将找到一个完整的 Visual Studio Solution,它已经配置了 Costura.Fody NuGet 包。Costura 会自动将非托管的 donut.dll(同时支持 x86 和 x64)静态嵌入到您的托管程序集中。您只需打开 .sln 文件并编译即可。 输出将是一个独立的可执行文件,随时可用于您的行动。

``` // Snippet from lib/DonutCsharp/Program.cs using System; using System.Runtime.InteropServices; namespace DonutCsharp { // ... [Structures and Constants Mapping] ... class Program { [DllImport("donut.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int DonutCreate(ref DONUT_CONFIG config); [DllImport("donut.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int DonutDelete(ref DONUT_CONFIG config); [DllImport("donut.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr DonutError(int err); static void Main(string[] args) { DONUT_CONFIG config = new DONUT_CONFIG(); config.input = args[0]; config.output = args.Length >= 2 ? args[1] : "payload.bin"; config.arch = DonutConstants.DONUT_ARCH_X84; // ... [Other Configs] ... int err = DonutCreate(ref config); if (err == DonutConstants.DONUT_ERROR_SUCCESS) { Console.WriteLine($"[+] Shellcode successfully generated at: {config.output}"); } else { IntPtr errorPtr = DonutError(err); string errorMessage = Marshal.PtrToStringAnsi(errorPtr); Console.WriteLine($"[-] Donut Error: {errorMessage}"); } DonutDelete(ref config); } } } ```

5. 子项目

Donut 提供了四个配套项目:

工具 说明
DemoCreateProcess 用于测试的示例 .NET 程序集。接受两个命令行参数,每个参数指定一个要执行的程序。
DonutTest 用于测试 donut 的简单 C# shellcode 注入器。shellcode 必须经过 base64 编码并以字符串形式复制进去。
ModuleMonitor 一个概念验证工具,可以检测由 Donut 和 Cobalt Strike 的 execute-assembly 等工具执行的 CLR 注入。
ProcessManager 一个进程发现工具,进攻方操作人员可用于确定注入目标,防御方操作人员可用于确定正在运行的进程、这些进程具有哪些属性,以及它们是否加载了 CLR。

6. 使用 Donut 进行开发

您可能希望增加对更多类型 payload 的支持,更改我们的功能集,或者将 Donut 集成到您现有的工具中。我们提供了 开发者文档。额外的功能留作读者的练习。我们的建议:

  • 添加环境密钥。
  • 通过在每次生成 shellcode 时对加载器进行混淆,使 Donut 具有多态性。
  • 将 Donut 作为模块集成到您最喜欢的 RAT/C2 框架中。

7. 问题与讨论

如果您对 Donut 有任何问题或意见。请加入 BloodHound Gang Slack 中的 #Donut 频道

8. 免责声明

我们对本软件或技术的任何滥用行为不承担责任。Donut 作为 CLR 注入和通过 shellcode 进行内存加载的演示提供,旨在为红队人员提供模拟对手的方法,并为防御者提供构建分析和缓解措施的参考框架。这不可避免地会面临恶意软件作者和威胁行为者滥用它的风险。然而,我们相信净收益大于风险。希望这是正确的。如果 EDR 或 AV 产品能够通过特征码或行为模式检测到 Donut,我们不会更新 Donut 来对抗特征码或检测方法。为避免引起不快,请勿询问。

8. 鸣谢 / 致谢

此分支的自定义 CLR 宿主实现深受 IBM X-Force Red 团队提供的出色研究和概念验证启发。非常感谢 Being-A-Good-CLR-Host 的创建者为隐蔽的、无 Fusion 的 .NET 执行铺平了道路。

我们还要感谢 Donut 的原作者 TheWover,是他构建了使这种 OPSEC 演进成为可能的基础框架。

标签:Gophish, Shellcode生成, 免杀技术, 内存加载, 安全意识培训, 客户端加密, 暴力破解检测, 知识库安全, 请求拦截, 逆向工具