Skip to content

Instantly share code, notes, and snippets.

@teddziuba
Last active May 1, 2024 16:53
Show Gist options
  • Save teddziuba/3ff08bdda120d1f7822f3baf52e606c2 to your computer and use it in GitHub Desktop.
Save teddziuba/3ff08bdda120d1f7822f3baf52e606c2 to your computer and use it in GitHub Desktop.
Extract a Mac OSX Catalina user's password hash as a hashcat-compatible string
#!/usr/bin/env python3
"""
Mac OSX Catalina User Password Hash Extractor
Extracts a user's password hash as a hashcat-compatible string.
Mac OSX Catalina (10.15) uses a salted SHA-512 PBKDF2 for storing user passwords
(hashcat type 7100), and it's saved in an annoying binary-plist-nested-inside-xml-plist
format, so previously reported methods for extracting the hash don't work.
** You must be root to do this. **
Example Usage:
sudo ./osx_hash_extract.py <username> > hash.txt
hashcat -a 0 -m 7100 --username hash.txt wordlist.dat
"""
import plistlib
import sys
def read_user_plist(username):
plist_path = f"/var/db/dslocal/nodes/Default/users/{username}.plist"
with open(plist_path, "rb") as f:
plist = plistlib.load(f)
return plist
def extract_shadow_hash(user_plist):
# Nested binary plist
nested_bplist = user_plist["ShadowHashData"]
shadow_hash_plist = plistlib.loads(nested_bplist[0])
shadow = shadow_hash_plist["SALTED-SHA512-PBKDF2"]
pbkdf2 = {"iterations": shadow["iterations"],
"entropy": shadow["entropy"][:64].hex(), # Only the first 512 bits
"salt": shadow["salt"].hex()}
return pbkdf2
def format_hashcat(username, pbkdf2):
hc_line = f"{username}:$ml${pbkdf2['iterations']}${pbkdf2['salt']}${pbkdf2['entropy']}"
return hc_line
def main(args):
username = args[1]
user_plist = read_user_plist(username)
shadow = extract_shadow_hash(user_plist)
hc_input = format_hashcat(username, shadow)
print(hc_input)
if __name__ == "__main__":
main(sys.argv)
@mnrkbys
Copy link

mnrkbys commented Jan 20, 2021

@Rickorn622
You should use Python 3 and give an username as the argument of this script.

@sunknudsen
Copy link

Thanks for the script @teddziuba!

Can you please expand on https://gist.github.com/teddziuba/3ff08bdda120d1f7822f3baf52e606c2#file-osx_extract_hash-py-L42.

Only the first 512 bits

Why?

@richwrightnyc
Copy link

this is amazing. been looking for something like this for the longest, thank you so much!!

@jvictors-tp
Copy link

jvictors-tp commented Nov 10, 2023

After considerable debugging, I found it necessary to manually specify the format.
If you get OverflowError, InvalidFileException, or ExpatError, try flipping the format manually. Sometimes you may need

shadow_hash_plist = plistlib.loads(nested_bplist[0], fmt=plistlib.FMT_XML)

and other times you may need:

shadow_hash_plist = plistlib.loads(nested_bplist[0], fmt=plistlib.FMT_BINARY)

@jvictors-tp
Copy link

Thanks for the script @teddziuba!

Can you please expand on https://gist.github.com/teddziuba/3ff08bdda120d1f7822f3baf52e606c2#file-osx_extract_hash-py-L42.

Only the first 512 bits

Why?

I don't know, but I tested it against my own credentials, with my own password in the wordlist, and hashcat cracked it right away.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment