Donut Shellcode Generator

repository·master·Indexed 26 days ago

https://github.com/thewover/donut

An open-source position-independent code generator that enables in-memory execution of VBScript, JScript, EXE, DLL, and .NET assemblies. Designed for red teaming and security research, it features symmetric encryption (Chaskey), compression (aPLib, LZNT1, Xpress), and AMSI/ETW/WLDP bypassing. The project includes a Python extension, a C# remote injector (DonutTest), and monitoring tools like ModuleMonitor and ProcessManager.

Tokens
8.1K
Snippets
15
Records
56
Agent score
84%

What's inside Donut

  1. Overview of Donut

    master

    Donut is a position-independent code generator that enables in-memory execution of VBScript, JScript, EXE, DLL files, and .NET assemblies. It can be used as a staged module from an HTTP server or embedded directly in a loader.

    Key features include:

    • Compression (aPLib, LZNT1, Xpress, Xpress Huffman).
    • 128-bit symmetric encryption using Chaskey.
    • AMSI, WLDP, and ETW patching/bypassing.
    • Overwriting native PE headers to deter memory scanners.
    • Multiple output formats: C, Ruby, Python, PowerShell, Base64, C#, Hexadecimal, and UUID string.
  2. Overview of ProcessManager

    master
    ProcessManager is a .NET Assembly designed for enumerating processes on the local machine or a remote machine (using the current token). It is compatible with .NET v3.5 and uses only built-in .NET APIs and PInvoke, avoiding third-party libraries or WMI. A key feature is its ability to identify if a process is 'managed' (has the CLR loaded).
  3. Overview of ModuleMonitor

    master
    ModuleMonitor is a tool that uses the WMI Event Win32_ModuleLoadTrace to monitor module loading activities. It provides filtering capabilities and detailed data regarding loaded modules. A key feature is the CLR Sentry option, which attempts to detect CLR (Common Language Runtime) injection attacks by identifying processes that load the CLR despite not being native .NET programs.
  4. Understand the Donut Instance and Module execution flow

    master

    The Donut loader contains an Instance which acts as a configuration object holding data typically found in the stack, .data, or .rodata sections.

    Execution Flow:

    1. Decryption: If encryption is enabled, the loader decrypts the data before resolving API addresses.
    2. Verification: To ensure successful decryption, the loader hashes a randomly generated string from the sig field using the Maru algorithm and compares it to the mac field.
    3. Retrieval: The loader checks if the executable is embedded in the instance or must be downloaded from a remote HTTP staging server.
    4. Decompression: If required, the data is decompressed.
    5. Execution: The module is loaded into memory and executed.
  5. Extend the Donut loader with new APIs

    master

    Because Donut is not natively modular, adding new functionality requires manual updates to the loader's API resolution mechanism. To include a new API, follow these four steps:

    1. Declare function pointers: In loader/winapi.h, declare a typedef for the function pointer using the WINAPI calling convention.
    2. Update API metadata:
      • Add an entry to the api_imports array in donut.c. This array contains API_IMPORT structures consisting of a symbolic constant for the DLL and a case-sensitive API string.
      • Ensure the order of entries in api_imports matches the order of function pointers in the api member of the DONUT_INSTANCE structure.
    3. Update DLL names: If the required API is in a DLL other than ntdll.dll, kernel32.dll, or kernelbase.dll, you must add the DLL name (separated by semicolons) to the DLL_NAMES definition.
    4. Invoke the API: Once resolved, call the API by referencing the function pointer through the DONUT_INSTANCE pointer.

    Note: The api member in DONUT_INSTANCE (defined in include/donut.h) is a union that can hold up to 64 function pointers or hashes.

  6. Payload requirements for .NET Assemblies

    master

    To ensure successful loading of .NET assemblies, the following requirements must be met:

    • The entry point method must be public and static.
    • The entry point method must take either no arguments or only string arguments.
    • The class containing the entry point method must be public.
    • The Assembly must NOT be a Mixed Assembly (it cannot contain both managed and native code/Unmanaged Exports).
  7. Run DonutTest.exe

    master

    DonutTest is a C# shellcode remote injector. It determines the architecture (x86 or x64) of the target process and injects the appropriate shellcode version using CreateRemoteThread.

    Usage: DonutTest.exe [PID]

    If no PID is specified, the application will inject the shellcode into its own process.

    DonutTest.exe [PID]
  8. Payload requirements for Native EXE/DLL

    master

    Requirements for unmanaged/native binaries:

    • EXE/DLL: If specifying a user-defined entry point method, it must take either no arguments or only a string argument.
    • Cygwin: Binaries built with Cygwin are unsupported as they expect the host process to be running from disk and will likely crash when executed from memory.
  9. Use the Donut API to generate shellcode

    master

    To generate shellcode using the Donut dynamic library, follow these steps:

    1. Load the library (donut.dll on Windows or donut.so on Linux) using dlopen.
    2. Resolve the API symbols DonutCreate, DonutDelete, and DonutError using dlsym.
    3. Initialize your configuration structure.
    4. Call _DonutCreate(&c) to generate the shellcode.
    5. Check the return error against DONUT_ERROR_SUCCESS.
    6. If an error occurs, retrieve the error message using _DonutError(err).
    7. Clean up resources using _DonutDelete(&c).
  10. Build Donut support tools

    master

    Donut includes support tools like hash.exe, encrypt.exe, inject.exe, and inject_local.exe. To build these using the MSVC makefile, use the following pattern:

    nmake <tool_name> -f Makefile.msvc

    Example to build inject_local.exe:

    nmake inject_local -f Makefile.msvc