From 398dda48f5ed119302777790d294c37479c163e8 Mon Sep 17 00:00:00 2001 From: its-a-feature Date: Wed, 8 Jan 2025 14:18:04 -0800 Subject: [PATCH] ticket_store bug fixes --- Payload_Type/apollo/CHANGELOG.MD | 6 + .../KerberosTickets/KerberosHelpers.cs | 131 +++++++++--------- .../KerberosTickets/ticket_store_add.cs | 4 +- .../apollo/mythic/agent_functions/builder.py | 2 +- .../apollo/mythic/agent_functions/inject.py | 1 + 5 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Payload_Type/apollo/CHANGELOG.MD b/Payload_Type/apollo/CHANGELOG.MD index 1d98bf75..eabd2ebd 100644 --- a/Payload_Type/apollo/CHANGELOG.MD +++ b/Payload_Type/apollo/CHANGELOG.MD @@ -4,6 +4,12 @@ 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.24] - 2025-01-08 + +### Changed + +- Updated the KerberosTicket storage to handle fetching the right ticket more reliably + ## [v2.2.23] - 2025-01-08 ### Changed diff --git a/Payload_Type/apollo/apollo/agent_code/KerberosTickets/KerberosHelpers.cs b/Payload_Type/apollo/apollo/agent_code/KerberosTickets/KerberosHelpers.cs index 93a9929e..79c32d13 100644 --- a/Payload_Type/apollo/apollo/agent_code/KerberosTickets/KerberosHelpers.cs +++ b/Payload_Type/apollo/apollo/agent_code/KerberosTickets/KerberosHelpers.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Sockets; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; @@ -20,12 +21,12 @@ namespace KerberosTickets; internal class KerberosHelpers { private static HANDLE systemHandle { get; set; } - + private static List createdArtifacts = new List(); - - - - + + + + //private helper methods private static HANDLE GetLsaHandleUntrusted(bool elevateToSystem = true) { @@ -88,38 +89,30 @@ private static uint GetAuthPackage(HANDLE lsaHandle, HANDLE packa return authPackage; } - + private static IEnumerable GetLogonSessions() { List logonIds = []; try { - if (Agent.GetIdentityManager().GetIntegrityLevel() >= IntegrityLevel.HighIntegrity) + // get all logon ids + DebugHelp.DebugWriteLine("enumerating logon session"); + WindowsAPI.LsaEnumerateLogonSessionsDelegate(out uint logonCount, out HANDLE logonIdHandle); + createdArtifacts.Add(Artifact.WindowsAPIInvoke("LsaEnumerateLogonSessions")); + var logonWorkingHandle = logonIdHandle; + for (var i = 0; i < logonCount; i++) { - // get all logon ids - DebugHelp.DebugWriteLine("enumerating logon session"); - WindowsAPI.LsaEnumerateLogonSessionsDelegate(out uint logonCount, out HANDLE logonIdHandle); - createdArtifacts.Add(Artifact.WindowsAPIInvoke("LsaEnumerateLogonSessions")); - var logonWorkingHandle = logonIdHandle; - for (var i = 0; i < logonCount; i++) + var logonId = logonWorkingHandle.CastTo(); + if (logonId.IsNull || logonIds.Contains(logonId)) { - var logonId = logonWorkingHandle.CastTo(); - if (logonId.IsNull || logonIds.Contains(logonId)) - { - DebugHelp.DebugWriteLine("LogonId is null or is already in the list, skipping"); - continue; - } - logonIds.Add(logonId); - logonWorkingHandle = logonWorkingHandle.Increment(); + DebugHelp.DebugWriteLine("LogonId is null or is already in the list, skipping"); + continue; } - WindowsAPI.LsaFreeReturnBufferDelegate(logonIdHandle); - } - else - { - // we can only get our own if not elevated - logonIds.Add(GetCurrentLuid()); + logonIds.Add(logonId); + logonWorkingHandle = logonWorkingHandle.Increment(); } + WindowsAPI.LsaFreeReturnBufferDelegate(logonIdHandle); } catch (Exception e) { @@ -136,6 +129,11 @@ private static LogonSessionData GetLogonSessionData(HANDLE luidHandle) { WindowsAPI.LsaGetLogonSessionDataDelegate(luidHandle, out logonSessionDataHandle); createdArtifacts.Add(Artifact.WindowsAPIInvoke("LsaGetLogonSessionData")); + if(logonSessionDataHandle.IsNull) + { + DebugHelp.DebugWriteLine($"Error getting logon session data"); + return new LogonSessionData(); + } var seclogonSessionData = logonSessionDataHandle.CastTo(); LogonSessionData sessionData = new() { @@ -163,7 +161,7 @@ private static LogonSessionData GetLogonSessionData(HANDLE luidHandle) WindowsAPI.LsaFreeReturnBufferDelegate(logonSessionDataHandle); } } - + private static IEnumerable GetTicketCache(HANDLE lsaHandle, uint authPackage, LUID logonId) { //needs to be elevated to pass in a logon id so if we arent we wipe the value here @@ -174,7 +172,7 @@ private static IEnumerable GetTicketCache(HANDLE lsaHandle, uint } // tickets to return List tickets = []; - + KERB_QUERY_TKT_CACHE_REQUEST request = new() { MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheExMessage, @@ -192,7 +190,7 @@ private static IEnumerable GetTicketCache(HANDLE lsaHandle, uint return tickets; } var response = returnBuffer.CastTo(); - + if (response.CountOfTickets == 0) { DebugHelp.DebugWriteLine("No tickets found"); @@ -244,7 +242,7 @@ private static (HANDLE, uint, IEnumerable) InitKerberosConnectionAndSessio } DebugHelp.DebugWriteLine("Got LSA Handle"); connectionInfo.Item1 = lsaHandle; - + // get auth package LSA_IN_STRING packageName = new("kerberos"); HANDLE packageNameHandle = new(packageName); @@ -262,7 +260,6 @@ private static (HANDLE, uint, IEnumerable) InitKerberosConnectionAndSessio // get all logon sessions if (GetSessions) { - DebugHelp.DebugWriteLine("Getting Logon Sessions"); var logonSessions = GetLogonSessions(); var logonSessionList = logonSessions.ToList(); DebugHelp.DebugWriteLine($"Found {logonSessionList.Count()} logon sessions"); @@ -295,7 +292,7 @@ private static HANDLE CreateNewLogonSession() { createdArtifacts.Add(Artifact.PlaintextLogon(userName.ToString())); } - //debug get the luid for the token + //debug get the luid for the token int tokenInfoSize = Marshal.SizeOf(); HANDLE tokenInfo = (HANDLE)Marshal.AllocHGlobal(tokenInfoSize); WindowsAPI.GetTokenInformationDelegate(token, Win32.TokenInformationClass.TokenStatistics,tokenInfo, tokenInfoSize, out int returnLength); @@ -312,7 +309,7 @@ private static HANDLE CreateNewLogonSession() return new(); } } - + /// /// Gets any created artifacts that were produced between the current call and the last call to this method /// @@ -324,7 +321,7 @@ internal static List GetCreatedArtifacts() createdArtifacts.Clear(); return artifacts; } - + //internal methods but are exposed via the KerberosTicketManager internal static LUID GetCurrentLuid() { @@ -343,8 +340,8 @@ internal static LUID GetCurrentLuid() } return new LUID(); } - - + + internal static LUID GetTargetProcessLuid(int pid) { HANDLE tokenInfo = new(); @@ -397,7 +394,7 @@ internal static LUID GetTargetProcessLuid(int pid) WindowsAPI.CloseHandleDelegate(targetProcessHandle); } } - + //get all tickets internal static IEnumerable TriageTickets(bool getSystemTickets = false, string targetLuid = "") { @@ -443,8 +440,8 @@ internal static IEnumerable TriageTickets(bool getSystemTickets } return allTickets; } - - + + //extract ticket internal static KerberosTicket? ExtractTicket(LUID targetLuid, string targetName) { @@ -467,7 +464,7 @@ internal static IEnumerable TriageTickets(bool getSystemTickets DebugHelp.DebugWriteLine($"Failed to find ticket for {targetName}"); return null; } - + KERB_RETRIEVE_TKT_REQUEST request = new() { MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage, @@ -504,8 +501,8 @@ internal static IEnumerable TriageTickets(bool getSystemTickets } //convert the location of the ticket in memory to a struct var response = returnBuffer.CastTo(); - - //make sure the ticket has some data + + //make sure the ticket has some data if (response.Ticket.EncodedTicketSize == 0) { DebugHelp.DebugWriteLine("No ticket Data to extract"); @@ -523,8 +520,8 @@ internal static IEnumerable TriageTickets(bool getSystemTickets return null; } } - - // load ticket + + // load ticket internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid) { HANDLE requestAndTicketHandle = new(); @@ -536,11 +533,11 @@ internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid) //Discarding the logonSessions because we do not need them so we pass false to prevent enumerating them (lsaHandle, uint authPackage, IEnumerable _) = InitKerberosConnectionAndSessionInfo(false); //if we are not an admin user then we cannot send a real lUID so we need to send a null one - if (Agent.GetIdentityManager().GetIntegrityLevel() is <= IntegrityLevel.MediumIntegrity) - { - DebugHelp.DebugWriteLine("Not high integrity, setting targetLuid to 0"); - targetLuid = new LUID(); - } + //if (Agent.GetIdentityManager().GetIntegrityLevel() <= IntegrityLevel.MediumIntegrity) + //{ + // DebugHelp.DebugWriteLine("Not high integrity, setting targetLuid to 0"); + // targetLuid = new LUID(); + //} //get the size of the request structure var requestSize = Marshal.SizeOf(); @@ -551,26 +548,25 @@ internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid) KerbCredSize = submittedTicket.Length, KerbCredOffset = requestSize, }; - + //get the size of the required parts and allocate memory for the struct and the ticket var ticketSize = submittedTicket.Length; DebugHelp.DebugWriteLine($"Ticket is of size {ticketSize}"); var requestPlusTicketSize = requestSize + ticketSize; DebugHelp.DebugWriteLine($"Allocating memory for request and ticket of size {requestPlusTicketSize}"); requestAndTicketHandle = new(Marshal.AllocHGlobal(requestPlusTicketSize)); - + //write the request to the start of the new memory block Marshal.StructureToPtr(request, requestAndTicketHandle, false); //get the address of the end of the struct HANDLE requestEndAddress = new(new(requestAndTicketHandle.PtrLocation.ToInt64() + requestSize)); //write the ticket to the end of the struct Marshal.Copy(submittedTicket, 0, requestEndAddress.PtrLocation, ticketSize); - + //submit the ticket DebugHelp.DebugWriteLine($"Submitting ticket of size {ticketSize} to LSA"); var status = WindowsAPI.LsaCallAuthenticationPackageDelegate(lsaHandle, authPackage, requestAndTicketHandle, requestPlusTicketSize, out returnBuffer, out uint returnLength, out NTSTATUS returnStatus); createdArtifacts.Add(Artifact.WindowsAPIInvoke("LsaCallAuthenticationPackage")); - if (status != NTSTATUS.STATUS_SUCCESS || returnStatus != NTSTATUS.STATUS_SUCCESS) { DebugHelp.DebugWriteLine($"Failed to submit ticket with api status: {status} and return status: {returnStatus}"); @@ -590,10 +586,10 @@ internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid) //is checked because for some operations loading the ticket is not the final step so we may want to keep the handle open WindowsAPI.LsaDeregisterLogonProcessDelegate(lsaHandle); createdArtifacts.Add(Artifact.WindowsAPIInvoke("LsaDeregisterLogonProcess")); - + } } - + // unload ticket internal static bool UnloadTicket(string serviceName, string domainName, LUID targetLuid, bool All) { @@ -617,7 +613,7 @@ internal static bool UnloadTicket(string serviceName, string domainName, LUID ta MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbPurgeTicketCacheMessage, LogonId = Agent.GetIdentityManager().GetIntegrityLevel() <= IntegrityLevel.MediumIntegrity ? new LUID() : targetLuid }; - + // Marshal the request structure first Marshal.StructureToPtr(request, requestBuffer, false); @@ -677,7 +673,7 @@ internal static bool UnloadTicket(string serviceName, string domainName, LUID ta createdArtifacts.Add(Artifact.WindowsAPIInvoke("LsaDeregisterLogonProcess")); } } - + //describe ticket internal static KerberosTicket? TryGetTicketDetailsFromKirbi(byte[] kirbiTicket) { @@ -695,11 +691,22 @@ internal static bool UnloadTicket(string serviceName, string domainName, LUID ta WindowsAPI.ImpersonateLoggedOnUserDelegate(newlogonHandle); createdArtifacts.Add(Artifact.WindowsAPIInvoke("ImpersonateLoggedOnUser")); //passing new luid here is fine because we have switched to the new logon session - LoadTicket(kirbiTicket, new LUID()); - ticket = TriageTickets().FirstOrDefault(); - ticket.Kirbi = kirbiTicket; + int tokenInfoSize = Marshal.SizeOf(); + HANDLE tokenInfo = (HANDLE)Marshal.AllocHGlobal(tokenInfoSize); + WindowsAPI.GetTokenInformationDelegate(newlogonHandle, Win32.TokenInformationClass.TokenStatistics, tokenInfo, tokenInfoSize, out int returnLength); + createdArtifacts.Add(Artifact.WindowsAPIInvoke("GetTokenInformation")); + TOKEN_STATISTICS tokenStats = tokenInfo.CastTo(); + LoadTicket(kirbiTicket, tokenStats.AuthenticationId); + ticket = TriageTickets(getSystemTickets:true, targetLuid:$"{tokenStats.AuthenticationId}").FirstOrDefault(); + if(ticket != null) + { + ticket.Kirbi = kirbiTicket; + DebugHelp.DebugWriteLine($"Converted base64 ticket to KerberosTicket: {ticket.ToString().ToIndentedString()}"); + } else + { + DebugHelp.DebugWriteLine($"Failed to triage any tickets"); + } } - DebugHelp.DebugWriteLine($"Converted base64 ticket to KerberosTicket: {ticket.ToString().ToIndentedString()}"); } catch (Exception e) { diff --git a/Payload_Type/apollo/apollo/agent_code/Tasks/Features/KerberosTickets/ticket_store_add.cs b/Payload_Type/apollo/apollo/agent_code/Tasks/Features/KerberosTickets/ticket_store_add.cs index c58e4d2c..3b338981 100644 --- a/Payload_Type/apollo/apollo/agent_code/Tasks/Features/KerberosTickets/ticket_store_add.cs +++ b/Payload_Type/apollo/apollo/agent_code/Tasks/Features/KerberosTickets/ticket_store_add.cs @@ -41,7 +41,7 @@ public override void Start() KerberosTicket? ticket = _agent.GetTicketManager().GetTicketDetailsFromKirbi(ticketBytes); if(ticket == null) { - resp = CreateTaskResponse($"Failed to extract ticket from kirbi", true, "error"); + resp = CreateTaskResponse($"Failed to extract ticket from kirbi or failed to parse new data", true, "error"); } else { @@ -51,7 +51,7 @@ public override void Start() } catch (Exception e) { - resp = CreateTaskResponse($"Failed to inject ticket into session: {e.Message}", true, "error"); + resp = CreateTaskResponse($"Failed to add ticket into store: {e.Message}", true, "error"); } //get and send back any artifacts IEnumerable artifacts = _agent.GetTicketManager().GetArtifacts(); diff --git a/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py b/Payload_Type/apollo/apollo/mythic/agent_functions/builder.py index 356e7306..fee115d2 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.23" + version = "2.2.24" wrapper = False wrapped_payloads = ["scarecrow_wrapper", "service_wrapper"] note = """ diff --git a/Payload_Type/apollo/apollo/mythic/agent_functions/inject.py b/Payload_Type/apollo/apollo/mythic/agent_functions/inject.py index 2c013515..1f31c2ee 100644 --- a/Payload_Type/apollo/apollo/mythic/agent_functions/inject.py +++ b/Payload_Type/apollo/apollo/mythic/agent_functions/inject.py @@ -53,6 +53,7 @@ async def get_payloads(self, inputMsg: PTRPCDynamicQueryFunctionMessage) -> PTRP for f in payload_search.Payloads: file_names.append(f"{f.Filename} - {f.Description}") fileResponse.Success = True + file_names.sort() fileResponse.Choices = file_names return fileResponse else: