This article is about filtering and formatting your results from Hashcat, let me explain, there is a chance your Active Directory database is filled with old user accounts that do not comply with your me recent password policies so if an account is disabled, it will still be included in this audit.
Pre-Flight Files Required
WARNING: You need to ensure you have the results from the lmpacket dump (the secrets) and extract from the Hashcat pot file (which you can get with the --show variable) in this example this is the files below:
The file are as follows:
hashcat_output.txt - Hascat pot file extract
ntlm-extract.ntds - lmpacket file as a results of the secretsdump.py
ADDS Housekeeping and Pruning
If you find lots of password no longer comply to your password policy might I suggest you do a bit of housekeeping and pruning, obviously, as we’ve taken a snapshot of Active Directory during the secrets dump this files does not differentiate between enabled and disabled accounts - neither will Hashcat.
Password Policy and FGPP
You have your password policy which for this example lets say is 15 characters which will cover all accounts that are not assigned a FGPP (Fine Grained Password Policy) requirement, so the base is 15 characters but certain accounts could be more or less - depending on company requirements.
Pre-Script Mission Requirements
Therefore, first, let’s go through the list of requirements that we need to filter and audit:
- Ignore passwords under 15 characters
- Provide a detailed analysis of the passwords that have been audited
- Provide details analysis of usernames that are linked to the passwords that have been audited and provide details statistics in how many audited passwords have then been shared with accounts (essentially, is this password active on a single account or multiple accounts?)
We now have all requirements so we can get to work with Powershell, yayy!
Script : PasswordFilter.ps1
This script will only show you passwords that are 15 characters or more and give you a count at the end for easy visibility.
Files Required : hascat_output.txt
# Read the input from a file (replace 'hashcat_output.txt' with your actual file name)
$inputData = Get-Content -Path 'hashcat_output.txt'
# Initialize a counter for passwords meeting the criteria
$passwordCount = 0
# Process each line
foreach ($line in $inputData) {
# Split the line at the colon
$parts = $line -split ':'
# Check if there are two parts and the password is 15 characters or longer
if ($parts.Count -eq 2 -and $parts[1].Length -ge 15) {
# Output the password
Write-Output $parts[1]
# Increment the counter
$passwordCount++
}
}
# Output the total count of passwords meeting the criteria
Write-Output "`nTotal number of passwords 15 characters or longer: $passwordCount"
That when run will look like this:
Script : PasswordAnalysis.ps1
This script will analyse your passwords and give you a report on the distribution on all the password you have that have been audited.
Files Required : hascat_output.txt
$inputData = Get-Content -Path 'hashcat_output.txt'
# Initialize counters and collections
$totalPasswords = 0$passwordLengths = @{}
$containsUppercase = 0
$containsLowercase = 0
$containsNumbers = 0
$containsSpecialChars = 0
$commonPasswords = @('password', '123456', 'qwerty', 'admin', 'letmein', 'welcome', 'god')
$commonPasswordCount = 0
$uniquePasswords = @{}
function Get-PasswordStrength {
param (
[string]$password
)
$strength = 0
if ($password.Length -ge 12) { $strength += 2 } elseif ($password.Length -ge 8) { $strength += 1 }
if ($password -cmatch '[A-Z]') { $strength++ }
if ($password -cmatch '[a-z]') { $strength++ }
if ($password -match '\d') { $strength++ }
if ($password -match '[^a-zA-Z0-9]') { $strength++ }
return $strength
}
# Process each line
foreach ($line in $inputData) {
$parts = $line -split ':'
if ($parts.Count -eq 2) {
$password = $parts[1]
$totalPasswords++
# Length analysis
$length = $password.Length
if ($passwordLengths.ContainsKey($length)) {
$passwordLengths[$length]++
} else {
$passwordLengths[$length] = 1
}
# Character type analysis
if ($password -cmatch '[A-Z]') { $containsUppercase++ }
if ($password -cmatch '[a-z]') { $containsLowercase++ }
if ($password -match '\d') { $containsNumbers++ }
if ($password -match '[^a-zA-Z0-9]') { $containsSpecialChars++ }
# Common password check
if ($commonPasswords -contains $password.ToLower()) { $commonPasswordCount++ }
# Uniqueness check
if (-not $uniquePasswords.ContainsKey($password)) {
$uniquePasswords[$password] = 1
} else {
$uniquePasswords[$password]++
}
}
}
# Output results
Write-Output "Password Security Analysis Report`n"
Write-Output "Total passwords analyzed: $totalPasswords"
Write-Output "Unique passwords: $($uniquePasswords.Count)"
Write-Output "`nPassword Length Distribution:"
$passwordLengths.GetEnumerator() | Sort-Object Name | ForEach-Object {
$percentage = [math]::Round(($_.Value / $totalPasswords) * 100, 2)
Write-Output " $($_.Name) characters: $($_.Value) ($percentage%)"
}
Write-Output "`nCharacter Type Analysis:"
Write-Output " Passwords with uppercase letters: $containsUppercase ($([math]::Round(($containsUppercase / $totalPasswords) * 100, 2))%)"
Write-Output " Passwords with lowercase letters: $containsLowercase ($([math]::Round(($containsLowercase / $totalPasswords) * 100, 2))%)"
Write-Output " Passwords with numbers: $containsNumbers ($([math]::Round(($containsNumbers / $totalPasswords) * 100, 2))%)"
Write-Output " Passwords with special characters: $containsSpecialChars ($([math]::Round(($containsSpecialChars / $totalPasswords) * 100, 2))%)"
Write-Output "`nCommon Password Analysis:"
Write-Output " Passwords matching common password list: $commonPasswordCount ($([math]::Round(($commonPasswordCount / $totalPasswords) * 100, 2))%)"
Write-Output "`nPassword Strength Distribution:"
$strengthDistribution = @{0=0; 1=0; 2=0; 3=0; 4=0; 5=0; 6=0}
$uniquePasswords.Keys | ForEach-Object {
$strength = Get-PasswordStrength $_
$strengthDistribution[$strength]++
}
$strengthDistribution.GetEnumerator() | Sort-Object Name | ForEach-Object {
$percentage = [math]::Round(($_.Value / $totalPasswords) * 100, 2)
Write-Output " Strength $($_.Name): $($_.Value) ($percentage%)"
}
Write-Output "`nMost Common Passwords (Top 5):"
$uniquePasswords.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 5 | ForEach-Object {
$percentage = [math]::Round(($_.Value / $totalPasswords) * 100, 2)
Write-Output " $($_.Key): $($_.Value) occurrences ($percentage%)"
}
This is what that looks like when it is run:
Script : UserAnalysis.ps1
This script will map the passwords back to the usernames and then give you some detailed analysis of the results including how many passwords have been used on mutiple accounts and what those accounts are, both in a count and % and a list of accounts that have shared passwords.
# Read the hashcat output file
$hashcatOutput = Get-Content -Path "hashcat_output.txt"
# Create a hashtable to store hash-password pairs
$hashPasswordMap = @{}
# Parse the hashcat output and populate the hashtable
foreach ($line in $hashcatOutput) {
$hash, $password = $line -split ':', 2
if (![string]::IsNullOrWhiteSpace($password) -and $password.Length -ge 15) {
$hashPasswordMap[$hash] = $password
}
}
# Read the NTLM extract file
$ntlmExtract = Get-Content -Path "ntlm-extract.ntds"
# Create hashtables for analysis
$matchCount = 0
$passwordUsage = @{}
$userPasswords = @{}
# Process each line in the NTLM extract
foreach ($line in $ntlmExtract) {
$parts = $line -split ':'
$username = ($parts[0] -split '\\')[-1] # Extract username after the backslash
$ntlmHash = $parts[3] # NTLM hash is the 4th field
# Check if the hash exists in our map
if ($hashPasswordMap.ContainsKey($ntlmHash)) {
$password = $hashPasswordMap[$ntlmHash]
Write-Output "Username: $username, Password: $password"
$matchCount++
# Track password usage
if ($passwordUsage.ContainsKey($password)) {
$passwordUsage[$password]++
} else {
$passwordUsage[$password] = 1
}
# Track user-password pairs
$userPasswords[$username] = $password
}
}
# Output summary and analysis
Write-Output "`n--- Summary and Analysis (Passwords 15+ characters) ---"
Write-Output "Total matches found: $matchCount"
Write-Output "`nPassword usage breakdown:"
$passwordUsage.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object {
$password = $_.Key
$count = $_.Value
$percentage = ($count / $matchCount) * 100
Write-Output ("Password '{0}' (Length: {1}) is used by {2} account(s) ({3:N2}% of matched accounts)" -f $password, $password.Length, $count, $percentage)
}
Write-Output "`nAccounts sharing passwords:"
$passwordUsage.GetEnumerator() | Where-Object { $_.Value -gt 1 } | ForEach-Object {
$password = $_.Key
$users = $userPasswords.GetEnumerator() | Where-Object { $_.Value -eq $password } | ForEach-Object { $_.Key }
Write-Output ("Password '{0}' (Length: {1}) is shared by: {2}" -f $password, $password.Length, ($users -join ", "))
}
That will look like this when run and you may find this will give you information you really may not want to see about your password hygiene:
<pending image>
Then you get the "accounts sharing the same password" analysis, obviously after the "shared by" you will see the samAccountName:Absolutely magic 🪄🌈