Skip to content

Instantly share code, notes, and snippets.

@chrismaddalena
Last active April 15, 2019 13:55
Show Gist options
  • Save chrismaddalena/e277425272ccec4bb50a2edc2a5a11d2 to your computer and use it in GitHub Desktop.
Save chrismaddalena/e277425272ccec4bb50a2edc2a5a11d2 to your computer and use it in GitHub Desktop.
def compare_dumps(first_hashdump, second_hashdump):
"""Compare the two password dumps and return a dictionary of the results. JSON output:
{
"accounts": {
"CHRISM": {
"enabled": true,
"pwdlastset": "2019-04-14 22:53:08",
"domain": "DOMAIN.COM"
},
"matching_accounts": {}
"CHRISM_DA": {
"enabled": true,
"pwdlastset": "2019-04-13 13:37:12",
"domain": "SECURE.DOMAIN.COM"
}
},
"hash": {
"value": "32-CHAR NTLM HASH VALUE",
"sanitized_value": "a123************************b456",
"matched_value": "32-CHAR NTLM HASH VALUE",
"sanitized_matched_value": "a123************************b456",
"cracked": false
}
},
...
},
"results": {
"total": TOTAL MATCHES WITH ENABLED ACCOUNTS IN SECOND DUMP,
"disabled_matches": TOTAL MATCHES WITH DISABLED ACCOUNTS IN SECOND DUMP,
"unique_accounts": TOTAL UNIQUE MATCHES FROM FIRST DUMP
}
"""
# Setup dictionaries and counters for matches
shared_passwords = {}
shared_passwords['results'] = {}
shared_passwords['accounts'] = {}
shared_password_count = 0
disabled_shared_password_count = 0
# Connect to the Neo4j database
neo4j_driver = setup_database_conn()
# Process the hash dumps and potfile
first_hashes = process_hashes(first_hashdump)
second_hashes = process_hashes(second_hashdump)
potfile_hashes = process_potfile('hashcat.potfile')
# Create a progress bar because this can take a while
with click.progressbar(length=len(first_hashes), label='Sniffing through hashes') as bar:
# Loop over every username and hash in the first list
for first_username, first_ntlm_hash in first_hashes.items():
# Proceed only if the NTLM hash appears as a value in the second dump
if first_ntlm_hash in second_hashes.values():
# Loop over every username nad hash in the second list
for second_username, second_ntlm_hash in second_hashes.items():
# Proceed if the first NTLM hash matches this NTLM hash
if first_ntlm_hash == second_ntlm_hash:
# Get the user data from BloodHound
first_results = execute_query(neo4j_driver, first_username)
if check_records(first_results):
# Enumerate over the results (there _could_ be more than one)
for first_record in first_results:
first_enabled = first_record[0]
first_pwdlastset = datetime.utcfromtimestamp(first_record[1]).strftime('%Y-%m-%d %H:%M:%S')
first_domain = first_record[2]
# Only continue if this first account is enabled (it should be based on the query)
if first_enabled:
# Record the account details in the dictionary
shared_passwords['accounts'][first_username] = {}
shared_passwords['accounts'][first_username]['username'] = first_username.upper() + '@' + first_domain
shared_passwords['accounts'][first_username]['enabled'] = first_enabled
shared_passwords['accounts'][first_username]['pwdlastset'] = first_pwdlastset
shared_passwords['accounts'][first_username]['domain'] = first_domain
shared_passwords['accounts'][first_username]['matching_accounts'] = {}
# Check if the hash was cracked
cracked = False
if first_ntlm_hash in potfile_hashes.keys():
cracked = True
# Add details about the hash to the entry for this account
shared_passwords['accounts'][first_username]['hash'] = {}
shared_passwords['accounts'][first_username]['hash']['value'] = first_ntlm_hash
shared_passwords['accounts'][first_username]['hash']['sanitized_value'] = sanitize(first_ntlm_hash)
shared_passwords['accounts'][first_username]['hash']['matched_value'] =second_ntlm_hash
shared_passwords['accounts'][first_username]['hash']['sanitized_matched_value'] = sanitize(second_ntlm_hash)
shared_passwords['accounts'][first_username]['hash']['cracked'] = cracked
# Record a plaintext value if the hash was cracked
if cracked:
shared_passwords['accounts'][first_username]['hash']['plaintext'] = potfile_hashes[first_ntlm_hash]
# Collect the second account from BloodHound
second_results = execute_query(neo4j_driver, second_username)
# This time proceed if there are results or record the result as a disabled account match
if check_records(second_results):
for second_record in second_results:
second_enabled = second_record[0]
second_pwdlastset = datetime.utcfromtimestamp(second_record[1]).strftime('%Y-%m-%d %H:%M:%S')
second_domain = second_record[2]
if second_enabled:
# Track accounts in list two that share a password with a list one account
shared_password_count += 1
# Add the results to the 'matching_account' key
shared_passwords['accounts'][first_username]['matching_accounts'][second_username] = {}
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['username'] = second_username.upper() + '@' + second_domain
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['enabled'] = second_enabled
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['pwdlastset'] = second_pwdlastset
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['domain'] = second_domain
else:
# Same as above but for disabled accounts
second_results = execute_query(neo4j_driver, second_username, enabled=False)
if check_records(second_results):
disabled_shared_password_count += 1
for second_record in second_results:
second_enabled = second_record[0]
second_pwdlastset = datetime.utcfromtimestamp(second_record[1]).strftime('%Y-%m-%d %H:%M:%S')
second_domain = second_record[2]
# Add the results to the 'matching_account' key
shared_passwords['accounts'][first_username]['matching_accounts'][second_username] = {}
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['username'] = second_username.upper() + '@' + second_domain
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['enabled'] = second_enabled
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['pwdlastset'] = second_pwdlastset
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['domain'] = second_domain
# Manually update the progress by 1 for each account loop
bar.update(1)
# Record the final numeric results in the 'results' key in the dictionary
shared_passwords['results']['total'] = shared_password_count
shared_passwords['results']['disabled_matches'] = disabled_shared_password_count
shared_passwords['results']['unique_accounts'] = len(shared_passwords['accounts'])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment