Skip to content

Commit b70467e

Browse files
committed
added options for preserving native PE headers
1 parent aa42dc2 commit b70467e

13 files changed

+1799
-1728
lines changed

Diff for: README.md

+8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050

5151
<p>The loader can disable AMSI and WLDP to help evade detection of malicious files executed in-memory. For more information, read <a href="https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/">How Red Teams Bypass AMSI and WLDP for .NET Dynamic Code</a>. It also supports decompression of files in memory using aPLib or the RtlDecompressBuffer API. Read <a href="https://modexp.wordpress.com/2019/12/08/shellcode-compression/">Data Compression</a> for more information.</p>
5252

53+
<p>By default, the loader will overwrite the PE headers of unmanaged PEs (from the base address to `IMAGE_OPTIONAL_HEADER.SizeOfHeaders`). If no decoy module is used (module overloading), then the PE headers will be zeroed. If a decoy module is used, the PE headers of the decoy module will be used to overwrite those of the payload module. This is to deter detection by comparing the PE headers of modules in memory with the file backing them on disk. The user may request that all PE headers be preserved in their original state. This is helpful for scenarios when the payload module needs to access its PE headers, such as when looking up embedded PE resources.</p>
54+
5355
<p>For a detailed walkthrough using the generator and how Donut affects tradecraft, read <a href="https://thewover.github.io/Introducing-Donut/">Donut - Injecting .NET Assemblies as Shellcode</a>. For more information about the loader, read <a href="https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/">Loading .NET Assemblies From Memory</a>.</p>
5456

5557
<p>Those who wish to know more about the internals should refer to <a href="https://github.com/TheWover/donut/blob/master/docs/devnotes.md">Developer notes.</a></p>
@@ -148,6 +150,12 @@
148150
<td><var>level</var></td>
149151
<td>Behavior for bypassing AMSI/WLDP : 1=None, 2=Abort on fail, 3=Continue on fail.(default)</td>
150152
</tr>
153+
154+
<tr>
155+
<td><strong>-k</strong></td>
156+
<td><var>headers</var></td>
157+
<td>Preserve PE headers. 1=Overwrite (default), 2=Keep all</td>
158+
</tr>
151159

152160
<tr>
153161
<td><strong>-c</strong></td>

Diff for: donut.c

+34-2
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,8 @@ static int build_instance(PDONUT_CONFIG c) {
866866
inst->entropy = c->entropy;
867867
// set the bypass level
868868
inst->bypass = c->bypass;
869+
// set the headers level
870+
inst->headers = c->headers;
869871
// set the module length
870872
inst->mod_len = c->mod_len;
871873

@@ -1408,6 +1410,13 @@ static int validate_loader_cfg(PDONUT_CONFIG c) {
14081410
DPRINT("Option to bypass AMSI/WDLP %"PRId32" is invalid.", c->bypass);
14091411
return DONUT_ERROR_BYPASS_INVALID;
14101412
}
1413+
1414+
if(c->headers != DONUT_HEADERS_OVERWRITE &&
1415+
c->headers != DONUT_HEADERS_KEEP)
1416+
{
1417+
DPRINT("Option to preserve PE headers (or not) %"PRId32" is invalid.", c->headers);
1418+
return DONUT_ERROR_HEADERS_INVALID;
1419+
}
14111420

14121421
DPRINT("Loader configuration passed validation.");
14131422
return DONUT_ERROR_OK;
@@ -1675,6 +1684,9 @@ const char *DonutError(int err) {
16751684
case DONUT_ERROR_BYPASS_INVALID:
16761685
str = "Invalid bypass option specified.";
16771686
break;
1687+
case DONUT_ERROR_HEADERS_INVALID:
1688+
str = "Invalid PE headers preservation option.";
1689+
break;
16781690
case DONUT_ERROR_NORELOC:
16791691
str = "This file has no relocation information required for in-memory execution.";
16801692
break;
@@ -2075,6 +2087,19 @@ static int validate_bypass(opt_arg *arg, void *args) {
20752087
return 1;
20762088
}
20772089

2090+
// calback to validate headers options
2091+
static int validate_headers(opt_arg *arg, void *args) {
2092+
char *str = (char*)args;
2093+
2094+
arg->u32 = 0;
2095+
if(str == NULL) return 0;
2096+
2097+
// just temporary
2098+
arg->u32 = atoi(str);
2099+
2100+
return 1;
2101+
}
2102+
20782103
static void usage (void) {
20792104
printf(" usage: donut [options] <EXE/DLL/VBS/JS>\n\n");
20802105
printf(" Only the finest artisanal donuts are made of shells.\n\n");
@@ -2107,6 +2132,7 @@ static void usage (void) {
21072132
printf(" -z,--compress: <engine> Pack/Compress file. 1=None, 2=aPLib\n");
21082133
#endif
21092134
printf(" -b,--bypass: <level> Bypass AMSI/WLDP : 1=None, 2=Abort on fail, 3=Continue on fail.(default)\n\n");
2135+
printf(" -k,--headers: <level> Preserve PE headers. 1=Overwrite (default), 2=Keep all\n\n");
21102136

21112137
printf(" examples:\n\n");
21122138
printf(" donut -ic2.dll\n");
@@ -2125,7 +2151,7 @@ int main(int argc, char *argv[]) {
21252151

21262152
printf("\n");
21272153
printf(" [ Donut shellcode generator v0.9.3 (built " __DATE__ " " __TIME__ ")\n");
2128-
printf(" [ Copyright (c) 2019-2020 TheWover, Odzhan\n\n");
2154+
printf(" [ Copyright (c) 2019-2021 TheWover, Odzhan\n\n");
21292155

21302156
// zero initialize configuration
21312157
memset(&c, 0, sizeof(c));
@@ -2134,6 +2160,7 @@ int main(int argc, char *argv[]) {
21342160
c.inst_type = DONUT_INSTANCE_EMBED; // file is embedded
21352161
c.arch = DONUT_ARCH_X84; // dual-mode (x86+amd64)
21362162
c.bypass = DONUT_BYPASS_CONTINUE; // continues loading even if disabling AMSI/WLDP fails
2163+
c.headers = DONUT_HEADERS_OVERWRITE;// overwrites PE headers
21372164
c.format = DONUT_FORMAT_BINARY; // default output format
21382165
c.compress = DONUT_COMPRESS_NONE; // compression is disabled by default
21392166
c.entropy = DONUT_ENTROPY_DEFAULT; // enable random names + symmetric encryption by default
@@ -2144,6 +2171,7 @@ int main(int argc, char *argv[]) {
21442171
get_opt(argc, argv, OPT_TYPE_NONE, NULL, "h;?", "help", usage);
21452172
get_opt(argc, argv, OPT_TYPE_DEC, &c.arch, "a", "arch", validate_arch);
21462173
get_opt(argc, argv, OPT_TYPE_DEC, &c.bypass, "b", "bypass", validate_bypass);
2174+
get_opt(argc, argv, OPT_TYPE_DEC, &c.headers, "k", "headers", validate_headers);
21472175
get_opt(argc, argv, OPT_TYPE_STRING, c.cls, "c", "class", NULL);
21482176
get_opt(argc, argv, OPT_TYPE_STRING, c.domain, "d", "domain", NULL);
21492177
get_opt(argc, argv, OPT_TYPE_DEC, &c.entropy, "e", "entropy", validate_entropy);
@@ -2242,7 +2270,11 @@ int main(int argc, char *argv[]) {
22422270

22432271
printf(" [ AMSI/WDLP : %s\n",
22442272
c.bypass == DONUT_BYPASS_NONE ? "none" :
2245-
c.bypass == DONUT_BYPASS_ABORT ? "abort" : "continue");
2273+
c.bypass == DONUT_BYPASS_ABORT ? "abort" : "continue");
2274+
2275+
printf(" [ PE Headers : %s\n",
2276+
c.headers == DONUT_HEADERS_OVERWRITE ? "overwrite" :
2277+
c.headers == DONUT_HEADERS_KEEP ? "keep" : "Undefined");
22462278

22472279
printf(" [ Shellcode : \"%s\"\n", c.output);
22482280
if(c.oep != 0) {

Diff for: donutmodule.c

+17-11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ static PyObject *Donut_Create(PyObject *self, PyObject *args, PyObject *keywds)
3939

4040
int arch = 0; // target CPU architecture or mode
4141
int bypass = 0; // AMSI/WDLP bypassing behavior
42+
int headers = 0; // Preserve PE headers behavior
4243
int compress = 0; // compress input file
4344
int entropy = 0; // whether to randomize API hashes and use encryption
4445
int format = 0; // output format
@@ -60,14 +61,14 @@ static PyObject *Donut_Create(PyObject *self, PyObject *args, PyObject *keywds)
6061
char *modname = NULL; // name of module stored on HTTP server
6162

6263
static char *kwlist[] = {
63-
"file", "arch", "bypass", "compress", "entropy",
64+
"file", "arch", "bypass", "headers", "compress", "entropy",
6465
"format", "exit_opt", "thread", "oep", "output",
6566
"runtime", "appdomain", "cls", "method", "params",
6667
"unicode", "server", "url", "modname", NULL};
6768

6869
if (!PyArg_ParseTupleAndKeywords(
69-
args, keywds, "s|iiiiiiisssssssisss", kwlist, &input, &arch,
70-
&bypass, &compress, &entropy, &format, &exit_opt, &thread,
70+
args, keywds, "s|iiiiiiiisssssssisss", kwlist, &input, &arch,
71+
&bypass, &headers, &compress, &entropy, &format, &exit_opt, &thread,
7172
&oep, &output, &runtime, &domain, &cls, &method, &params,
7273
&unicode, &server, &server, &modname))
7374
{
@@ -80,14 +81,15 @@ static PyObject *Donut_Create(PyObject *self, PyObject *args, PyObject *keywds)
8081
memset(&c, 0, sizeof(c));
8182

8283
// default settings
83-
c.inst_type = DONUT_INSTANCE_EMBED; // file is embedded
84-
c.arch = DONUT_ARCH_X84; // dual-mode (x86+amd64)
85-
c.bypass = DONUT_BYPASS_CONTINUE; // continues loading even if disabling AMSI/WLDP fails
86-
c.format = DONUT_FORMAT_BINARY; // default output format
87-
c.compress = DONUT_COMPRESS_NONE; // compression is disabled by default
88-
c.entropy = DONUT_ENTROPY_DEFAULT; // enable random names + symmetric encryption by default
89-
c.exit_opt = DONUT_OPT_EXIT_THREAD; // default behaviour is to exit the thread
90-
c.unicode = 0; // command line will not be converted to unicode for unmanaged DLL function
84+
c.inst_type = DONUT_INSTANCE_EMBED; // file is embedded
85+
c.arch = DONUT_ARCH_X84; // dual-mode (x86+amd64)
86+
c.bypass = DONUT_BYPASS_CONTINUE; // continues loading even if disabling AMSI/WLDP fails
87+
c.headers = DONUT_HEADERS_OVERWRITE;// overwrite PE header
88+
c.format = DONUT_FORMAT_BINARY; // default output format
89+
c.compress = DONUT_COMPRESS_NONE; // compression is disabled by default
90+
c.entropy = DONUT_ENTROPY_DEFAULT; // enable random names + symmetric encryption by default
91+
c.exit_opt = DONUT_OPT_EXIT_THREAD; // default behaviour is to exit the thread
92+
c.unicode = 0; // command line will not be converted to unicode for unmanaged DLL function
9193

9294
// input file
9395
if(input != NULL) {
@@ -102,6 +104,10 @@ static PyObject *Donut_Create(PyObject *self, PyObject *args, PyObject *keywds)
102104
if(bypass != 0) {
103105
c.bypass = bypass;
104106
}
107+
// headers options
108+
if(headers != 0) {
109+
c.headers = headers;
110+
}
105111
// class of .NET assembly
106112
if(cls != NULL) {
107113
strncpy(c.cls, cls, DONUT_MAX_NAME - 1);

Diff for: examples/dynamic.c

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ int main(int argc, char *argv[]) {
6161
c.inst_type = DONUT_INSTANCE_EMBED; // file is embedded
6262
c.arch = DONUT_ARCH_X84; // dual-mode (x86+amd64)
6363
c.bypass = DONUT_BYPASS_CONTINUE; // continues loading even if disabling AMSI/WLDP fails
64+
c.headers = DONUT_HEADERS_OVERWRITE;// overwrite PE headers
6465
c.format = DONUT_FORMAT_BINARY; // default output format
6566
c.compress = DONUT_COMPRESS_NONE; // compression is disabled by default
6667
c.entropy = DONUT_ENTROPY_DEFAULT; // enable random names + symmetric encryption by default

Diff for: examples/static.c

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ int main(int argc, char *argv[]) {
2323
c.inst_type = DONUT_INSTANCE_EMBED; // file is embedded
2424
c.arch = DONUT_ARCH_X84; // dual-mode (x86+amd64)
2525
c.bypass = DONUT_BYPASS_CONTINUE; // continues loading even if disabling AMSI/WLDP fails
26+
c.headers = DONUT_HEADERS_OVERWRITE;// overwrite PE headers
2627
c.format = DONUT_FORMAT_BINARY; // default output format
2728
c.compress = DONUT_COMPRESS_NONE; // compression is disabled by default
2829
c.entropy = DONUT_ENTROPY_DEFAULT; // enable random names + symmetric encryption by default

Diff for: include/donut.h

+7
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ typedef struct _GUID {
128128
#define DONUT_ERROR_COMPRESSION 19
129129
#define DONUT_ERROR_INVALID_ENTROPY 20
130130
#define DONUT_ERROR_MIXED_ASSEMBLY 21
131+
#define DONUT_ERROR_HEADERS_INVALID 22
131132

132133
// target architecture
133134
#define DONUT_ARCH_ANY -1 // for vbs and js files
@@ -178,6 +179,10 @@ typedef struct _GUID {
178179
#define DONUT_BYPASS_ABORT 2 // If bypassing AMSI/WLDP fails, the loader stops running
179180
#define DONUT_BYPASS_CONTINUE 3 // If bypassing AMSI/WLDP fails, the loader continues running
180181

182+
// Preserve PE headers options
183+
#define DONUT_HEADERS_OVERWRITE 1 // Overwrite PE headers
184+
#define DONUT_HEADERS_KEEP 2 // Preserve PE headers
185+
181186
#define DONUT_MAX_NAME 256 // maximum length of string for domain, class, method and parameter names
182187
#define DONUT_MAX_DLL 8 // maximum number of DLL supported by instance
183188
#define DONUT_MAX_MODNAME 8
@@ -371,6 +376,7 @@ typedef struct _DONUT_INSTANCE {
371376
char exit_api[DONUT_MAX_NAME]; // exit-related API
372377

373378
int bypass; // indicates behaviour of byassing AMSI/WLDP/ETW
379+
int headers; // indicates whether to overwrite PE headers
374380
char wldpQuery[32]; // WldpQueryDynamicCodeTrust
375381
char wldpIsApproved[32]; // WldpIsClassInApprovedList
376382
char amsiInit[16]; // AmsiInitialize
@@ -425,6 +431,7 @@ typedef struct _DONUT_CONFIG {
425431
// general / misc options for loader
426432
int arch; // target architecture
427433
int bypass; // bypass option for AMSI/WDLP
434+
int headers; // preserve PE headers option
428435
int compress; // engine to use when compressing file via RtlCompressBuffer
429436
int entropy; // entropy/encryption level
430437
int format; // output format for loader

Diff for: lib/donut.h

+6
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#define DONUT_ERROR_COMPRESSION 19
7171
#define DONUT_ERROR_INVALID_ENTROPY 20
7272
#define DONUT_ERROR_MIXED_ASSEMBLY 21
73+
#define DONUT_ERROR_HEADERS_INVALID 22
7374

7475
// target architecture
7576
#define DONUT_ARCH_ANY -1 // just for vbs,js and xsl files
@@ -120,6 +121,10 @@
120121
#define DONUT_BYPASS_ABORT 2 // If bypassing AMSI/WLDP fails, the loader stops running
121122
#define DONUT_BYPASS_CONTINUE 3 // If bypassing AMSI/WLDP fails, the loader continues running
122123

124+
// Preserve PE headers options
125+
#define DONUT_HEADERS_OVERWRITE 1 // Overwrite PE headers
126+
#define DONUT_HEADERS_KEEP 1 // Preserve PE headers
127+
123128
#define DONUT_MAX_NAME 256 // maximum length of string for domain, class, method and parameter names
124129
#define DONUT_MAX_DLL 8 // maximum number of DLL supported by instance
125130
#define DONUT_MAX_MODNAME 8
@@ -132,6 +137,7 @@ typedef struct _DONUT_CONFIG {
132137
// general / misc options for loader
133138
int arch; // target architecture
134139
int bypass; // bypass option for AMSI/WDLP
140+
int headers; // preserve PE headers option
135141
int compress; // engine to use when compressing file via RtlCompressBuffer
136142
int entropy; // entropy/encryption level
137143
int format; // output format for loader

Diff for: loader/inmem_pe.c

+6-3
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,12 @@ VOID RunPE(PDONUT_INSTANCE inst, PDONUT_MODULE mod) {
281281

282282
Memcpy(shcp, sh, ntc.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER));
283283

284-
DPRINT("Wiping Headers from memory");
285-
Memset(cs, 0, nt->OptionalHeader.SizeOfHeaders);
286-
Memset(base, 0, nt->OptionalHeader.SizeOfHeaders);
284+
if(inst->headers == 1)
285+
{
286+
DPRINT("Wiping Headers from memory");
287+
Memset(cs, 0, nt->OptionalHeader.SizeOfHeaders);
288+
Memset(base, 0, nt->OptionalHeader.SizeOfHeaders);
289+
}
287290

288291
DPRINT("Ummapping temporary local view of section to persist changes.");
289292
status = inst->api.NtUnmapViewOfSection(inst->api.GetCurrentProcess(), cs);

0 commit comments

Comments
 (0)