TheWover/donut
GitHub: TheWover/donut
将 .NET 程序集、原生 PE 文件及脚本转换为位置无关 Shellcode,实现 Windows 载荷内存加载执行的开源安全研究工具。
Stars: 4482 | Forks: 733
当前版本:v1.1
目录
1. 简介
Donut 是一个位置无关代码,能够实现 VBScript、JScript、EXE、DLL 文件和 dotNET 程序集的内存执行。由 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#、Hexadecimal 和 UUID 字符串。
Linux 和 Windows 都有动态和静态库,可以集成到你自己的项目中。还有一个 Python 模块,你可以在 构建和使用 Python 扩展 中了解更多信息。
2. 工作原理
Donut 为每种支持的文件类型包含独立的加载器。对于 dotNET EXE/DLL 程序集,Donut 使用非托管 CLR 托管 API 来加载公共语言运行时。一旦 CLR 加载到宿主进程中,就会创建一个新的应用程序域,以便在可释放的 AppDomain 中运行程序集。当 AppDomain 准备就绪时,dotNET 程序集通过 AppDomain.Load_3 方法加载。最后,用户指定的 EXE 入口点或 DLL 公共方法将附带任何额外参数被调用。有关 非托管 CLR 托管 API 的文档,请参阅 MSDN。有关 CLR 宿主的独立示例,请参阅 此处代码。
VBScript 和 JScript 文件使用 IActiveScript 接口执行。还对 Windows Script Host (wscript/cscript) 提供的某些方法提供了最低限度的支持。有关独立示例,请参阅 此处代码。有关更详细的描述,请阅读:JavaScript、VBScript、JScript 和 XSL 的内存执行
非托管或原生 EXE/DLL 文件使用自定义 PE 加载器执行,支持延迟导入、TLS 和修补命令行。仅支持具有重定位信息的文件。阅读 DLL 的内存执行 了解更多信息。
加载器可以禁用 AMSI 和 WLDP,以帮助逃避对内存中执行的恶意文件的检测。欲了解更多信息,请阅读 红队如何绕过 .NET 动态代码的 AMSI 和 WLDP。它还支持使用 aPLib 或 RtlDecompressBuffer API 在内存中解压文件。阅读 数据压缩 了解更多信息。
自 v1.0 起,ETW 也被绕过。与 AMSI/WLDP 一样,这是一个模块化系统,允许你用自己的绕过方法替换默认的。默认绕过源自 XPN 的研究。阅读 隐藏你的 .NET - ETW 了解更多信息。
默认情况下,加载器将覆盖非托管 PE 的 PE 头(从基址到 `IMAGE_OPTIONAL_HEADER.SizeOfHeaders`)。如果未使用诱饵模块(模块重载),则 PE 头将被清零。如果使用了诱饵模块,则诱饵模块的 PE 头将用于覆盖有效载荷模块的 PE 头。这是为了通过比较内存中模块的 PE 头与磁盘上支持它们的文件来阻止检测。用户可以请求将所有 PE 头保留在其原始状态。这对于有效载荷模块需要访问其 PE 头的场景很有帮助,例如查找嵌入式 PE 资源时。
有关使用生成器的详细演示以及 Donut 如何影响战术技术,请阅读 Donut - 将 .NET 程序集作为 Shellcode 注入。有关加载器的更多信息,请阅读 从内存加载 .NET 程序集。
希望了解更多内部细节的人应参考 开发者说明。
3. 构建
有两种类型的构建。如果你想调试 Donut,请参考 此处的文档。如果不需要,请继续阅读有关发布版构建的内容。
克隆
从 Windows 命令提示符或 Linux 终端克隆仓库。
git clone http://github.com/thewover/donut.git下一步取决于你的操作系统和你决定使用的编译器。目前,Donut 的生成器和加载器模板可以使用 Microsoft Visual Studio 2019 和 MinGW-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 或进程名将原始二进制文件 (loader.bin) 注入到进程中。"inject_local.exe" 会将原始二进制文件注入到其自己的进程中。
要单独构建这些支持可执行文件,你可以使用 MSVC makefile。例如,要构建 "inject_local.exe" 来测试你的 donut shellcode,你可以运行。
nmake inject_local -f Makefile.msvc发布版本
Donut 的每个发布版本都提供了包含已编译可执行文件的标签。
目前,还有两个可用的生成器。
4. 使用说明
下表列出了命令行版本生成器支持的开关。
| 开关 | 参数 | 描述 |
|---|---|---|
| -a | 加载器的目标架构 : 1=x86, 2=amd64, 3=x86+amd64(默认)。 | |
| -b | level | 绕过 AMSI/WLDP 的行为 : 1=无, 2=失败时中止, 3=失败时继续(默认) |
| -k | headers | 保留 PE 头。1=覆盖 (默认), 2=全部保留 |
| -j | decoy | 用于模块重载的诱饵模块的可选路径。 |
| -c | class | 可选的类名。(.NET DLL 必需)也可以包括命名空间:例如 namespace.class |
| -d | name | 为 .NET 创建的 AppDomain 名称。如果启用了熵,则会随机生成一个。 |
| -e | level | 熵级别。1=无, 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=无, 2=aPLib, 3=LZNT1, 4=Xpress, 5=Xpress Huffman。目前,后三者仅在 Windows 上受支持。 |
Payload Requirements
你的 Payload 必须满足一些特定要求 Donut 才能成功加载它。
.NET Assemblies
- 入口点方法必须仅接受字符串作为参数,或不接受参数。
- 入口点方法必须标记为 public 和 static。
- 包含入口点方法的类必须标记为 public。
- 程序集不得是混合程序集(同时包含托管和非托管代码)。
- 因此,程序集不得包含任何非托管导出。
Native EXE/DLL
- 不支持使用 Cygwin 构建的二进制文件。
- 不支持使用 Cygwin 构建的二进制文件。
Cygwin 可执行文件使用的初始化例程期望宿主进程从磁盘运行。如果从内存执行,宿主进程很可能会崩溃。
Unmanaged DLLs
- 用户指定的入口点方法必须仅接受字符串作为参数,或不接受参数。我们提供了一个 示例。
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 以对抗签名或检测方法。为避免冒犯,请勿询问。