-
-
Save teddziuba/3ff08bdda120d1f7822f3baf52e606c2 to your computer and use it in GitHub Desktop.
| #!/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) |
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)
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.
Thanks for this! I was able to get the hash from an OSX 10.7 Lion install with a few small changes.
https://gist.github.com/moddedBear/ee67bd9a8d6f77a7158875ca36b0b2d6
this is amazing. been looking for something like this for the longest, thank you so much!!