diff --git a/.gitignore b/.gitignore index 9564866..b1c2275 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ [Rr]elease/ [Bb]in/ [Oo]bj/ +.DS_Store + diff --git a/CHANGELOG.md b/CHANGELOG.md index bb267e4..87fd155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ 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). +## [1.3.0] - 2019-02-05 +### Added +* **klist** action + * lists current user's (or if elevated, all users') ticket information + +### Changed +* **s4u** landed @eladshamir's pull requests + * RBCD support + * support loading TGS from Kirbi to skip S4U2Self and perform S4U2Proxy only + * perform S4U2Self only + * print output for each stage +* **asreproast** landed @rvrsh3ll's pull request + * added hashcat output format +* **asktgt** landed @qlemaire's pull request + * now accepts a /password:X parameter +* **monitor** and **harvest** landed @djhohnstein's pull request + * ticket extraction can now be saved to the registry with the "/registry:X" flag + +### Fixed +* **dump** display of service tickets with multiple slashes +* response buffer size in lib/Networking.cs increased for large ticket responses +* landed @BlueSkeye's fixes for PTT bug fix, TicketFlags display, and dead code removal in PA_DATA.Encode + ## [1.2.1] - 2018-10-09 ### Changed diff --git a/README.md b/README.md index 972b222..690b00b 100755 --- a/README.md +++ b/README.md @@ -17,24 +17,24 @@ Rubeus is licensed under the BSD 3-Clause license. Rubeus usage: - Retrieve a TGT based on a user hash, optionally applying to the current logon session or a specific LUID: - Rubeus.exe asktgt /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid] + Retrieve a TGT based on a user password/hash, optionally applying to the current logon session or a specific LUID: + Rubeus.exe asktgt /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid] - Retrieve a TGT based on a user hash, start a /netonly process, and to apply the ticket to the new process/logon session: - Rubeus.exe asktgt /user:USER /createnetonly:C:\Windows\System32\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] + Retrieve a TGT based on a user password/hash, start a /netonly process, and to apply the ticket to the new process/logon session: + Rubeus.exe asktgt /user:USER /createnetonly:C:\Windows\System32\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] - Renew a TGT, optionally appling the ticket or auto-renewing the ticket up to its renew-till limit: + Retrieve a service ticket for one or more SPNs, optionally applying the ticket: + Rubeus.exe asktgs [/dc:DOMAIN_CONTROLLER] [/ptt] + + Renew a TGT, optionally applying the ticket or auto-renewing the ticket up to its renew-till limit: Rubeus.exe renew [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew] Reset a user's password from a supplied TGT (AoratoPw): Rubeus.exe changepw /new:PASSWORD [/dc:DOMAIN_CONTROLLER] - Retrieve a service ticket for one or more SPNs, optionally applying the ticket: - Rubeus.exe asktgs [/dc:DOMAIN_CONTROLLER] [/ptt] - Perform S4U constrained delegation abuse: - Rubeus.exe s4u /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] - Rubeus.exe s4u /user:USER [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:cifs,HOST,...] [/dc:DOMAIN_CONTROLLER] [/ptt] + Rubeus.exe s4u /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] + Rubeus.exe s4u /user:USER [/domain:DOMAIN] /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] Submit a TGT, optionally targeting a specific LUID (if elevated): Rubeus.exe ptt [/luid:LOGINID] @@ -60,14 +60,17 @@ Rubeus is licensed under the BSD 3-Clause license. Dump all current ticket data (if elevated, dump for all users), optionally targeting a specific service/LUID: Rubeus.exe dump [/service:SERVICE] [/luid:LOGINID] + List all current tickets (if elevated, list for all users), optionally targeting a specific LUID: + Rubeus.exe klist [/luid:LOGINID] + Retrieve a usable TGT .kirbi for the current user (w/ session key) without elevation by abusing the Kerberos GSS-API, faking delegation: Rubeus.exe tgtdeleg [/target:SPN] - Monitor every SECONDS (default 60 seconds) for 4624 logon events, dump any TGT data for new logon sessions, and save data to specified registry path (Default: Disabled): - Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER] [/registry:PATH\UNDER\HKLM] + Monitor every SECONDS (default 60) for 4624 logon events and dump any TGT data for new logon sessions: + Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER] [/registry:SOFTWARENAME] - Monitor every MINUTES (default 60 minutes) for 4624 logon events, dump any new TGT data, and auto-renew TGTs that are about to expire, and save TGTs to a specified registry path (Default: Disabled): - Rubeus.exe harvest [/interval:MINUTES] [/registry:PATH\UNDER\HKLM] + Monitor every MINUTES (default 60) for 4624 logon events, dump any new TGT data, and auto-renew TGTs that are about to expire: + Rubeus.exe harvest [/interval:MINUTES] [/registry:SOFTWARENAME] NOTE: Base64 ticket blobs can be decoded with : @@ -77,7 +80,7 @@ Rubeus is licensed under the BSD 3-Clause license. ## asktgt -The **asktgt** action will build raw AS-REQ (TGT request) traffic for the specified user and encryption key (/rc4 or /aes256). If no /domain is specified, the computer's current domain is extracted, and if no /dc is specified the same is done for the system's current domain controller. If authentication is successful, the resulting AS-REP is parsed and the KRB-CRED (a .kirbi, which includes the user's TGT) is output as a base64 blob. The /ptt flag will "pass-the-ticket" and apply the resulting Kerberos credential to the current logon session. The /luid:X flag will apply the ticket to the specified logon session ID (elevation needed). +The **asktgt** action will build raw AS-REQ (TGT request) traffic for the specified user and encryption key (/rc4 or /aes256). A /password flag can also be used instead of a hash. If no /domain is specified, the computer's current domain is extracted, and if no /dc is specified the same is done for the system's current domain controller. If authentication is successful, the resulting AS-REP is parsed and the KRB-CRED (a .kirbi, which includes the user's TGT) is output as a base64 blob. The /ptt flag will "pass-the-ticket" and apply the resulting Kerberos credential to the current logon session. The /luid:X flag will apply the ticket to the specified logon session ID (elevation needed). Note that no elevated privileges are needed on the host to request TGTs or apply them to the **current** logon session, just the correct hash for the target user. Also, another opsec note: only one TGT can be applied at a time to the current logon session, so the previous TGT is wiped when the new ticket is applied when using the /ptt option. A workaround is to use the **/createnetonly:X** parameter, or request the ticket and apply it to another logon session with **ptt /luid:X**. @@ -664,7 +667,7 @@ The /user:X parameter is required, while the /domain and /dc arguments are optio ## dump -The **dump** action will extract current TGTs and service tickets from memory, if in an elevated context. The resulting extracted tickets can be filtered by /service (use /service:krbtgt for TGTs) and/or logon ID (the /luid:X parameter). The KRB-CRED files (.kirbis) are output as base64 blobs and can be reused with the ptt function, or Mimikatz's **kerberos::ptt** functionality. +The **dump** action will extract current TGTs and service tickets from memory, if in an elevated context. If not elevated, tickets for the current user are extracted. The resulting extracted tickets can be filtered by /service (use /service:krbtgt for TGTs) and/or logon ID (the /luid:X parameter). The KRB-CRED files (.kirbis) are output as base64 blobs and can be reused with the ptt function, or Mimikatz's **kerberos::ptt** functionality. c:\Temp\tickets>Rubeus.exe dump /service:krbtgt /luid:366300 @@ -723,6 +726,47 @@ The **dump** action will extract current TGTs and service tickets from memory, i **Note that this action needs to be run from an elevated context to extract usable TGTs!** +## klist + +The **klist** action will list current logon sessions and associated ticket information, if in an elevated context. If not elevated, information on the current user's tickets is displayed. + + C:\Temp>Rubeus.exe klist + + ______ _ + (_____ \ | | + _____) )_ _| |__ _____ _ _ ___ + | __ /| | | | _ \| ___ | | | |/___) + | | \ \| |_| | |_) ) ____| |_| |___ | + |_| |_|____/|____/|_____)____/(___/ + + v1.3.0 + + + + [*] Action: List Kerberos Tickets (All Users) + + + UserName : dfm.a + Domain : TESTLAB + LogonId : 1915357 + UserSID : S-1-5-21-883232822-274137685-4173207997-1110 + AuthenticationPackage : Kerberos + LogonType : Interactive + LogonTime : 2/5/2019 8:05:32 PM + LogonServer : PRIMARY + LogonServerDNSDomain : TESTLAB.LOCAL + UserPrincipalName : dfm.a@testlab.local + + [0] - 0x12 - aes256_cts_hmac_sha1 + Start/End/MaxRenew: 2/5/2019 12:05:32 PM ; 2/5/2019 5:05:32 PM ; 2/12/2019 12:05:32 PM + Server Name : krbtgt/TESTLAB.LOCAL @ TESTLAB.LOCAL + Client Name : dfm.a @ TESTLAB.LOCAL + Flags : name_canonicalize, initial, renewable, forwardable (40c10000) + + + ...(snip)... + + ## tgtdeleg The **tgtdeleg** using [@gentilkiwi](https://twitter.com/gentilkiwi)'s [Kekeo](https://github.com/gentilkiwi/kekeo/) trick (**tgt::deleg**) that abuses the Kerberos GSS-API to retrieve a usable TGT for the current user without needing elevation on the host. AcquireCredentialsHandle() is used to get a handle to the current user's Kerberos security credentials, and InitializeSecurityContext() with the ISC_REQ_DELEGATE flag and a target SPN of HOST/DC.domain.com to prepare a fake delegate context to send to the DC. This results in an AP-REQ in the GSS-API output that contains a KRB_CRED in the authenticator checksum. The service ticket session key is extracted from the local Kerberos cache and is used to decrypt the KRB_CRED in the authenticator, resulting in a usable TGT .kirbi. diff --git a/Rubeus/Commands/Klist.cs b/Rubeus/Commands/Klist.cs new file mode 100755 index 0000000..d393984 --- /dev/null +++ b/Rubeus/Commands/Klist.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + + +namespace Rubeus.Commands +{ + public class Klist : ICommand + { + public static string CommandName => "klist"; + + public void Execute(Dictionary arguments) + { + if (arguments.ContainsKey("/luid")) + { + UInt32 luid = 0; + try + { + luid = UInt32.Parse(arguments["/luid"]); + } + catch + { + try + { + luid = Convert.ToUInt32(arguments["/luid"], 16); + } + catch + { + Console.WriteLine("[X] Invalid LUID format ({0})\r\n", arguments["/LUID"]); + return; + } + } + LSA.ListKerberosTickets(luid); + } + else + { + LSA.ListKerberosTickets(); + } + } + } +} \ No newline at end of file diff --git a/Rubeus/Domain/CommandCollection.cs b/Rubeus/Domain/CommandCollection.cs index 30e8c7f..1e581c9 100755 --- a/Rubeus/Domain/CommandCollection.cs +++ b/Rubeus/Domain/CommandCollection.cs @@ -26,6 +26,7 @@ namespace Rubeus.Domain _availableCommands.Add(Dump.CommandName, () => new Dump()); _availableCommands.Add(HarvestCommand.CommandName, () => new HarvestCommand()); _availableCommands.Add(Kerberoast.CommandName, () => new Kerberoast()); + _availableCommands.Add(Klist.CommandName, () => new Klist()); _availableCommands.Add(Monitor.CommandName, () => new Monitor()); _availableCommands.Add(Ptt.CommandName, () => new Ptt()); _availableCommands.Add(Purge.CommandName, () => new Purge()); diff --git a/Rubeus/Domain/Info.cs b/Rubeus/Domain/Info.cs index c09f20e..90808f8 100755 --- a/Rubeus/Domain/Info.cs +++ b/Rubeus/Domain/Info.cs @@ -12,7 +12,7 @@ namespace Rubeus.Domain Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)"); Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |"); Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n"); - Console.WriteLine(" v1.2.1\r\n"); + Console.WriteLine(" v1.3.0\r\n"); } public static void ShowUsage() @@ -60,7 +60,10 @@ namespace Rubeus.Domain Console.WriteLine(" Rubeus.exe asreproast /user:USER [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]"); Console.WriteLine("\r\n Dump all current ticket data (if elevated, dump for all users), optionally targeting a specific service/LUID:"); - Console.WriteLine(" Rubeus.exe dump [/service:SERVICE] [/luid:LOGINID]"); + Console.WriteLine(" Rubeus.exe dump [/service:SERVICE] [/luid:LOGINID]"); + + Console.WriteLine("\r\n List all current tickets (if elevated, list for all users), optionally targeting a specific LUID:"); + Console.WriteLine(" Rubeus.exe klist [/luid:LOGINID]"); Console.WriteLine("\r\n Retrieve a usable TGT .kirbi for the current user (w/ session key) without elevation by abusing the Kerberos GSS-API, faking delegation:"); Console.WriteLine(" Rubeus.exe tgtdeleg [/target:SPN]"); diff --git a/Rubeus/Rubeus.csproj b/Rubeus/Rubeus.csproj index 114639c..4172d59 100755 --- a/Rubeus/Rubeus.csproj +++ b/Rubeus/Rubeus.csproj @@ -57,6 +57,7 @@ + diff --git a/Rubeus/lib/Interop.cs b/Rubeus/lib/Interop.cs index 7c3148a..eba4368 100755 --- a/Rubeus/lib/Interop.cs +++ b/Rubeus/lib/Interop.cs @@ -734,7 +734,6 @@ namespace Rubeus { public KERB_PROTOCOL_MESSAGE_TYPE MessageType; public int CountOfTickets; - // public KERB_TICKET_CACHE_INFO[] Tickets; public IntPtr Tickets; } @@ -750,6 +749,20 @@ namespace Rubeus public UInt32 TicketFlags; } + [StructLayout(LayoutKind.Sequential)] + public struct KERB_TICKET_CACHE_INFO_EX + { + public LSA_STRING_OUT ClientName; + public LSA_STRING_OUT ClientRealm; + public LSA_STRING_OUT ServerName; + public LSA_STRING_OUT ServerRealm; + public Int64 StartTime; + public Int64 EndTime; + public Int64 RenewTime; + public Int32 EncryptionType; + public UInt32 TicketFlags; + } + [StructLayout(LayoutKind.Sequential)] public struct KERB_EXTERNAL_NAME { @@ -757,7 +770,7 @@ namespace Rubeus public UInt16 NameCount; [MarshalAs(UnmanagedType.ByValArray, - SizeConst = 2)] + SizeConst = 3)] public LSA_STRING_OUT[] Names; //public LSA_STRING_OUT[] Names; } diff --git a/Rubeus/lib/LSA.cs b/Rubeus/lib/LSA.cs index 9d23829..d0788d8 100755 --- a/Rubeus/lib/LSA.cs +++ b/Rubeus/lib/LSA.cs @@ -310,7 +310,6 @@ namespace Rubeus public static void ListKerberosTicketData(uint targetLuid = 0, string targetService = "", bool monitor = false, string registryBasePath = null) { - // lists if (Helpers.IsHighIntegrity()) { ListKerberosTicketDataAllUsers(targetLuid, targetService, monitor, false, registryBasePath); @@ -325,6 +324,18 @@ namespace Rubeus } } + public static void ListKerberosTickets(uint targetLuid = 0) + { + if (Helpers.IsHighIntegrity()) + { + ListKerberosTicketsAllUsers(targetLuid); + } + else + { + ListKerberosTicketsCurrentUser(); + } + } + public static void ListKerberosTicketDataAllUsers(uint targetLuid = 0, string targetService = "", bool monitor = false, bool harvest = false, string registryBasePath = null) { // extracts Kerberos ticket data for all users on the system (assuming elevation) @@ -606,6 +617,13 @@ namespace Rubeus string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim(); serviceName = String.Format("{0}/{1}", serviceNameStr1, serviceNameStr2); } + else if (serviceNameStruct.NameCount == 3) + { + string serviceNameStr1 = Marshal.PtrToStringUni(serviceNameStruct.Names[0].Buffer, serviceNameStruct.Names[0].Length / 2).Trim(); + string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim(); + string serviceNameStr3 = Marshal.PtrToStringUni(serviceNameStruct.Names[2].Buffer, serviceNameStruct.Names[2].Length / 2).Trim(); + serviceName = String.Format("{0}/{1}/{2}", serviceNameStr1, serviceNameStr2, serviceNameStr3); + } else { } } @@ -625,6 +643,13 @@ namespace Rubeus string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); targetName = String.Format("{0}/{1}", targetNameStr1, targetNameStr2); } + else if (targetNameStruct.NameCount == 3) + { + string targetNameStr1 = Marshal.PtrToStringUni(targetNameStruct.Names[0].Buffer, targetNameStruct.Names[0].Length / 2).Trim(); + string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); + string targetNameStr3 = Marshal.PtrToStringUni(targetNameStruct.Names[2].Buffer, targetNameStruct.Names[2].Length / 2).Trim(); + targetName = String.Format("{0}/{1}/{2}", targetNameStr1, targetNameStr2, targetNameStr3); + } else { } } @@ -759,6 +784,190 @@ namespace Rubeus } } + public static void ListKerberosTicketsAllUsers(uint targetLuid = 0) + { + // lists Kerberos tickets for all users on the system (assuming elevation) + + // first elevates to SYSTEM and uses LsaRegisterLogonProcessHelper connect to LSA + // then calls LsaCallAuthenticationPackage w/ a KerbQueryTicketCacheMessage message type to enumerate all cached tickets + + // adapted partially from Vincent LE TOUX' work + // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 + // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ + // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 + + Console.WriteLine("\r\n\r\n[*] Action: List Kerberos Tickets (All Users)\r\n"); + + if (targetLuid != 0) + { + Console.WriteLine("[*] Target LUID : 0x{0:x}", targetLuid); + } + + int retCode; + int authPack; + string name = "kerberos"; + Interop.LSA_STRING_IN LSAString; + LSAString.Length = (ushort)name.Length; + LSAString.MaximumLength = (ushort)(name.Length + 1); + LSAString.Buffer = name; + + IntPtr lsaHandle = LsaRegisterLogonProcessHelper(); + + // if the original call fails then it is likely we don't have SeTcbPrivilege + // to get SeTcbPrivilege we can Impersonate a NT AUTHORITY\SYSTEM Token + if (lsaHandle == IntPtr.Zero) + { + string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name; + if (currentName == "NT AUTHORITY\\SYSTEM") + { + // if we're already SYSTEM, we have the proper privilegess to get a Handle to LSA with LsaRegisterLogonProcessHelper + lsaHandle = LsaRegisterLogonProcessHelper(); + } + else + { + // elevated but not system, so gotta GetSystem() first + Helpers.GetSystem(); + // should now have the proper privileges to get a Handle to LSA + lsaHandle = LsaRegisterLogonProcessHelper(); + // we don't need our NT AUTHORITY\SYSTEM Token anymore so we can revert to our original token + Interop.RevertToSelf(); + } + } + + try + { + // obtains the unique identifier for the kerberos authentication package. + retCode = Interop.LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack); + + // first return all the logon sessions + DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate + UInt64 count; + IntPtr luidPtr = IntPtr.Zero; + IntPtr iter = luidPtr; + + uint ret = Interop.LsaEnumerateLogonSessions(out count, out luidPtr); // get an array of pointers to LUIDs + + for (ulong i = 0; i < count; i++) + { + IntPtr sessionData; + ret = Interop.LsaGetLogonSessionData(luidPtr, out sessionData); + Interop.SECURITY_LOGON_SESSION_DATA data = (Interop.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(Interop.SECURITY_LOGON_SESSION_DATA)); + + // if we have a valid logon + if (data.PSiD != IntPtr.Zero) + { + // user session data + string username = Marshal.PtrToStringUni(data.Username.Buffer).Trim(); + System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD); + string domain = Marshal.PtrToStringUni(data.LoginDomain.Buffer).Trim(); + string authpackage = Marshal.PtrToStringUni(data.AuthenticationPackage.Buffer).Trim(); + Interop.SECURITY_LOGON_TYPE logonType = (Interop.SECURITY_LOGON_TYPE)data.LogonType; + DateTime logonTime = systime.AddTicks((long)data.LoginTime); + string logonServer = Marshal.PtrToStringUni(data.LogonServer.Buffer).Trim(); + string dnsDomainName = Marshal.PtrToStringUni(data.DnsDomainName.Buffer).Trim(); + string upn = Marshal.PtrToStringUni(data.Upn.Buffer).Trim(); + + IntPtr ticketsPointer = IntPtr.Zero; + DateTime sysTime = new DateTime(1601, 1, 1, 0, 0, 0, 0); + + int returnBufferLength = 0; + int protocalStatus = 0; + + Interop.KERB_QUERY_TKT_CACHE_REQUEST tQuery = new Interop.KERB_QUERY_TKT_CACHE_REQUEST(); + Interop.KERB_QUERY_TKT_CACHE_RESPONSE tickets = new Interop.KERB_QUERY_TKT_CACHE_RESPONSE(); + Interop.KERB_TICKET_CACHE_INFO_EX ticket; + + // input object for querying the ticket cache for a specific logon ID + Interop.LUID userLogonID = new Interop.LUID(); + userLogonID.LowPart = data.LoginID.LowPart; + userLogonID.HighPart = 0; + tQuery.LogonId = userLogonID; + + if ((targetLuid == 0) || (data.LoginID.LowPart == targetLuid)) + { + tQuery.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheExMessage; + + // query LSA, specifying we want the ticket cache + IntPtr tQueryPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tQuery)); + Marshal.StructureToPtr(tQuery, tQueryPtr, false); + retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, tQueryPtr, Marshal.SizeOf(tQuery), out ticketsPointer, out returnBufferLength, out protocalStatus); + + if (ticketsPointer != IntPtr.Zero) + { + // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure + tickets = (Interop.KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketsPointer, typeof(Interop.KERB_QUERY_TKT_CACHE_RESPONSE)); + int count2 = tickets.CountOfTickets; + + if (count2 != 0) + { + Console.WriteLine("\r\n UserName : {0}", username); + Console.WriteLine(" Domain : {0}", domain); + Console.WriteLine(" LogonId : {0}", data.LoginID.LowPart); + Console.WriteLine(" UserSID : {0}", sid.Value); + Console.WriteLine(" AuthenticationPackage : {0}", authpackage); + Console.WriteLine(" LogonType : {0}", logonType); + Console.WriteLine(" LogonTime : {0}", logonTime); + Console.WriteLine(" LogonServer : {0}", logonServer); + Console.WriteLine(" LogonServerDNSDomain : {0}", dnsDomainName); + Console.WriteLine(" UserPrincipalName : {0}", upn); + Console.WriteLine(); + + // get the size of the structures we're iterating over + Int32 dataSize = Marshal.SizeOf(typeof(Interop.KERB_TICKET_CACHE_INFO_EX)); + + for (int j = 0; j < count2; j++) + { + // iterate through the result structures + IntPtr currTicketPtr = (IntPtr)(long)((ticketsPointer.ToInt64() + (int)(8 + j * dataSize))); + + // parse the new ptr to the appropriate structure + ticket = (Interop.KERB_TICKET_CACHE_INFO_EX)Marshal.PtrToStructure(currTicketPtr, typeof(Interop.KERB_TICKET_CACHE_INFO_EX)); + + DateTime startTime = DateTime.FromFileTime(ticket.StartTime); + DateTime endTime = DateTime.FromFileTime(ticket.EndTime); + DateTime renewTime = DateTime.FromFileTime(ticket.RenewTime); + + string ticketFlags = ((Interop.TicketFlags)ticket.TicketFlags).ToString(); + + // extract the server name/realm and client name/realm + string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2); + string serverRealm = Marshal.PtrToStringUni(ticket.ServerRealm.Buffer, ticket.ServerRealm.Length / 2); + string clientName = Marshal.PtrToStringUni(ticket.ClientName.Buffer, ticket.ClientName.Length / 2); + string clientRealm = Marshal.PtrToStringUni(ticket.ClientRealm.Buffer, ticket.ClientRealm.Length / 2); + + Console.WriteLine(" [{0:x}] - 0x{1:x} - {2}", j, (int)ticket.EncryptionType, (Interop.KERB_ETYPE)ticket.EncryptionType); + Console.WriteLine(" Start/End/MaxRenew: {0} ; {1} ; {2}", startTime, endTime, renewTime); + Console.WriteLine(" Server Name : {0} @ {1}", serverName, serverRealm); + Console.WriteLine(" Client Name : {0} @ {1}", clientName, clientRealm); + Console.WriteLine(" Flags : {0} ({1:x})", ticketFlags, ticket.TicketFlags); + Console.WriteLine(); + } + } + } + + // cleanup + Interop.LsaFreeReturnBuffer(ticketsPointer); + Marshal.FreeHGlobal(tQueryPtr); + } + } + + // move the pointer forward + luidPtr = (IntPtr)((long)luidPtr.ToInt64() + Marshal.SizeOf(typeof(Interop.LUID))); + + // cleaup + Interop.LsaFreeReturnBuffer(sessionData); + } + Interop.LsaFreeReturnBuffer(luidPtr); + + // disconnect from LSA + Interop.LsaDeregisterLogonProcess(lsaHandle); + } + catch (Exception ex) + { + Console.WriteLine("[X] Exception: {0}", ex); + } + } + public static void ListKerberosTicketDataCurrentUser(string targetService) { // extracts Kerberos ticket data for the current user @@ -898,6 +1107,13 @@ namespace Rubeus string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim(); serviceName = String.Format("{0}/{1}", serviceNameStr1, serviceNameStr2); } + else if (serviceNameStruct.NameCount == 3) + { + string serviceNameStr1 = Marshal.PtrToStringUni(serviceNameStruct.Names[0].Buffer, serviceNameStruct.Names[0].Length / 2).Trim(); + string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim(); + string serviceNameStr3 = Marshal.PtrToStringUni(serviceNameStruct.Names[2].Buffer, serviceNameStruct.Names[2].Length / 2).Trim(); + serviceName = String.Format("{0}/{1}/{2}", serviceNameStr1, serviceNameStr2, serviceNameStr3); + } else { } } @@ -917,6 +1133,13 @@ namespace Rubeus string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); targetName = String.Format("{0}/{1}", targetNameStr1, targetNameStr2); } + else if (targetNameStruct.NameCount == 3) + { + string targetNameStr1 = Marshal.PtrToStringUni(targetNameStruct.Names[0].Buffer, targetNameStruct.Names[0].Length / 2).Trim(); + string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); + string targetNameStr3 = Marshal.PtrToStringUni(targetNameStruct.Names[2].Buffer, targetNameStruct.Names[2].Length / 2).Trim(); + targetName = String.Format("{0}/{1}/{2}", targetNameStr1, targetNameStr2, targetNameStr3); + } else { } } @@ -1011,6 +1234,96 @@ namespace Rubeus Console.WriteLine("[*] Extracted {0} total tickets\r\n", extractedTicketCount); } + public static void ListKerberosTicketsCurrentUser() + { + // lists Kerberos tickets for the current user + + // first uses LsaConnectUntrusted to connect and LsaCallAuthenticationPackage w/ a KerbQueryTicketCacheMessage message type + // to enumerate all cached tickets + + // adapted partially from Vincent LE TOUX' work + // https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1#L2939-L2950 + // and https://www.dreamincode.net/forums/topic/135033-increment-memory-pointer-issue/ + // also Jared Atkinson's work at https://github.com/Invoke-IR/ACE/blob/master/ACE-Management/PS-ACE/Scripts/ACE_Get-KerberosTicketCache.ps1 + + Console.WriteLine("\r\n\r\n[*] Action: List Kerberos Tickets (Current User)\r\n"); + + string name = "kerberos"; + Interop.LSA_STRING_IN LSAString; + LSAString.Length = (ushort)name.Length; + LSAString.MaximumLength = (ushort)(name.Length + 1); + LSAString.Buffer = name; + + IntPtr ticketsPointer = IntPtr.Zero; + int authPack; + int returnBufferLength = 0; + int protocalStatus = 0; + IntPtr lsaHandle; + int retCode; + + // If we want to look at tickets from a session other than our own + // then we need to use LsaRegisterLogonProcess instead of LsaConnectUntrusted + retCode = Interop.LsaConnectUntrusted(out lsaHandle); + + // obtains the unique identifier for the kerberos authentication package. + retCode = Interop.LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack); + + Interop.KERB_QUERY_TKT_CACHE_REQUEST cacheQuery = new Interop.KERB_QUERY_TKT_CACHE_REQUEST(); + Interop.KERB_QUERY_TKT_CACHE_RESPONSE cacheTickets = new Interop.KERB_QUERY_TKT_CACHE_RESPONSE(); + Interop.KERB_TICKET_CACHE_INFO_EX ticket; + + // input object for querying the ticket cache (https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_kerb_query_tkt_cache_request) + cacheQuery.LogonId = new Interop.LUID(); + cacheQuery.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheExMessage; + + // query LSA, specifying we want the ticket cache + IntPtr cacheQueryPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cacheQuery)); + Marshal.StructureToPtr(cacheQuery, cacheQueryPtr, false); + retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, cacheQueryPtr, Marshal.SizeOf(cacheQuery), out ticketsPointer, out returnBufferLength, out protocalStatus); + + // parse the returned pointer into our initial KERB_QUERY_TKT_CACHE_RESPONSE structure + cacheTickets = (Interop.KERB_QUERY_TKT_CACHE_RESPONSE)Marshal.PtrToStructure((System.IntPtr)ticketsPointer, typeof(Interop.KERB_QUERY_TKT_CACHE_RESPONSE)); + int count = cacheTickets.CountOfTickets; + + // get the size of the structures we're iterating over + Int32 dataSize = Marshal.SizeOf(typeof(Interop.KERB_TICKET_CACHE_INFO_EX)); + + for (int i = 0; i < count; i++) + { + // iterate through the result structures + IntPtr currTicketPtr = (IntPtr)(long)((ticketsPointer.ToInt64() + (int)(8 + i * dataSize))); + + // parse the new ptr to the appropriate structure + ticket = (Interop.KERB_TICKET_CACHE_INFO_EX)Marshal.PtrToStructure(currTicketPtr, typeof(Interop.KERB_TICKET_CACHE_INFO_EX)); + + DateTime startTime = DateTime.FromFileTime(ticket.StartTime); + DateTime endTime = DateTime.FromFileTime(ticket.EndTime); + DateTime renewTime = DateTime.FromFileTime(ticket.RenewTime); + + string ticketFlags = ((Interop.TicketFlags)ticket.TicketFlags).ToString(); + + // extract the server name/realm and client name/realm + string serverName = Marshal.PtrToStringUni(ticket.ServerName.Buffer, ticket.ServerName.Length / 2); + string serverRealm = Marshal.PtrToStringUni(ticket.ServerRealm.Buffer, ticket.ServerRealm.Length / 2); + string clientName = Marshal.PtrToStringUni(ticket.ClientName.Buffer, ticket.ClientName.Length / 2); + string clientRealm = Marshal.PtrToStringUni(ticket.ClientRealm.Buffer, ticket.ClientRealm.Length / 2); + + Console.WriteLine(" [{0:x}] - 0x{1:x} - {2}", i, (int)ticket.EncryptionType, (Interop.KERB_ETYPE)ticket.EncryptionType); + Console.WriteLine(" Start/End/MaxRenew: {0} ; {1} ; {2}", startTime, endTime, renewTime); + Console.WriteLine(" Server Name : {0} @ {1}", serverName, serverRealm); + Console.WriteLine(" Client Name : {0} @ {1}", clientName, clientRealm); + Console.WriteLine(" Flags : {0} ({1:x})", ticketFlags, ticket.TicketFlags); + Console.WriteLine(); + } + + // clean up + Interop.LsaFreeReturnBuffer(ticketsPointer); + Marshal.FreeHGlobal(cacheQueryPtr); + + // disconnect from LSA + Interop.LsaDeregisterLogonProcess(lsaHandle); + } + public static List ExtractTGTs(uint targetLuid = 0, bool includeComputerAccounts = false) { // extracts Kerberos TGTs for all users on the system (assuming elevation) or for a specific logon ID (luid) @@ -1443,65 +1756,6 @@ namespace Rubeus byte[] sessionKey = new byte[sessionKeyLength]; Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength); - //string serviceName = ""; - //if (response.Ticket.ServiceName != IntPtr.Zero) - //{ - // Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME)); - // if (serviceNameStruct.NameCount == 1) - // { - // string serviceNameStr1 = Marshal.PtrToStringUni(serviceNameStruct.Names[0].Buffer, serviceNameStruct.Names[0].Length / 2).Trim(); - // serviceName = serviceNameStr1; - // } - // else if (serviceNameStruct.NameCount == 2) - // { - // string serviceNameStr1 = Marshal.PtrToStringUni(serviceNameStruct.Names[0].Buffer, serviceNameStruct.Names[0].Length / 2).Trim(); - // string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim(); - // serviceName = String.Format("{0}/{1}", serviceNameStr1, serviceNameStr2); - // } - // else { } - //} - - - //string targetName = ""; - //if (response.Ticket.TargetName != IntPtr.Zero) - //{ - // Interop.KERB_EXTERNAL_NAME targetNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(Interop.KERB_EXTERNAL_NAME)); - // if (targetNameStruct.NameCount == 1) - // { - // string targetNameStr1 = Marshal.PtrToStringUni(targetNameStruct.Names[0].Buffer, targetNameStruct.Names[0].Length / 2).Trim(); - // targetName = targetNameStr1; - // } - // else if (targetNameStruct.NameCount == 2) - // { - // string targetNameStr1 = Marshal.PtrToStringUni(targetNameStruct.Names[0].Buffer, targetNameStruct.Names[0].Length / 2).Trim(); - // string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); - // targetName = String.Format("{0}/{1}", targetNameStr1, targetNameStr2); - // } - // else { } - //} - - - //string clientName = ""; - //if (response.Ticket.ClientName != IntPtr.Zero) - //{ - // Interop.KERB_EXTERNAL_NAME clientNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(Interop.KERB_EXTERNAL_NAME)); - // if (clientNameStruct.NameCount == 1) - // { - // string clientNameStr1 = Marshal.PtrToStringUni(clientNameStruct.Names[0].Buffer, clientNameStruct.Names[0].Length / 2).Trim(); - // clientName = clientNameStr1; - // } - // else if (clientNameStruct.NameCount == 2) - // { - // string clientNameStr1 = Marshal.PtrToStringUni(clientNameStruct.Names[0].Buffer, clientNameStruct.Names[0].Length / 2).Trim(); - // string clientNameStr2 = Marshal.PtrToStringUni(clientNameStruct.Names[1].Buffer, clientNameStruct.Names[1].Length / 2).Trim(); - // clientName = String.Format("{0}@{1}", clientNameStr1, clientNameStr2); - // } - // else { } - //} - //Console.WriteLine("ServiceName: {0}", serviceName); - //Console.WriteLine("TargetName: {0}", targetName); - //Console.WriteLine("ClientName: {0}", clientName); - returnedSessionKey = sessionKey; } else