Skip to content

Commit 2f23a4d

Browse files
authored
Merge pull request #3 from TheWover/dev
Version v0.9.1 "Apple Fritter"
2 parents 35087f4 + ccedde3 commit 2f23a4d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3164
-1471
lines changed
Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
<?xml version="1.0" encoding="utf-8"?>
2-
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3-
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
1+
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
42
<PropertyGroup>
53
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
64
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7-
<ProjectGuid>4fcdf3a3-aeef-43ea-9297-0d3bde3bdad2</ProjectGuid>
5+
<ProjectGuid>{75C4A31E-6E99-4289-8701-EF0B6CD94435}</ProjectGuid>
86
<OutputType>Library</OutputType>
9-
<AppDesignerFolder>Properties</AppDesignerFolder>
10-
<RootNamespace>DemoCreateProcess</RootNamespace>
11-
<AssemblyName>DemoCreateProcess</AssemblyName>
12-
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
7+
<NoStandardLibraries>false</NoStandardLibraries>
8+
<AssemblyName>ClassLibrary</AssemblyName>
9+
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
1310
<FileAlignment>512</FileAlignment>
14-
<Deterministic>true</Deterministic>
1511
</PropertyGroup>
1612
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1713
<DebugSymbols>true</DebugSymbols>
@@ -30,25 +26,26 @@
3026
<ErrorReport>prompt</ErrorReport>
3127
<WarningLevel>4</WarningLevel>
3228
</PropertyGroup>
29+
<PropertyGroup>
30+
<RootNamespace>DemoCreateProcess</RootNamespace>
31+
</PropertyGroup>
3332
<ItemGroup>
34-
<Reference Include="System"/>
35-
36-
<Reference Include="System.Core"/>
37-
<Reference Include="System.Xml.Linq"/>
38-
<Reference Include="System.Data.DataSetExtensions"/>
39-
40-
41-
<Reference Include="Microsoft.CSharp"/>
42-
43-
<Reference Include="System.Data"/>
44-
45-
<Reference Include="System.Net.Http"/>
46-
47-
<Reference Include="System.Xml"/>
33+
<Reference Include="Microsoft.CSharp" />
34+
<Reference Include="System" />
35+
<Reference Include="System.Core" />
36+
<Reference Include="System.Data" />
37+
<Reference Include="System.Data.DataSetExtensions" />
38+
<Reference Include="System.Xml" />
39+
<Reference Include="System.Xml.Linq" />
4840
</ItemGroup>
4941
<ItemGroup>
5042
<Compile Include="Class1.cs" />
51-
<Compile Include="Properties\AssemblyInfo.cs" />
5243
</ItemGroup>
53-
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
54-
</Project>
44+
<ItemGroup>
45+
<None Include="Readme.md" />
46+
</ItemGroup>
47+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSHARP.Targets" />
48+
<ProjectExtensions>
49+
<VisualStudio AllowExistingFolder="true" />
50+
</ProjectExtensions>
51+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.28307.136
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoCreateProcess", "DemoCreateProcess.csproj", "{75C4A31E-6E99-4289-8701-EF0B6CD94435}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{75C4A31E-6E99-4289-8701-EF0B6CD94435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{75C4A31E-6E99-4289-8701-EF0B6CD94435}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{75C4A31E-6E99-4289-8701-EF0B6CD94435}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{75C4A31E-6E99-4289-8701-EF0B6CD94435}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {3A24F1AC-B24D-4029-9661-05CA11DAFC82}
24+
EndGlobalSection
25+
EndGlobal

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
donut:
2-
gcc -Wall -fpack-struct=8 -DDONUT_EXE -I include donut.c hash.c encrypt.c -odonut
3-
gcc -Wall -c -fpack-struct=8 -fPIC -I include donut.c hash.c encrypt.c
2+
gcc -Wall -fpack-struct=8 -DDONUT_EXE -I include donut.c hash.c encrypt.c payload/clib.c -odonut
3+
gcc -Wall -c -fpack-struct=8 -fPIC -I include donut.c hash.c encrypt.c payload/clib.c
44
ar rcs lib/libdonut.a donut.o hash.o encrypt.o
55
gcc -Wall -shared -o lib/libdonut.so donut.o hash.o encrypt.o
66
debug:
7-
gcc -Wall -Wno-format -fpack-struct=8 -DDEBUG -DDONUT_EXE -I include donut.c hash.c encrypt.c -odonut
7+
gcc -Wall -Wno-format -fpack-struct=8 -DDEBUG -DDONUT_EXE -I include donut.c hash.c encrypt.c payload/clib.c -odonut
88
clean:
99
rm *.o donut lib/libdonut.a lib/libdonut.so

Makefile.mingw

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
donut:
2-
x86_64-w64-mingw32-gcc -Wall -fpack-struct=8 -DDONUT_EXE -I include donut.c hash.c encrypt.c -odonut.exe
2+
x86_64-w64-mingw32-gcc -Wall -fpack-struct=8 -DDONUT_EXE -I include donut.c hash.c encrypt.c include/mmap-windows.c payload/clib.c -odonut.exe
33
debug:
4-
x86_64-w64-mingw32-gcc -Wall -Wno-format -fpack-struct=8 -DDEBUG -DDONUT_EXE -I include donut.c hash.c encrypt.c -odonut.exe
4+
x86_64-w64-mingw32-gcc -Wall -Wno-format -fpack-struct=8 -DDEBUG -DDONUT_EXE -I include donut.c hash.c encrypt.c include/mmap-windows.c payload/clib.c -odonut.exe
55
clean:
66
rm donut.exe *.o

Makefile.msvc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
donut:
2-
cl -Zp8 -nologo -DDONUT_EXE -I include donut.c hash.c encrypt.c
3-
cl -Zp8 -nologo -DDLL -LD -I include donut.c hash.c encrypt.c
2+
cl -Zp8 -nologo -DDONUT_EXE -I include donut.c hash.c encrypt.c include/mmap-windows.c payload/clib.c
3+
cl -Zp8 -nologo -DDLL -LD -I include donut.c hash.c encrypt.c include/mmap-windows.c payload/clib.c
44
move donut.lib lib/donut.lib
55
move donut.exp lib/donut.exp
66
move donut.dll lib/donut.dll
77
debug:
8-
cl -Zp8 -nologo -DDEBUG -DDONUT_EXE -I include donut.c hash.c encrypt.c
8+
cl -Zp8 -nologo -DDEBUG -DDONUT_EXE -I include donut.c hash.c encrypt.c include/mmap-windows.c payload/clib.c
99
clean:
1010
del *.obj *.bin donut.exe lib/donut.exp lib/donut.lib lib/donut.dll

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Version: 0.9 (Beta) *please submit issues and requests for v1.0 release*
55

66
Odzhan's blog post (about the generator): https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/
77

8-
TheWover's blog post (detailed walkthorugh, and about how donut affects tradecraft): https://thewover.github.io/Introducing-Donut/
8+
TheWover's blog post (detailed walkthrough, and about how donut affects tradecraft): https://thewover.github.io/Introducing-Donut/
99

1010
## Introduction
1111

@@ -90,6 +90,23 @@ make -f Makefile.mingw
9090

9191
Once you've recompiled for all architectures, you may rebuild donut.
9292

93+
## Bypasses
94+
95+
Donut includes a bypass system for AMSI and other security features. Currently we bypass:
96+
97+
* AMSI in .NET v4.8
98+
* Device Guard policy preventing dynamicly generated code from executing
99+
100+
You may customize our bypasses or add your own. The bypass logic is defined in payload/bypass.c.
101+
102+
Each bypass implements the DisableAMSI fuction with the signature ```BOOL DisableAMSI(PDONUT_INSTANCE inst)```, and comes with 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 is called ```BYPASS_AMSI_A```. If donut is built with that variable defined, then that bypass will be used.
103+
104+
Why do it this way? Because it means that only the bypass you are using is built into payload.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.
105+
106+
Another benefit of this design is that you may write your own AMSI 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.
107+
108+
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.
109+
93110
### Additional features.
94111

95112
These are left as exercises to the reader. I would personally recommend:

docs/2019-5-31-Apple-Fritter.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
layout: post
3+
title: Donut v0.9.1 "Apple Fritter" - Dual-Mode Shellcode, AMSI, and More
4+
---
5+
6+
*TLDR: Version v0.9.1 "Apple Fritter" of Donut has been released, including dual-mode (AMD64+x86) shellcode, AMSI bypassing for .NET v4.8, automatic version detection of payloads, better support for Program.Main().*
7+
8+
# Introduction
9+
10+
In case you are unaware, [Donut](https://github.com/TheWover/donut "Donut") is a shellcode generation tool that creates native shellcode payloads from .NET Assemblies. This shellcode may be used to inject the Assembly into arbitrary Windows processes. Given an arbitrary .NET Assembly, parameters, and an entry point (such as Program.Main), it produces position-independent shellcode that loads it from memory. The .NET Assembly can either be staged from a URL or stageless by being embedded directly in the shellcode. Either way, the .NET Assembly is encrypted with the Chaskey block cipher and a 128-bit randomly generated key. After the Assembly is loaded through the CLR, the original reference is erased from memory to deter memory scanners. The Assembly is loaded into a new Application Domain to allow for running Assemblies in disposable AppDomains.
11+
12+
Today, we released version v0.9.1. The major features include:
13+
14+
* Dual-Mode shellcode that can run in either x64 for x86 (WOW64) processes.
15+
* Automatic detection of the CLR version required for .NET Assembly payloads.
16+
* AMSI bypassing for version .NET 4.8 that ensure all Assemblies can be safely loaded.
17+
* Modular system for adding bypasses. Your choide of bypass functionality is compiled into payload.exe based on compiler flags.
18+
* Bypass for Device Guard policy preventing execution of dynamically generated .NET code
19+
* Better handling of Main functions (Entry Points) that use an object array containing string arrays, rather than an array of strings
20+
21+
# Feature Breakdown
22+
23+
## Dual-Mode Shellcode
24+
25+
Odzhan knew an old trick for crafting shellcode that can run in either x86 or x64 Windows processes using REX prefixes. We combine the x86 and x64 shellcode with a stub that "detects" the architecture of the process. The layout in memory looks like:
26+
27+
```
28+
--------------------------------------------------
29+
| detection stub | x64 shellcode | x86 shellcode |
30+
--------------------------------------------------
31+
```
32+
33+
And the logic:
34+
35+
```assembly
36+
0x31C0 xor eax, eax // null eax
37+
0x48 dec eax // decrement eax to produce an underflow
38+
0x0F88 js dword x86_code // jump to x86 payload if we are in a WOW64 process
39+
<x64_code> // the x64 PIC machine code for the payload
40+
<x86_code> // the x86 PIC machine code for the payload
41+
```
42+
43+
There are two ways this code can execute.
44+
45+
If the process is WOW64 (x86):
46+
47+
1) ```eax``` will be nulled.
48+
2) ```eax``` will be decremented, resulting in an underflow.
49+
3) Since the sign flag is set from the underflow, the condition for the jump is satisfied. Jump to the x86 shellcode.
50+
51+
If the process is x64:
52+
53+
1) ```eax``` will be nulled.
54+
2) ```0x48``` is an REX prefix for the next instruction
55+
3) The previous REX prefix is not valid for the ```js``` instruction. As such, nothing happens.
56+
4) Since the x64 PIC is immediately following the previous instruction in memory, ```eip``` is now pointing at the first instruction in the x64 PIC. It now executes.
57+
58+
Starting in the v0.9.1 "Apple Fritter" release, dual-mode shellcode is the default. You may still tell Donut to produce x86 or x64 shellcode, rather than AMD64+x86.
59+
60+
Naturally, the dual-mode PIC will be larger than the other options. If the size of the PIC matters, use the version for the particular process you are targeting. Or, have your injector check the architecture of the target process before injecting into it. If not, use the dual-mode version to ensure maximum compatbility with host processes.
61+
62+
## Auto-Detect CLR Version
63+
64+
Rather than require the user to specify the CLR version, we now read the headers of the .NET Assembly to determine the appropriate CLR version.
65+
66+
The .NET Assembly file format is an extension of the regular [PE Format](https://en.wikipedia.org/wiki/Portable_Executable) used by Windows executables. One of the optional fields used by .NET is the ```IMAGE_COR20_HEADER```, which references a ```STORAGESIGNATURE``` structure containing the version details necessary to load the correct runtime. We check the ```iVersionString``` variable to get the exact version requirement for your Assembly. Please note, the names of these data structures and variables are somewhat arbitrary. I am borrowing [dnSpy's](https://github.com/0xd4d/dnSpy) terminology so that I can show you these two pretty pictures.
67+
68+
The relevant layout of the .NET headers in my SafetyKatz DLL as stored on disk:
69+
70+
![_config.yml]({{ site.baseurl }}/images/Apple_Fritter/headers_in_PE.PNG)
71+
72+
And what the ```STORAGESIGNATURE``` structure actually looks like:
73+
74+
![_config.yml]({{ site.baseurl }}/images/Apple_Fritter/structured_headers.PNG)
75+
76+
If you do not want us to automatically determine the version number, you may still manually specify what version to use with the `-r` flag.
77+
78+
79+
## Main Entry Point
80+
81+
The original version of Donut did not handle Main entry points for EXEs well due to the fact that it uses an object array as its function signature rather than a string array. We now correctly handle this so that you don't have to know about the difference. :-)
82+
83+
## AMSI Patching
84+
85+
To provide some context, AMSI integration has been added to the new version of the .NET Framework. It has also been ported to [.NET Core](https://github.com/dotnet/coreclr/issues/21370).
86+
87+
Specifically, AMSI integration was added to the CLR itself so that any .NET Assemblies loaded from memory will be scanned with ```AmsiScanBuffer``` from ```amsi.dll``` before they are loaded. If the result of ```AmsiScanBuffer``` is anything but ```S_OK``` it will return an ```HRESULT``` error code. This affects everything that loads Assemblies from memory using the CLR, including ```System.Reflection.Assembly.Load```, Donut shellcode, and (presumably if I could test it) Cobalt Strike's ```execute-assembly``` command.
88+
89+
When you try to load a .NET Assembly from memory that is known to be malicious, you get a Defender alert that looks like the picture below. Notice that data source was AMSI, and that the process it was running in is ```notepad.exe```. The assembly was injected into notepad through Donut shellcode.
90+
91+
![_config.yml]({{ site.baseurl }}/images/Apple_Fritter/donut_AMSI.PNG)
92+
93+
However, their implementation of AMSII integration is subject to memory patching bypasses in the same way that PowerShell is. We developed on existing research, produced some custom bypasses, and added a modular bypass system to Donut that lets you choose which technique you would like to use.
94+
95+
Odzhan wrote a [blog post](https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/) detailing each of the AMSI bypasses we added to Donut. It is important to note that there could be many more. I believe that anyone who sits down to do the research and develop an AMSI bypass will probably come up with their own slightly different variant. As long as Microsoft continues to rely on calling DLL functions from user-level memory space, AMSI will be subject to memory patching bypasses.
96+
97+
The result looks like the picture below. I safely injected SafetyKatz into ```notepad.exe``` using Donut shellcode, even thought AMSI was used. Defender shows no detections.
98+
99+
![_config.yml]({{ site.baseurl }}/images/Apple_Fritter/amsi_is_dead.PNG)
100+
101+
I must strongly emphasize, the fact that 4.8 AMSI can be bypassed like in PowerShell does NOT make it useless. This new AMSI is a *good thing* that will benefit .NET Security. It incurs cost upon adversaries. Use it. But also recognise that, like everything, it has its limitations.
102+
103+
### Modular Bypass System
104+
105+
As we researched bypasses for AMSI, it became clear that there is many ways to do it. It would be silly to force users of Donut to have to use whatever we came up with. As such, we ensured that you may easily add your own bypass or customize one of ours. The bypasses are defined in ```payload/bypass.c```. You may either modify our C code, or add your own. Each bypass implements the same ```BOOL DisableAMSI(PDONUT_INSTANCE inst)``` function and is wrapped in an ```#ifdef BYPASS_NAME``` preprocessor directive. To change which bypass is used, change the Makefile to define the bypass name specified by the directive.
106+
107+
For example, you could change the relevant line in ```payload/Makefile.msvc``` from
108+
109+
```
110+
cl -DBYPASS_AMSI_A -DBYPASS_WLDP_A -Zp8 -c -nologo -Os -O1 -Gm- -GR- -EHa -Oi -GS- -I ..\include payload.c ..\hash.c ..\encrypt.c bypass.c clib.c
111+
```
112+
113+
To:
114+
115+
```
116+
cl -DBYPASS_AMSI_B -DBYPASS_WLDP_A -Zp8 -c -nologo -Os -O1 -Gm- -GR- -EHa -Oi -GS- -I ..\include payload.c ..\hash.c ..\encrypt.c bypass.c clib.c
117+
```
118+
119+
In order to switch from using BypassA to BypassB.
120+
121+
This system not only makes it easy to change the bypass technique, but also reduces the size, complexity, and signaturability of the shellcode by ensuring that code you are not using is present in the PIC to be found by AV/EDR.
122+
123+
## Device Guard Dynamic Code Prevention Bypass
124+
125+
Windows Defender Device Guard includes an optional policy for disabling dynamically-generated .NET code from executing. Because it was mixed-in with the AMSI scanning code, we went ahead and disabled it too. Not sure if that will help anyone, but hey it was easy. ¯\_(ツ)_
126+
127+
![_config.yml]({{ site.baseurl }}/images/Apple_Fritter/code_integrity.png)
128+
129+
# Conclusion
130+
131+
Donut v0.9.1 "Apple Fritter" represents the first improvements to Donut. More improvements are coming as we have time to make them. In the meantime, Donut is still in Beta so we welcome feedback and testing.
132+
133+
I know that several people have already had difficulties integrating Donut into their toolsets because of the complexity of the data structures it uses. To help with this, our plan for the full release (version 1.0) is to produce C# and Python generators. That will be the primary focus of our efforts moving forward.
134+
135+
## Plans
136+
137+
Below is the current version release plan for Donut.
138+
139+
* v0.9.1:
140+
* Dual-Mode shellcode that can run in either x64 for x86 (WOW64) processes.
141+
* Automatic detection of the CLR version required for .NET Assembly payloads.
142+
* AMSI bypassing for version .NET 4.8 that ensure all Assemblies can be safely loaded.
143+
* Modular system for adding bypasses. Your choide of bypass functionality is compiled into payload.exe based on compiler flags.
144+
* Bypass for Device Guard policy preventing execution of dynamically generated .NET code
145+
* Better handling of Main functions (Entry Points) that use an object array containing string arrays, rather than an array of strings
146+
* v1.0:
147+
* C# generator
148+
* C# wrapper for our dynamic library
149+
* Python generator
150+
* Python wrapper for our dynamic library
151+
* Better documentation for debugging, designing with, and integrating Donut.
152+
* v1.1:
153+
* Automatic unloading of Application Domains after the Assembly finishes executing.
154+
* Support for HTTP proxies

0 commit comments

Comments
 (0)