diff --git a/README.md b/README.md
index f9f079c..6209959 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,9 @@ SendHashes(UserHash[] userHashes)
| bofnet_jobkill *job_id* | Dump any pending console buffer from the background job then kill it. Warning, can cause deadlocks when terminating a thread that have transitioned into native code |
| bofnet_boo *booscript.boo* | Compile and execute Boo script in seperate temporary AppDomain |
| bofnet_vfs_add *local_path* *vfs_filename* *content_type* | Add a file from the operator machine and store inside the BOFNET VFS |
+| bofnet_executeassembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments. Blocks Beacon until completion. |
+| bofnet_jobassembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments. Runs as a background job (two threads). |
+| bofnet_patchexit | Re-patch .NET's Environment.Exit() to prevent exit. Performed by default during `bofnet_init` but useful if DLLs are unhooked later. |
## Built-in BOFS
@@ -213,4 +216,4 @@ Once the steps are complete, the `build\dist` folder should contain the artifact
## References
* https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/ - CLR creation using native raw COM interfaces
-* https://gist.github.com/sysenter-eip/1a985a224c67aa78f62be83f190b6e86 - Great trick for declaring BOF imports
+* https://gist.github.com/sysenter-eip/1a985a224c67aa78f62be83f190b6e86 - Great trick for declaring BOF imports
\ No newline at end of file
diff --git a/bofnet.cna b/bofnet.cna
index c5bfcce..e0064ac 100644
--- a/bofnet.cna
+++ b/bofnet.cna
@@ -11,7 +11,9 @@ beacon_command_register("bofnet_jobstatus", "Dump the console buffer of an activ
beacon_command_register("bofnet_jobkill", "Kills a running jobs thread (warning, could leave leaked resources/sockets behind", "Synopsis: bofnet_jobkill jobid\nKills a running jobs thread (warning, could leave leaked resources/sockets behind\n");
beacon_command_register("bofnet_boo", "Runs a Boo script in a temporary AppDomain which is then unloaded", "Synopsis: bofnet_boo filename.boo\nRuns a Boo script in a temporary AppDomain which is then unloaded\n");
beacon_command_register("bofnet_vfs_add", "Uploads a file to the in memory VFS storage", "Synopsis: bofnet_vfs_add local_path filename content_type\Uploads a file to the in memory VFS store\n");
-
+beacon_command_register("bofnet_executeassembly", "Execute a standard .NET assembly calling the entry point (blocks until completion)", "Synopsis: bofnet_executeassembly assembly_name arg1 arg2 ...\nExecute a standard .NET assembly calling the entry point and passing all arguments supplied (blocks until completion)\n");
+beacon_command_register("bofnet_jobassembly", "Execute a standard .NET assembly calling the entry point (as a background job)", "Synopsis: bofnet_jobassembly assembly_name arg1 arg2 ...\nExecute a standard .NET assembly calling the entry point and passing all arguments supplied (as a background job)\n");
+beacon_command_register("bofnet_patchexit", "Re-patch .NET's Environment.Exit() to prevent exit", "Synopsis: bofnet_patchexit \nRe-patch .NET's Environment.Exit() to prevent exit");
@@ -82,6 +84,7 @@ alias bofnet_init {
btask($1, "Initializing BOFNET");
beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.Initializer\x00".$bofnetRuntime);
+ beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.PatchEnvironmentExit\x00");
}
alias bofnet_shutdown {
@@ -212,6 +215,50 @@ alias bofnet_boo {
bofnet_execute_raw($1, "BOFNET.Bofs.Boo.BooRunner", $args);
}
+alias bofnet_executeassembly {
+
+ $bofnetNative = loadBOFNativeRuntime($1);
+ if($bofnetNative != $null){
+ return;
+ }
+ $bofArguments = "\x00";
+
+ @argParts = sublist(@_,2);
+ if(size(@argParts) > 0){
+ $bofArguments = " ".addQuotes(@argParts)."\x00";
+ }
+
+ btask($1, "Attempting to start .NET assembly in blocking mode");
+ beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.ExecuteAssembly ".$2.$bofArguments);
+}
+
+alias bofnet_jobassembly {
+
+ $bofnetNative = loadBOFNativeRuntime($1);
+ if($bofnetNative != $null){
+ return;
+ }
+ $bofArguments = "\x00";
+
+ @argParts = sublist(@_,2);
+ if(size(@argParts) > 0){
+ $bofArguments = " ".addQuotes(@argParts)."\x00";
+ }
+
+ btask($1, "Attempting to start .NET assembly as a job");
+ beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.Jobs.JobRunnerAssembly ".$2.$bofArguments);
+}
+
+alias bofnet_patchexit {
+ $bofnetNative = loadBOFNativeRuntime($1);
+ if($bofnetNative != $null){
+ return;
+ }
+
+ btask($1, "Re-patching .NET Environment.Exit()");
+ beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.PatchEnvironmentExit\x00");
+}
+
alias bofnet_vfs_add{
local('$fileData $args');
diff --git a/managed/BOFNET/BOFNET.csproj b/managed/BOFNET/BOFNET.csproj
index 080d927..e28eb5c 100644
--- a/managed/BOFNET/BOFNET.csproj
+++ b/managed/BOFNET/BOFNET.csproj
@@ -45,6 +45,7 @@
AnyCPU
+ true
@@ -56,6 +57,14 @@
false
true
+
+
+ true
+
+
+
+ true
+
diff --git a/managed/BOFNET/BeaconConsoleWriter.cs b/managed/BOFNET/BeaconConsoleWriter.cs
index d637780..4361c88 100644
--- a/managed/BOFNET/BeaconConsoleWriter.cs
+++ b/managed/BOFNET/BeaconConsoleWriter.cs
@@ -32,12 +32,53 @@ public override void Write(byte[] buffer, int offset, int count) {
public override void Flush() {
base.Flush();
-
+
if (Position > 0 && beaconCallbackWriter != null && ownerThread == Thread.CurrentThread) {
byte[] data = new byte[Position];
Seek(0, SeekOrigin.Begin);
Read(data, 0, data.Length);
- beaconCallbackWriter(OutputTypes.CALLBACK_OUTPUT_UTF8, data, data.Length);
+
+ bool currentSeekHit = false;
+ int stringIndex = 0;
+ for (int index = 0; index < (data.Length - 2); index++)
+ {
+ if (currentSeekHit != true)
+ {
+ /*
+ * Logical to check for sequential null values that go beyond our buffer length.
+ * Due to representation of null bytes in a UTF-8 callback, this can otherwise lead to "phantom" output which is the flushing of an allocated MemoryStream object.
+ *
+ * As such, a "beacon operator" may receive multiple instances of "received output" from the callback.
+ * We implement our own needle here, assuming in both UTF-8 and UTF-16 arrays that sequential null bytes represent the termination of any string.
+ *
+ * This prevents both pollution of output, and cleanliness of logs.
+ * All instances of the base class are still properly flushed at the end of our operations with a call the the base class' Dispose method.
+ */
+ if ((data[index] == (byte)0x00) && (data[index+1] == (byte)0x00))
+ {
+ stringIndex = index + 1;
+ currentSeekHit = true;
+ }
+ else
+ {
+ stringIndex += 1;
+ }
+ }
+ }
+
+ if (currentSeekHit != true)
+ {
+ beaconCallbackWriter(OutputTypes.CALLBACK_OUTPUT_UTF8, data, data.Length);
+ }
+ else
+ {
+ if (stringIndex >= 2)
+ {
+ beaconCallbackWriter(OutputTypes.CALLBACK_OUTPUT_UTF8, data, stringIndex);
+ }
+ }
+
+ // Regardless of output, seek to the beginning of the MemoryStream
Seek(0, SeekOrigin.Begin);
}
}
diff --git a/managed/BOFNET/BeaconJob.cs b/managed/BOFNET/BeaconJob.cs
index ca32289..15fa4aa 100644
--- a/managed/BOFNET/BeaconJob.cs
+++ b/managed/BOFNET/BeaconJob.cs
@@ -1,5 +1,7 @@
using System;
+using System.IO;
+using System.Text;
using System.Threading;
namespace BOFNET {
@@ -11,8 +13,16 @@ public class BeaconJob {
public BeaconJobWriter BeaconConsole { get; private set; }
- public BeaconJob(BeaconObject bo, string[] args, BeaconJobWriter beaconTaskWriter) {
+ public string StandardAssembly { get; private set; }
+ string StandardAssemblyEntryPointType { get; set; }
+
+ volatile static ProducerConsumerStream MemoryStreamPC = new ProducerConsumerStream();
+
+ volatile static bool RunThread;
+
+ public BeaconJob(BeaconObject bo, string[] args, BeaconJobWriter beaconTaskWriter, string standardAssembly = null) {
+ StandardAssembly = standardAssembly;
BeaconConsole = beaconTaskWriter;
BeaconObject = bo;
Thread = new Thread(new ParameterizedThreadStart(this.DoTask));
@@ -22,15 +32,145 @@ public BeaconJob(BeaconObject bo, string[] args, BeaconJobWriter beaconTaskWrite
private void DoTask(object args) {
try {
if (args is string[] stringArgs) {
- BeaconObject.Go(stringArgs);
+ if (StandardAssembly != null) {
+ // Redirect stdout to MemoryStream
+ StreamWriter memoryStreamWriter = new StreamWriter(MemoryStreamPC);
+ memoryStreamWriter.AutoFlush = true;
+ Console.SetOut(memoryStreamWriter);
+ Console.SetError(memoryStreamWriter);
+
+ // Start thread to check MemoryStream to send data to Beacon
+ RunThread = true;
+ Thread runtimeWriteLine = new Thread(() => RuntimeWriteLine(BeaconConsole));
+ runtimeWriteLine.Start();
+
+ // Run main program passing original arguments
+ object[] mainArguments = new[] { stringArgs };
+ StandardAssemblyEntryPointType = Runtime.LoadedAssemblies[StandardAssembly].Assembly.EntryPoint.DeclaringType.ToString();
+ Runtime.LoadedAssemblies[StandardAssembly].Assembly.EntryPoint.Invoke(null, mainArguments);
+
+ // Trigger safe exit of thread, ensuring MemoryStream is emptied too
+ RunThread = false;
+ runtimeWriteLine.Join();
+ }
+ else {
+ BeaconObject.Go(stringArgs);
+ }
}
- }catch(Exception e) {
+ } catch(Exception e) {
BeaconConsole.WriteLine($"Job execution failed with exception:\n{e}");
}
}
public override string ToString() {
- return $"Type: {BeaconObject.GetType().Name}, Id: {Thread.ManagedThreadId}, Active: {Thread.IsAlive}, Console Data: {BeaconConsole.HasData}";
+ return $"Type: {(StandardAssembly != null ? StandardAssemblyEntryPointType : BeaconObject.GetType().FullName).ToString()}, Id: {Thread.ManagedThreadId}, Standard Assembly: {(StandardAssembly != null ? true : false).ToString()}, Active: {Thread.IsAlive}, Console Data: {BeaconConsole.HasData}";
+ }
+
+ public static void RuntimeWriteLine(BeaconJobWriter beaconconsole) {
+ bool LastCheck = false;
+ while (RunThread == true || LastCheck == true) {
+ int offsetWritten = 0;
+ int currentCycleMemoryStreamLength = Convert.ToInt32(MemoryStreamPC.Length);
+ if (currentCycleMemoryStreamLength > offsetWritten) {
+ try {
+ var byteArrayRaw = new byte[currentCycleMemoryStreamLength];
+ int count = MemoryStreamPC.Read(byteArrayRaw, offsetWritten, currentCycleMemoryStreamLength);
+
+ if (count > 0) {
+ // Need to stop at last new line otherwise it will run into encoding errors in the Beacon logs.
+ int lastNewLine = 0;
+ for (int i = 0; i < byteArrayRaw.Length; i++) {
+ if (byteArrayRaw[i] == '\n') {
+ lastNewLine = i;
+ }
+ }
+ if (LastCheck) {
+ // If last run ensure all remaining MemoryStream data is obtained.
+ lastNewLine = currentCycleMemoryStreamLength;
+ }
+ if (lastNewLine > 0) {
+ var byteArrayToLastNewline = new byte[lastNewLine];
+ Buffer.BlockCopy(byteArrayRaw, 0, byteArrayToLastNewline, 0, lastNewLine);
+ beaconconsole.WriteLine(Encoding.ASCII.GetString(byteArrayToLastNewline));
+ offsetWritten = offsetWritten + lastNewLine;
+ }
+ }
+ }
+ catch (Exception e) {
+ beaconconsole.WriteLine($"[!] BOFNET threw an exception when returning captured console output: {e}");
+ }
+ }
+ Thread.Sleep(50);
+ if (LastCheck) {
+ break;
+ }
+ if (RunThread == false && LastCheck == false) {
+ LastCheck = true;
+ }
+ }
+ }
+
+ // Code taken from Polity at: https://stackoverflow.com/questions/12328245/memorystream-have-one-thread-write-to-it-and-another-read
+ // Provides means to have multiple threads reading and writing from and to the same MemoryStream
+ public class ProducerConsumerStream : Stream {
+ private readonly MemoryStream innerStream;
+ private long readPosition;
+ private long writePosition;
+
+ public ProducerConsumerStream() {
+ innerStream = new MemoryStream();
+ }
+
+ public override bool CanRead { get { return true; } }
+
+ public override bool CanSeek { get { return false; } }
+
+ public override bool CanWrite { get { return true; } }
+
+ public override void Flush() {
+ lock (innerStream) {
+ innerStream.Flush();
+ }
+ }
+
+ public override long Length {
+ get {
+ lock (innerStream) {
+ return innerStream.Length;
+ }
+ }
+ }
+
+ public override long Position {
+ get { throw new NotSupportedException(); }
+ set { throw new NotSupportedException(); }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count) {
+ lock (innerStream) {
+ innerStream.Position = readPosition;
+ int red = innerStream.Read(buffer, offset, count);
+ readPosition = innerStream.Position;
+
+ return red;
+ }
+ }
+
+ public override long Seek(long offset, SeekOrigin origin) {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value) {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count) {
+ lock (innerStream) {
+ innerStream.Position = writePosition;
+ innerStream.Write(buffer, offset, count);
+ writePosition = innerStream.Position;
+ }
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/managed/BOFNET/Bofs/ExecuteAssembly.cs b/managed/BOFNET/Bofs/ExecuteAssembly.cs
new file mode 100644
index 0000000..29c9dd7
--- /dev/null
+++ b/managed/BOFNET/Bofs/ExecuteAssembly.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace BOFNET.Bofs {
+ public class ExecuteAssembly : BeaconObject {
+ public ExecuteAssembly(BeaconApi api) : base(api) { }
+
+ public override void Go(string[] args) {
+ if (args.Length == 0) {
+ BeaconConsole.WriteLine("[!] Cannot continue execution, no .NET assembly specified to run");
+ return;
+ }
+
+ if (Runtime.Jobs.Values.Where(p => p.StandardAssembly != null && p.Thread.IsAlive).Count() > 0) {
+ BeaconConsole.WriteLine("[!] Cannot continue execution, unable to execute while a bofnet_job_assembly instance is active");
+ return;
+ }
+
+ if (Runtime.LoadedAssemblies.ContainsKey(args[0])) {
+ // Redirect stdout to MemoryStream
+ var memStream = new MemoryStream();
+ var memStreamWriter = new StreamWriter(memStream);
+ memStreamWriter.AutoFlush = true;
+ Console.SetOut(memStreamWriter);
+ Console.SetError(memStreamWriter);
+
+ // Call entry point
+ Assembly assembly = Runtime.LoadedAssemblies[args[0]].Assembly;
+ object[] mainArguments = new[] { args.Skip(1).ToArray() };
+ object execute = assembly.EntryPoint.Invoke(null, mainArguments);
+
+ // Write MemoryStream to Beacon output
+ BeaconConsole.WriteLine(Encoding.ASCII.GetString(memStream.ToArray()));
+ }
+ else {
+ BeaconConsole.WriteLine("[!] Cannot continue execution, specified .NET assembly not loaded");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/managed/BOFNET/Bofs/Initializer.cs b/managed/BOFNET/Bofs/Initializer.cs
index 0a4710c..7759893 100644
--- a/managed/BOFNET/Bofs/Initializer.cs
+++ b/managed/BOFNET/Bofs/Initializer.cs
@@ -8,7 +8,7 @@ public Initializer(BeaconApi api) : base(api) { }
public override void Go(byte[] assemblyData) {
Runtime.RegisterRuntimeAssembly(assemblyData);
- BeaconConsole.WriteLine($"[+] BOFNET Runtime Initalized, assembly size {assemblyData.Length}, .NET Runtime Version: {Environment.Version} in AppDomain {AppDomain.CurrentDomain.FriendlyName}");
+ BeaconConsole.WriteLine($"[+] BOFNET Runtime Initalized, assembly size {assemblyData.Length}, .NET Runtime Version: {Environment.Version} in AppDomain {AppDomain.CurrentDomain.FriendlyName}");
}
}
}
diff --git a/managed/BOFNET/Bofs/Jobs/JobRunnerAssembly.cs b/managed/BOFNET/Bofs/Jobs/JobRunnerAssembly.cs
new file mode 100644
index 0000000..7f7175b
--- /dev/null
+++ b/managed/BOFNET/Bofs/Jobs/JobRunnerAssembly.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace BOFNET.Bofs.Jobs {
+ public class JobRunnerAssembly : BeaconObject {
+ public JobRunnerAssembly(BeaconApi api) : base(api) {
+ }
+
+ public AppDomain LoadAssemblyInAppDomain(string appDomain, byte[] data, int len) {
+ return null;
+ }
+
+ public override void Go(string[] args) {
+ if (args.Length == 0) {
+ BeaconConsole.WriteLine("[!] Cannot continue execution, no .NET assembly specified to run");
+ return;
+ }
+
+ if (Runtime.Jobs.Values.Where(p => p.StandardAssembly != null && p.Thread.IsAlive).Count() > 0) {
+ BeaconConsole.WriteLine("[!] Cannot continue execution, multiple instances of bofnet_job_assembly cannot run in parallel");
+ return;
+ }
+
+ if (Runtime.LoadedAssemblies.ContainsKey(args[0])) {
+ BeaconJobWriter btw = new BeaconJobWriter();
+ BeaconJob beaconJob = new BeaconJob(null, args.Skip(1).ToArray(), btw, args[0]);
+
+ Runtime.Jobs[beaconJob.Thread.ManagedThreadId] = beaconJob;
+ BeaconConsole.WriteLine($"[+] Started Task {args[0]} with job id {beaconJob.Thread.ManagedThreadId}");
+ }
+ else {
+ BeaconConsole.WriteLine("[!] Cannot continue execution, specified .NET assembly not loaded");
+ }
+ }
+ }
+}
diff --git a/managed/BOFNET/Bofs/ListAssemblies.cs b/managed/BOFNET/Bofs/ListAssemblies.cs
index 2183bf7..3193718 100644
--- a/managed/BOFNET/Bofs/ListAssemblies.cs
+++ b/managed/BOFNET/Bofs/ListAssemblies.cs
@@ -11,7 +11,7 @@ public ListAssemblies(BeaconApi api) : base(api) {}
public override void Go(string[] _) {
foreach(KeyValuePair assembly in Runtime.LoadedAssemblies) {
BeaconConsole.WriteLine($"{assembly.Key}: {assembly.Value.Assembly.FullName}");
- }
+ }
}
}
}
diff --git a/managed/BOFNET/Bofs/PatchEnvironmentExit.cs b/managed/BOFNET/Bofs/PatchEnvironmentExit.cs
new file mode 100644
index 0000000..ab9873a
--- /dev/null
+++ b/managed/BOFNET/Bofs/PatchEnvironmentExit.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BOFNET.Bofs {
+ public class PatchEnvironmentExit : BeaconObject {
+ public PatchEnvironmentExit(BeaconApi api) : base(api) { }
+ public override void Go(string[] args) {
+ if (Runtime.PatchEnvironmentExit()) {
+ BeaconConsole.WriteLine($"[+] Environment.Exit() patched successfully");
+ }
+ else {
+ BeaconConsole.WriteLine($"[!] Environment.Exit() patched failed");
+ }
+ }
+ }
+}
diff --git a/managed/BOFNET/Runtime.cs b/managed/BOFNET/Runtime.cs
index 5ad1a91..91fd368 100644
--- a/managed/BOFNET/Runtime.cs
+++ b/managed/BOFNET/Runtime.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -23,10 +24,45 @@ public class AssemblyInfo {
static bool firstInit = true;
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MEMORY_BASIC_INFORMATION
+ {
+ public IntPtr BaseAddress;
+ public IntPtr AllocationBase;
+ public uint AllocationProtect;
+ public IntPtr RegionSize;
+ public uint State;
+ public uint Protect;
+ public uint Type;
+ }
+
+ public enum AllocationProtect : uint
+ {
+ PAGE_EXECUTE = 0x00000010,
+ PAGE_EXECUTE_READ = 0x00000020,
+ PAGE_EXECUTE_READWRITE = 0x00000040,
+ PAGE_EXECUTE_WRITECOPY = 0x00000080,
+ PAGE_NOACCESS = 0x00000001,
+ PAGE_READONLY = 0x00000002,
+ PAGE_READWRITE = 0x00000004,
+ PAGE_WRITECOPY = 0x00000008,
+ PAGE_GUARD = 0x00000100,
+ PAGE_NOCACHE = 0x00000200,
+ PAGE_WRITECOMBINE = 0x00000400
+ }
+
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress,
+ UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress,
+ out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);
+
private static Type FindType(string name) {
//Try to get based on fully qualified name first
@@ -80,11 +116,10 @@ public static Assembly LoadAssembly(byte[] assemblyData) {
public static BeaconObject CreateBeaconObject(string bofName, BeaconOutputWriter bow, InitialiseChildBOFNETAppDomain initialiseChildBOFNETAppDomain, BeaconUseToken beaconUseToken, BeaconRevertToken beaconRevertToken, BeaconCallbackWriter beaconCallbackWriter) {
Type bofType = FindType(bofName);
-
- if (bofType == null) {
+ if (bofType == null)
+ {
throw new TypeLoadException($"[!] Failed to find type {bofName} within BOFNET AppDomain, have you loaded the containing assembly yet?");
}
-
BeaconObject bo = (BeaconObject)Activator.CreateInstance(bofType, new object[] { new DefaultBeaconApi(bow, initialiseChildBOFNETAppDomain, beaconUseToken, beaconRevertToken, beaconCallbackWriter) });
return bo;
}
@@ -173,5 +208,29 @@ public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEvent
return null;
}
}
+
+ public static bool PatchEnvironmentExit()
+ {
+ // Credit Peter Winter-Smith @ MDSec: https://www.mdsec.co.uk/2020/08/massaging-your-clr-preventing-environment-exit-in-in-process-net-assemblies/
+ var methods = new List(typeof(Environment).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
+ var exitMethod = methods.Find((MethodInfo mi) => mi.Name == "Exit");
+ RuntimeHelpers.PrepareMethod(exitMethod.MethodHandle);
+ var exitMethodPtr = exitMethod.MethodHandle.GetFunctionPointer();
+ unsafe {
+ IntPtr target = exitMethod.MethodHandle.GetFunctionPointer();
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQueryEx((IntPtr)(-1), target, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0) {
+ if (mbi.Protect == (uint)AllocationProtect.PAGE_EXECUTE_READ) {
+ uint flOldProtect;
+ if (VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (UIntPtr)1, (uint)AllocationProtect.PAGE_EXECUTE_READWRITE, out flOldProtect)) {
+ *(byte*)target = 0xc3; // ret
+ VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (UIntPtr)1, flOldProtect, out flOldProtect);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
}
}