Skip to content

Instantly share code, notes, and snippets.

@troyxmccall
Last active September 26, 2024 16:56
Show Gist options
  • Save troyxmccall/37dc0d0ed9e284f778c6c272194152fe to your computer and use it in GitHub Desktop.
Save troyxmccall/37dc0d0ed9e284f778c6c272194152fe to your computer and use it in GitHub Desktop.
bruteforce APFS encrypted drive with know words (dictionary attack)
  1. create a list of known passwords in a list.txt
my
backup
personal
photo
photos
rescue
seagate
  1. run
 diskutil list

to get the drive device name of the volumen you need to unlock

ie:

/dev/disk3 (synthesized):
  #:                       TYPE NAME                    SIZE       IDENTIFIER
  0:      APFS Container Scheme -                      +30.8 GB    disk3
                                Physical Store disk2s2
  1:                APFS Volume ⁨myEncryptedDrive        6.0 GB     disk3s1
  1. run the dictionary attack
python3 bruteforce.py /dev/disk3s1
#!/usr/bin/env python3
import itertools
import subprocess
import sys
import os
def unlock_volume(volume_id, passphrase):
"""Tries to unlock the volume using the provided passphrase."""
try:
# Run the diskutil command to attempt to unlock the volume
result = subprocess.run(
['diskutil', 'apfs', 'unlockVolume', volume_id, '-stdinpassphrase'],
input=passphrase.encode(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# Print output for debugging purposes
print(f"DiskUtil stdout: {result.stdout.decode().strip()}")
print(f"DiskUtil stderr: {result.stderr.decode().strip()}")
# Return True if the unlock was successful (stdout contains success message)
return result.returncode == 0 and "Unlocked" in result.stdout.decode()
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
return False
def load_words(file_path):
"""Loads the dictionary words from the file."""
with open(file_path, 'r') as f:
return [line.strip() for line in f]
def load_attempted_passwords(log_file):
"""Loads passwords that have already been attempted from used.txt."""
if not os.path.exists(log_file):
return set() # If log file doesn't exist, return an empty set
with open(log_file, 'r') as f:
return set(line.strip() for line in f)
def main(volume_id, dictionary_file, log_file, max_combination_length=2):
# Load dictionary words
words = load_words(dictionary_file)
# Load previously attempted passwords
attempted_passwords = load_attempted_passwords(log_file)
# Open log file to append newly tried passwords
with open(log_file, "a") as log_file_obj:
# Generate combinations of words (up to the specified max length)
for combination_length in range(1, max_combination_length + 1):
for combination in itertools.product(words, repeat=combination_length):
passphrase = ''.join(combination) # No spaces between words
# Skip passwords that have already been attempted
if passphrase in attempted_passwords:
continue
# Log the used passphrase
log_file_obj.write(passphrase + '\n')
log_file_obj.flush()
print(f"Trying passphrase: {passphrase}")
# Try unlocking the volume
if unlock_volume(volume_id, passphrase):
print(f"Success! Passphrase: {passphrase}")
return # Stop on success
print("No valid passphrase found.")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <volume_id>")
sys.exit(1)
volume_id = sys.argv[1] # e.g., "disk2s1"
# Assuming list.txt and used.txt are always in the current working directory
dictionary_file = os.path.join(os.getcwd(), 'list.txt')
log_file = os.path.join(os.getcwd(), 'used.txt')
# Adjust the max_combination_length if you want to try more words in combinations
main(volume_id, dictionary_file, log_file, max_combination_length=5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment