Zuigetzu/Donut-CustomHost
GitHub: Zuigetzu/Donut-CustomHost
Donut 的安全研究增强分支,通过自定义 CLR 宿主、架构感知 ETW 绕过和零补丁 AMSI 规避,生成具有高度隐蔽性的内存执行 shellcode。
Stars: 1 | Forks: 0
当前版本:v1.1
目录
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 宿主: 通过提供
IHostAssemblyManager、IHostAssemblyStore和IHostMemoryManager的自定义内存实现,Donut 成功拦截了程序集加载过程(AppDomain.Load_2)。我们的自定义 Assembly Store 不再依赖默认的 Fusion 加载器(它可能会留下跟踪痕迹并将文件释放到磁盘),而是以IStream的形式从内存中直接提供匹配完全限定名(FQN)的 payload。 - 内存跟踪规避: 自定义 Memory Manager 就像一层伪装,向那些试图寻找恶意内存跟踪事件的 EDR 启发式检测返回
S_OK,使其致盲。 - 架构感知的 ETW 绕过: 我们实现了一种高级的跳转技术。我们没有盲目地用标准的
RET指令修补NtTraceEvent(这可能会在 x86/WoW64 环境中破坏堆栈,因为stdcall实际上需要RET 10h),加载器会动态扫描该函数以找到其合法的返回指令。然后,它使用相对JMP(0xE9)修补入口点,直接指向那个原生的返回指令。这确保了在 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.mingwLinux
要生成动态库 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 构建的二进制文件。
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):
2. Golang 示例 (lib/main.go)
Go 可以通过 CGO 与 Donut 进行原生互操作。lib/ 中的 main.go 文件已完全配置了相应的 CFLAGS 和 LDFLAGS 以链接到修复后的库。
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 文件并编译即可。 输出将是一个独立的可执行文件,随时可用于您的行动。
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 演进成为可能的基础框架。