From 495a4510c9c1a8165d26f215de94d06dac81fb45 Mon Sep 17 00:00:00 2001 From: William Knowles Date: Thu, 17 Jun 2021 20:52:53 +0200 Subject: [PATCH 1/8] Added bofnet_executeassembly for calling entry point on standard .NET assemblies and capturing output --- README.md | 3 +- bofnet.cna | 20 +++++++++- managed/BOFNET/Bofs/ExecuteAssembly.cs | 51 ++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 managed/BOFNET/Bofs/ExecuteAssembly.cs diff --git a/README.md b/README.md index fc082de..d571045 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ bofnet_execute HelloWorld @_EthicalChaos_ | bofnet_jobs | List all currently active BOF.NET jobs | | bofnet_jobstatus *job_id* | Dump any pending console buffer from the background job | | 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_boo *booscript.boo* | Compile and execute Boo script in seperate temporary AppDomain | +| bofnet_executeassembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments | ## Caveats diff --git a/bofnet.cna b/bofnet.cna index 6f91f73..3c4a457 100644 --- a/bofnet.cna +++ b/bofnet.cna @@ -11,7 +11,7 @@ beacon_command_register("bofnet_jobs", "List active BOFNET background jobs", "Sy beacon_command_register("bofnet_jobstatus", "Dump the console buffer of an active BOFNET background job", "Synopsis: bofnet_jobstatus jobid\nDump the console buffer of an active BOFNET background job\n"); 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_executeassembly", "Execute a standard .NET assembly calling the entry point", "Synopsis: bofnet_executeassembly assembly_name arg1 arg2 ...\nExecute a standard .NET assembly calling the entry point and passing all arguments supplied.\n"); sub readAllFileData { @@ -209,4 +209,22 @@ alias bofnet_boo { blog ($1, "Executing script $2 with the following arguments: $scriptArgs"); 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, "Execute a standard .NET assembly"); + beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.ExecuteAssembly ".$2.$bofArguments); + } \ 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..cd07d4c --- /dev/null +++ b/managed/BOFNET/Bofs/ExecuteAssembly.cs @@ -0,0 +1,51 @@ +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) + { + try + { + if (args.Length == 0) + { + BeaconConsole.WriteLine("[!] Cannot continue execution, no .NET assembly specified to run"); + 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); + + // 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"); + } + } + catch (Exception e) + { + BeaconConsole.WriteLine($"[!] BOFNET executed but threw an unhandled exception: {e}"); + } + } + } +} \ No newline at end of file From 79e990b76032dabfaa39f11c05bba137dfbcae24 Mon Sep 17 00:00:00 2001 From: William Knowles Date: Thu, 17 Jun 2021 20:52:53 +0200 Subject: [PATCH 2/8] Added bofnet_executeassembly for calling entry point on standard .NET assemblies and capturing output --- README.md | 1 + bofnet.cna | 20 +++++++++- managed/BOFNET/Bofs/ExecuteAssembly.cs | 51 ++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 managed/BOFNET/Bofs/ExecuteAssembly.cs diff --git a/README.md b/README.md index 239227d..4433221 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ SendHashes(UserHash[] userHashes) | bofnet_jobstatus *job_id* | Dump any pending console buffer from the background job | | 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_executeassembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments | ## Caveats diff --git a/bofnet.cna b/bofnet.cna index 6f91f73..3c4a457 100644 --- a/bofnet.cna +++ b/bofnet.cna @@ -11,7 +11,7 @@ beacon_command_register("bofnet_jobs", "List active BOFNET background jobs", "Sy beacon_command_register("bofnet_jobstatus", "Dump the console buffer of an active BOFNET background job", "Synopsis: bofnet_jobstatus jobid\nDump the console buffer of an active BOFNET background job\n"); 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_executeassembly", "Execute a standard .NET assembly calling the entry point", "Synopsis: bofnet_executeassembly assembly_name arg1 arg2 ...\nExecute a standard .NET assembly calling the entry point and passing all arguments supplied.\n"); sub readAllFileData { @@ -209,4 +209,22 @@ alias bofnet_boo { blog ($1, "Executing script $2 with the following arguments: $scriptArgs"); 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, "Execute a standard .NET assembly"); + beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.ExecuteAssembly ".$2.$bofArguments); + } \ 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..cd07d4c --- /dev/null +++ b/managed/BOFNET/Bofs/ExecuteAssembly.cs @@ -0,0 +1,51 @@ +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) + { + try + { + if (args.Length == 0) + { + BeaconConsole.WriteLine("[!] Cannot continue execution, no .NET assembly specified to run"); + 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); + + // 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"); + } + } + catch (Exception e) + { + BeaconConsole.WriteLine($"[!] BOFNET executed but threw an unhandled exception: {e}"); + } + } + } +} \ No newline at end of file From ed15218e8dd8b6f878684dc0e8c5742dfcea3eee Mon Sep 17 00:00:00 2001 From: William Knowles Date: Sat, 14 Aug 2021 13:32:14 +0200 Subject: [PATCH 3/8] Added stderr capture --- managed/BOFNET/Bofs/ExecuteAssembly.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/managed/BOFNET/Bofs/ExecuteAssembly.cs b/managed/BOFNET/Bofs/ExecuteAssembly.cs index cd07d4c..013625e 100644 --- a/managed/BOFNET/Bofs/ExecuteAssembly.cs +++ b/managed/BOFNET/Bofs/ExecuteAssembly.cs @@ -28,6 +28,7 @@ public override void Go(string[] args) var memStreamWriter = new StreamWriter(memStream); memStreamWriter.AutoFlush = true; Console.SetOut(memStreamWriter); + Console.SetError(memStreamWriter); // Call entry point Assembly assembly = Runtime.LoadedAssemblies[args[0]].Assembly; From cb99e58edc73179d3671a79eb990bf86984ab7aa Mon Sep 17 00:00:00 2001 From: William Knowles Date: Thu, 16 Sep 2021 22:20:09 +0200 Subject: [PATCH 4/8] Added bofnet_patchexit to prevent Environment.Exit() killing Beacon. All credit MDSec (Peter Winter-Smith) --- README.md | 1 + bofnet.cna | 14 ++++- managed/BOFNET/BOFNET.csproj | 9 +++ managed/BOFNET/Bofs/Initializer.cs | 8 ++- managed/BOFNET/Bofs/PatchEnvironmentExit.cs | 21 +++++++ managed/BOFNET/Runtime.cs | 64 +++++++++++++++++++++ 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 managed/BOFNET/Bofs/PatchEnvironmentExit.cs diff --git a/README.md b/README.md index 1bb8690..b4c2ae7 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ 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_executeassembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments | +| bofnet_patchexit | Re-patch .NET's Environment.Exit() to prevent exit. Performed by default during `bofnet_init` but useful if DLLs are unhooked later. | ## Caveats diff --git a/bofnet.cna b/bofnet.cna index a9e2754..09dc2ed 100644 --- a/bofnet.cna +++ b/bofnet.cna @@ -11,7 +11,7 @@ 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_executeassembly", "Execute a standard .NET assembly calling the entry point", "Synopsis: bofnet_executeassembly assembly_name arg1 arg2 ...\nExecute a standard .NET assembly calling the entry point and passing all arguments supplied.\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"); sub readAllFileData { $fileHandle = openf($1); @@ -223,7 +223,17 @@ alias bofnet_executeassembly { $bofArguments = " ".addQuotes(@argParts)."\x00"; } - btask($1, "Execute a standard .NET assembly"); + btask($1, "Executing a standard .NET assembly"); beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.ExecuteAssembly ".$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"); } \ No newline at end of file diff --git a/managed/BOFNET/BOFNET.csproj b/managed/BOFNET/BOFNET.csproj index c118574..6324b1a 100644 --- a/managed/BOFNET/BOFNET.csproj +++ b/managed/BOFNET/BOFNET.csproj @@ -45,6 +45,7 @@ AnyCPU + true @@ -52,6 +53,14 @@ false true + + + true + + + + true + diff --git a/managed/BOFNET/Bofs/Initializer.cs b/managed/BOFNET/Bofs/Initializer.cs index 0a4710c..d9b434d 100644 --- a/managed/BOFNET/Bofs/Initializer.cs +++ b/managed/BOFNET/Bofs/Initializer.cs @@ -8,7 +8,13 @@ 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}"); + if (Runtime.PatchEnvironmentExit()) { + BeaconConsole.WriteLine($"[+] Environment.Exit() patched successfully"); + } + else { + BeaconConsole.WriteLine($"[!] Environment.Exit() patched failed"); + } } } } diff --git a/managed/BOFNET/Bofs/PatchEnvironmentExit.cs b/managed/BOFNET/Bofs/PatchEnvironmentExit.cs new file mode 100644 index 0000000..3328c41 --- /dev/null +++ b/managed/BOFNET/Bofs/PatchEnvironmentExit.cs @@ -0,0 +1,21 @@ +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..82b8dd8 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 @@ -173,5 +209,33 @@ public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEvent return null; } } + + public static bool PatchEnvironmentExit() + { + // Credit 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; + } } } From 948406f370ce95906f292f185dd14edf8ce59714 Mon Sep 17 00:00:00 2001 From: William Knowles Date: Sat, 18 Sep 2021 19:25:49 +0200 Subject: [PATCH 5/8] (1) Added bofnet_job_assembly; (2) renamed execute method to bofnet_execute_assembly; (3) tidied code for Exit() patch --- README.md | 3 +- bofnet.cna | 22 ++- managed/BOFNET/BeaconJob.cs | 150 +++++++++++++++++- managed/BOFNET/Bofs/ExecuteAssembly.cs | 62 ++++---- managed/BOFNET/Bofs/Initializer.cs | 6 - managed/BOFNET/Bofs/Jobs/JobRunnerAssembly.cs | 40 +++++ managed/BOFNET/Bofs/ListAssemblies.cs | 2 +- managed/BOFNET/Bofs/PatchEnvironmentExit.cs | 9 +- managed/BOFNET/Runtime.cs | 19 +-- 9 files changed, 245 insertions(+), 68 deletions(-) create mode 100644 managed/BOFNET/Bofs/Jobs/JobRunnerAssembly.cs diff --git a/README.md b/README.md index b4c2ae7..d11ac09 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,8 @@ SendHashes(UserHash[] userHashes) | bofnet_jobstatus *job_id* | Dump any pending console buffer from the background job | | 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_executeassembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments | +| bofnet_execute_assembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments. Blocks Beacon until completion. | +| bofnet_job_assembly *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. | ## Caveats diff --git a/bofnet.cna b/bofnet.cna index 09dc2ed..b6b9f50 100644 --- a/bofnet.cna +++ b/bofnet.cna @@ -10,7 +10,8 @@ beacon_command_register("bofnet_jobs", "List active BOFNET background jobs", "Sy beacon_command_register("bofnet_jobstatus", "Dump the console buffer of an active BOFNET background job", "Synopsis: bofnet_jobstatus jobid\nDump the console buffer of an active BOFNET background job\n"); 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_executeassembly", "Execute a standard .NET assembly calling the entry point", "Synopsis: bofnet_executeassembly assembly_name arg1 arg2 ...\nExecute a standard .NET assembly calling the entry point and passing all arguments supplied.\n"); +beacon_command_register("bofnet_execute_assembly", "Execute a standard .NET assembly calling the entry point (blocks until completion)", "Synopsis: bofnet_execute_assembly 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_job_assembly", "Execute a standard .NET assembly calling the entry point (as a background job)", "Synopsis: bofnet_job_assembly 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"); sub readAllFileData { @@ -80,6 +81,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 { @@ -210,7 +212,7 @@ alias bofnet_boo { bofnet_execute_raw($1, "BOFNET.Bofs.Boo.BooRunner", $args); } -alias bofnet_executeassembly { +alias bofnet_execute_assembly { $bofnetNative = loadBOFNativeRuntime($1); if($bofnetNative != $null){ @@ -225,7 +227,23 @@ alias bofnet_executeassembly { btask($1, "Executing a standard .NET assembly"); beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.ExecuteAssembly ".$2.$bofArguments); +} +alias bofnet_job_assembly { + + $bofnetNative = loadBOFNativeRuntime($1); + if($bofnetNative != $null){ + return; + } + $bofArguments = "\x00"; + + @argParts = sublist(@_,2); + if(size(@argParts) > 0){ + $bofArguments = " ".addQuotes(@argParts)."\x00"; + } + + btask($1, "Executing a standard .NET assembly"); + beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.Jobs.JobRunnerAssembly ".$2.$bofArguments); } alias bofnet_patchexit { 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 index 013625e..29c9dd7 100644 --- a/managed/BOFNET/Bofs/ExecuteAssembly.cs +++ b/managed/BOFNET/Bofs/ExecuteAssembly.cs @@ -5,47 +5,39 @@ using System.Reflection; using System.Text; -namespace BOFNET.Bofs -{ - public class ExecuteAssembly : BeaconObject - { +namespace BOFNET.Bofs { + public class ExecuteAssembly : BeaconObject { public ExecuteAssembly(BeaconApi api) : base(api) { } - public override void Go(string[] args) - { - try - { - if (args.Length == 0) - { - BeaconConsole.WriteLine("[!] Cannot continue execution, no .NET assembly specified to run"); - return; - } + 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); + 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); + // 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"); - } + // Write MemoryStream to Beacon output + BeaconConsole.WriteLine(Encoding.ASCII.GetString(memStream.ToArray())); } - catch (Exception e) - { - BeaconConsole.WriteLine($"[!] BOFNET executed but threw an unhandled exception: {e}"); + else { + BeaconConsole.WriteLine("[!] Cannot continue execution, specified .NET assembly not loaded"); } } } diff --git a/managed/BOFNET/Bofs/Initializer.cs b/managed/BOFNET/Bofs/Initializer.cs index d9b434d..7759893 100644 --- a/managed/BOFNET/Bofs/Initializer.cs +++ b/managed/BOFNET/Bofs/Initializer.cs @@ -9,12 +9,6 @@ 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}"); - if (Runtime.PatchEnvironmentExit()) { - BeaconConsole.WriteLine($"[+] Environment.Exit() patched successfully"); - } - else { - BeaconConsole.WriteLine($"[!] Environment.Exit() patched failed"); - } } } } 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 index 3328c41..ab9873a 100644 --- a/managed/BOFNET/Bofs/PatchEnvironmentExit.cs +++ b/managed/BOFNET/Bofs/PatchEnvironmentExit.cs @@ -3,13 +3,10 @@ using System.Linq; using System.Text; -namespace BOFNET.Bofs -{ - public class PatchEnvironmentExit : BeaconObject - { +namespace BOFNET.Bofs { + public class PatchEnvironmentExit : BeaconObject { public PatchEnvironmentExit(BeaconApi api) : base(api) { } - public override void Go(string[] args) - { + public override void Go(string[] args) { if (Runtime.PatchEnvironmentExit()) { BeaconConsole.WriteLine($"[+] Environment.Exit() patched successfully"); } diff --git a/managed/BOFNET/Runtime.cs b/managed/BOFNET/Runtime.cs index 82b8dd8..91fd368 100644 --- a/managed/BOFNET/Runtime.cs +++ b/managed/BOFNET/Runtime.cs @@ -116,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; } @@ -212,22 +211,18 @@ public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEvent public static bool PatchEnvironmentExit() { - // Credit MDSec: https://www.mdsec.co.uk/2020/08/massaging-your-clr-preventing-environment-exit-in-in-process-net-assemblies/ + // 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 - { + 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) - { + 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)) - { + 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; From 3acf40304db969b75b847e8ecd1ba0448a81d151 Mon Sep 17 00:00:00 2001 From: William Knowles Date: Sat, 18 Sep 2021 19:42:22 +0200 Subject: [PATCH 6/8] Adjusted naming convention to match existing BOF.NET commands --- README.md | 4 ++-- bofnet.cna | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d11ac09..1794519 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ SendHashes(UserHash[] userHashes) | bofnet_jobstatus *job_id* | Dump any pending console buffer from the background job | | 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_execute_assembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments. Blocks Beacon until completion. | -| bofnet_job_assembly *assembly_name* [*args*] | Execute a standard .NET assembly calling the entry point, supplying optional arguments. Runs as a background job (two threads). | +| 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. | ## Caveats diff --git a/bofnet.cna b/bofnet.cna index b6b9f50..75c420a 100644 --- a/bofnet.cna +++ b/bofnet.cna @@ -10,8 +10,8 @@ beacon_command_register("bofnet_jobs", "List active BOFNET background jobs", "Sy beacon_command_register("bofnet_jobstatus", "Dump the console buffer of an active BOFNET background job", "Synopsis: bofnet_jobstatus jobid\nDump the console buffer of an active BOFNET background job\n"); 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_execute_assembly", "Execute a standard .NET assembly calling the entry point (blocks until completion)", "Synopsis: bofnet_execute_assembly 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_job_assembly", "Execute a standard .NET assembly calling the entry point (as a background job)", "Synopsis: bofnet_job_assembly 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_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"); sub readAllFileData { @@ -212,7 +212,7 @@ alias bofnet_boo { bofnet_execute_raw($1, "BOFNET.Bofs.Boo.BooRunner", $args); } -alias bofnet_execute_assembly { +alias bofnet_executeassembly { $bofnetNative = loadBOFNativeRuntime($1); if($bofnetNative != $null){ @@ -225,11 +225,11 @@ alias bofnet_execute_assembly { $bofArguments = " ".addQuotes(@argParts)."\x00"; } - btask($1, "Executing a standard .NET assembly"); + btask($1, "Attempting to start .NET assembly in blocking mode"); beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.ExecuteAssembly ".$2.$bofArguments); } -alias bofnet_job_assembly { +alias bofnet_jobassembly { $bofnetNative = loadBOFNativeRuntime($1); if($bofnetNative != $null){ @@ -242,7 +242,7 @@ alias bofnet_job_assembly { $bofArguments = " ".addQuotes(@argParts)."\x00"; } - btask($1, "Executing a standard .NET assembly"); + btask($1, "Attempting to start .NET assembly as a job"); beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.Jobs.JobRunnerAssembly ".$2.$bofArguments); } From e187803f8fba0f266afaa4b0eda1fd00be8b78ca Mon Sep 17 00:00:00 2001 From: EspressoCake Date: Sun, 21 May 2023 22:06:34 -0400 Subject: [PATCH 7/8] Add internal needle to determine buffer length to truncate null bytes. --- managed/BOFNET/BeaconConsoleWriter.cs | 45 +++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) 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); } } From 5ccfa83fa1d3a33c40ff303af7831b92d27c0d59 Mon Sep 17 00:00:00 2001 From: wk Date: Tue, 2 Jan 2024 19:39:09 +0300 Subject: [PATCH 8/8] fix: missed a merge conflict --- bofnet.cna | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bofnet.cna b/bofnet.cna index f3755bf..e0064ac 100644 --- a/bofnet.cna +++ b/bofnet.cna @@ -215,7 +215,6 @@ alias bofnet_boo { bofnet_execute_raw($1, "BOFNET.Bofs.Boo.BooRunner", $args); } -<<<<<<< HEAD alias bofnet_executeassembly { $bofnetNative = loadBOFNativeRuntime($1); @@ -259,7 +258,7 @@ alias bofnet_patchexit { btask($1, "Re-patching .NET Environment.Exit()"); beacon_inline_execute($1, $bofnetNative, "go", "BOFNET.Bofs.PatchEnvironmentExit\x00"); } -======= + alias bofnet_vfs_add{ local('$fileData $args'); @@ -273,4 +272,3 @@ alias bofnet_vfs_add{ blog($1, "Attempting to host file $2 (" . strlen($fileData) . " bytes) inside the BOFNET VFS"); bofnet_execute_raw($1, "BOFNET.Bofs.VFS", $args); } ->>>>>>> upstream/main