Difficulty: Beginner

Module 1: The PE-to-Shellcode Problem

Why you would want to convert a PE file into position-independent shellcode, and the fundamental challenges involved.

Module Objective

Understand what PE-to-shellcode conversion means, why offensive operators need it, how Donut fits into the landscape alongside tools like sRDI and ReflectiveDLLInjection, and the fundamental distinction between a loader and a converter.

1. What Is a PE File?

The Portable Executable (PE) format is the standard binary format on Windows. Every .exe, .dll, .sys, and .NET assembly is a PE file. When you compile a C program with MSVC or MinGW, the output is a PE file that the Windows loader (ntdll!LdrLoadDll) knows how to map into memory.

A PE file is not position-independent by default. It contains:

Shellcode, by contrast, is a raw blob of machine code that can execute from any address in memory with no external dependencies. Converting a PE into shellcode means solving all of these problems in a self-contained way.

2. Why Convert PE to Shellcode?

Shellcode is the universal unit of code injection on Windows. Nearly every injection technique — VirtualAllocEx + WriteProcessMemory + CreateRemoteThread, APC injection, thread hijacking, fiber injection — expects a blob of position-independent code. If your payload is a PE file, you cannot directly inject it without a loader.

ScenarioWhy Shellcode?
Process injectionInjection primitives expect a function pointer to PIC code, not a structured PE
In-memory executionAvoid dropping files to disk; execute payloads entirely in memory
.NET in unmanaged processesRun C# assemblies in processes that don’t host the CLR, without spawning new processes
Staged payloadsDownload shellcode over HTTP/DNS and execute without touching the filesystem
Exploit payloadsExploits deliver shellcode; wrapping your tool as shellcode makes it exploit-deliverable
Loader diversitySame shellcode works with any injection technique — decouple payload from delivery

3. Loaders vs. Converters

Two fundamentally different approaches exist for running PE files in-memory without the Windows loader:

Reflective Loader (Embedded in the PE)

A reflective loader is compiled into the DLL itself. The DLL contains a special exported function (e.g., ReflectiveLoader) that, when called, maps itself into memory, resolves its own imports, and calls DllMain. The seminal work is Stephen Fewer’s Reflective DLL Injection.

Limitation: only works for DLLs you compile yourself. You must modify the source to include the reflective loader. Does not support EXEs, .NET assemblies, or third-party binaries.

Shellcode Converter (External Tool)

A converter takes an arbitrary PE file as input and produces standalone shellcode as output. The shellcode contains a PIC loader stub plus the original PE payload. At runtime, the stub performs all the steps the Windows loader would: map sections, resolve imports, apply relocations, and transfer control.

This is what Donut does. It supports EXEs, DLLs, .NET assemblies, VBS/JS scripts, and XSL files — without modifying the original binary.

Reflective Loader vs. Shellcode Converter

Your DLL
+ ReflectiveLoader()
Inject DLL blob
Call export
Self-loads

Any PE / .NET
Donut
PIC Shellcode
Loader + Payload

4. The Donut Approach

Donut, created by TheWover and Odzhan, is a shellcode generation framework that takes the converter approach to its logical conclusion. It supports the widest range of input formats of any public tool:

Input TypeHow Donut Handles It
Native EXE (x86/x64)PIC loader maps sections, resolves imports, applies relocations, calls entry point
Native DLL (x86/x64)Same as EXE, plus calls DllMain and optionally a named export with arguments
.NET EXEHosts the CLR, creates an AppDomain, loads the assembly via Assembly::Load, invokes Main()
.NET DLLSame CLR hosting, invokes a specified class method with arguments
VBScript / JScriptCreates scripting engine via COM, loads and executes the script in-memory
XSL filesUses IXMLDOMDocument and IXSLProcessor COM interfaces to process the stylesheet

5. sRDI — The Other Major Converter

Before Donut, the most popular shellcode converter was sRDI (Shellcode Reflective DLL Injection) by monoxgas. sRDI takes a DLL and wraps it with a PIC loader stub. Key differences from Donut:

FeaturesRDIDonut
Input typesDLL onlyEXE, DLL, .NET, VBS, JS, XSL
EncryptionNone (plaintext payload)Chaskey cipher with random keys
CompressionNoneaPLib, LZNT1, or Xpress
AMSI/ETW bypassNoBuilt-in bypass stubs
.NET supportNoFull CLR hosting
StagingNoHTTP/DNS staging support

6. How Donut Shellcode Is Structured

The output shellcode from Donut has a layered structure:

Donut Shellcode Layout

PIC Loader
~4-8 KB
DONUT_INSTANCE
Config + Keys
DONUT_MODULE
Encrypted + Compressed
Payload
  1. PIC Loader — position-independent C code compiled to resolve APIs via PEB walking, decrypt the instance, decompress the module, and dispatch to the correct handler based on payload type
  2. DONUT_INSTANCE — a configuration structure containing API hashes, decryption keys, module size, compression type, bypass flags, and other runtime parameters
  3. DONUT_MODULE — the encrypted (and optionally compressed) payload with metadata like class name, method name, and arguments

7. The Generation Pipeline

When you run donut -f payload.exe, the following steps occur:

Step-by-Step Generation

  1. Parse the input — identify file type (PE, .NET, VBS, JS, XSL), architecture, and characteristics
  2. Build DONUT_MODULE — serialize the payload with metadata (class, method, arguments, runtime version)
  3. Compress (optional) — compress the module using aPLib, LZNT1, or Xpress Huffman
  4. Generate random keys — create random Chaskey key, nonce, and counter for encryption
  5. Encrypt DONUT_MODULE — encrypt the compressed module with Chaskey in CTR mode
  6. Build DONUT_INSTANCE — populate with API hashes, keys, bypass flags, module metadata
  7. Encrypt DONUT_INSTANCE — encrypt the instance with a separate Chaskey key
  8. Concatenate — prepend the PIC loader, append the encrypted instance + module
  9. Output — write the final shellcode to loader.bin

8. Course Roadmap

What Comes Next

ModuleTopicFocus
1 (this)The PE-to-Shellcode ProblemMotivation and landscape
2PE Loader FundamentalsSections, imports, relocations
3.NET CLR HostingIn-memory .NET execution
4Module ArchitectureDONUT_MODULE and DONUT_INSTANCE
5The Donut LoaderPIC loader internals
6Encryption & Anti-DetectionChaskey, AMSI/WLDP/ETW bypass
7Advanced Payload TypesCOM-based script execution, exit options
8Full Chain & DetectionIntegration, YARA, forensics

Knowledge Check

1. What is the primary advantage of a shellcode converter (like Donut) over a reflective loader?

A converter like Donut can take any PE, .NET assembly, or script as input without needing to modify the original binary. A reflective loader must be compiled into the DLL source code.

2. Why can’t a standard PE file be directly injected as shellcode?

PE files are not position-independent: they contain absolute addresses, import references to external DLLs, and sections with different alignment requirements. All of these must be resolved by a loader before the code can execute.

3. Which encryption cipher does Donut use to protect the payload?

Donut uses the Chaskey lightweight block cipher (designed by Nicky Mouha) in CTR mode with randomly generated keys for each shellcode generation. This was chosen for its small code footprint and suitability for PIC code.