Odzhan's blog post (about the generator): https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/
TheWover's blog post (detailed walkthorugh, and about how donut affects tradecraft): https://thewover.github.io/Introducing-Donut/
Donut is a shellcode generation tool that creates x86 or x64 shellcode payloads from .NET Assemblies. This shellcode may be used to inject the Assembly into arbitrary Windows processes. Given an arbitrary .NET Assembly, parameters, and an entry point (such as Program.Main), it produces position-independent shellcode that loads it from memory. The .NET Assembly can either be staged from a URL or stageless by being embedded directly in the shellcode. Either way, the .NET Assembly is encrypted with the Chaskey block cipher and a 128-bit randomly generated key. After the Assembly is loaded through the CLR, the original reference is erased from memory to deter memory scanners. The Assembly is loaded into a new Application Domain to allow for running Assemblies in disposable AppDomains.
It can be used in several ways.
As a Standalone Tool
Donut can be used as-is to generate shellcode from arbitrary .NET Assemblies. Both a Windows EXE and a Python script (Python planned for v1.0) are provided for payload generation. The command-line syntax is as described below.
usage: donut [options] -f <.NET assembly> -c <namespace.class> -m <Method> -f <path> .NET assembly to embed in PIC and DLL. -u <URL> HTTP server hosting the .NET assembly. -c <namespace.class> The assembly class name. -m <method> The assembly method name. -p <arg1,arg2...> Optional parameters for method, separated by comma or semi-colon. -a <arch> Target architecture : 1=x86, 2=amd64(default). -r <version> CLR runtime version. v4.0.30319 is used by default. -d <name> AppDomain name to create for assembly. Randomly generated by default. examples: donut -a 1 -c TestClass -m RunProcess -p notepad.exe -f loader.dll donut -f loader.dll -c TestClass -m RunProcess -p notepad.exe -u http://remote_server.com/modules/
Building Donut from Source:
Tags have been provided for each release version of donut that contain the compiled executables.
However, you may also clone and build the source yourself using the provided makefiles. Start a Microsoft Visual Studio Developer Command Prompt and
cd to donut's directory. The Microsft (non-gcc) Makefile can be specified with
-f Makefile.msvc. The makefile provides the following commmands to build donut:
nmake donut -f Makefile.msvc nmake debug -f Makefile.msvc nmake clean -f Makefile.msvc
As a Library
donut can be compiled as both dynamic and static libraries for both Linux (.a / .so) and Windows(.lib / .dll). It has a simple API that is described in docs/api.html. Two exported functions are provided:
int DonutCreate(PDONUT_CONFIG c) and
int DonutDelete(PDONUT_CONFIG c) .
As a Template - Rebuilding the shellcode
payload.c contains the .NET assembly loader, which should successfully compile with both Microsoft Visual Studio and mingw-w64. Make files have been provided for both compilers which will generate x86-64 shellcode by default unless x86 is supplied as a label to nmake/make. Whenever payload.c has been changed, recompiling for all architectures is recommended before rebuilding donut.
Microsoft Visual Studio
Open the x64 Microsoft Visual Studio build environment, switch to the payload directory, and type the following:
nmake clean -f Makefile.msvc nmake -f Makefile.msvc
This should generate a 64-bit executable (payload.exe) from payload.c. exe2h will then extract the shellcode from the .text segment of the PE file and save it as a C array to payload_exe_x64.h. When donut is rebuilt, this new shellcode will be used for all payloads that it generates.
To generate 32-bit shellcode, open the x86 Microsoft Visual Studio build environment, switch to the payload directory, and type the following:
nmake clean -f Makefile.msvc nmake x86 -f Makefile.msvc
This will save the shellcode as a C array to payload_exe_x86.h.
Assuming you're on Linux and mingw-w64 has been installed from packages or source, you may still rebuild the shellcode using our provided makefile. Change to the payload directory and type the following:
make clean -f Makefile.mingw make -f Makefile.mingw
Once you've recompiled for all architectures, you may rebuild donut.
These are left as exercises to the reader. I would personally recommend:
- Add environmental keying
- Make donut polymorphic by obfuscating payload every time shellcode is generated
- Integrate donut as a module into your favorite RAT/C2 Framework
- No, we will not update donut to counter signatures or detections by any AV.
- We are not responsible for any misuse of this software or technique. Donut is provided as a demonstration of CLR Injection through shellcode in order to provide red teamers a way to emulate adversaries and defenders a frame of reference for building analytics and mitigations. This inevitably runs the risk of malware authors and threat actors misusing it. However, we believe that the net benefit outweighs the risk. Hopefully that is correct.
How it works
Donut uses the Unmanaged CLR Hosting API to load the Common Language Runtime. If necessary, the Assembly is downloaded into memory. Either way, it is decrypted using the Chaskey block cipher. Once the CLR is loaded into the host process, a new AppDomain will be created using a random name unless otherwise specified. Once the AppDomain is ready, the .NET Assembly is loaded through AppDomain.Load_3. Finally, the Entry Point specified by the user is invoked with any specified parameters.
The logic above describes how the shellcode generated by donut works. That logic is defined in payload.exe. To get the shellcode, exe2h extracts the compiled machine code from the .text segment in payload.exe and saves it as a C array to a C header file. donut combines the shellcode with a Donut Instance (a configuration for the shellcode) and a Donut Module (a structure containing the .NET assembly, class name, method name and any parameters).
Refer to MSDN for documentation on the Undocumented CLR Hosting API: https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/clr-hosting-interfaces
For a standalone example of a CLR Host, refer to Casey Smith's AssemblyLoader repo: https://github.com/caseysmithrc/AssemblyLoader
Detailed blog posts about how donut works are available at both Odzhan's and TheWover's blogs. Links are at the top of the README.
Donut contains the following elements:
- donut.c: The source code for the donut payload generator
- donut.exe: The compiled payload generator as an EXE
- donut.py: The donut payload generator as a Python script (planned for version 1.0)
- lib/donut.dll, lib/donut.lib: Donut as a dynamic and static library for use in other projects on Windows platform
- lib/donut.so, lib/donut.a: Donut as a dynamic and static library for use in other projects on the Linux platform
- lib/donut.h: Header file to include if using the static or dynamic libraries in a C/C++ project
- payload/payload.c: Source code for the shellcode
- payload/payload.exe: The compiled payload. The shellcode is extracted from this binary file.
- payload/inject.c: A C shellcode injector that injects payload.bin into a specified process for testing.
- payload/inject.exe: The compiled C shellcode injector
- payload/runsc.c: A C shellcode runner for testing payload.bin in the simplest manner possible
- payload/runsc.exe: The compiled C shellcode runner
- payload/exe2h/exe2h.c: Source code for exe2h
- payload/exe2h/exe2h.exe: Extracts the useful machine code from payload.exe and saves as array to C header file
- encrypt.c: Chaskey 128-bit block cipher in Counter (CTR) mode used for encryption.
- hash.c: Maru hash function. Uses the Speck 64-bit block cipher with Davies-Meyer construction for API hashing.
There are three companion projects provided with donut:
- DemoCreateProcess: A sample .NET Assembly to use in testing. Takes two command-line parameters that each specify a program to execute.
- DonutTest: A simple C# shellcode injector to use in testing donut. The shellcode must be base64 encoded and copied in as a string.
- ModuleMonitor: A proof-of-concept tool that detects CLR injection as it is done by tools such as donut and Cobalt Strike's execute-assembly.
- ProcessManager: A Process Discovery tool that offensive operators may use to determine what to inject into and defensive operators may use to determine what is running, what properties those processes have, and whether or not they have the CLR loaded.
- Create a donut Python C extension that allows users to write Python programs that can use the donut API programmatically. It would be written in C, but exposed as a Python module.
- Create a C# version of the generator
- Create a donut.py generator that uses the same command-line parameters as donut.exe
- Add support for HTTP proxies
- Find ways to simplify the shellcode if possible
- Add option to specify max parameter length
- Add support for dynamically finding the entry point of EXEs and executing with command-line params
- Write a blog post on how to integrate donut into your tooling, debug it, customize it, and design payloads that work with it