Breaking into my MacBookPro10,2
I recently found my old 2012 Macbook Pro, but I forgot the password. I decided to try to get in without it. Here's what happened…
Accessing the filesystem
You can boot in single user mode to get into the filesystem (just hold ⌘-S at boot1). I thought it would be more fun to actually log in, so I decided to keep trying…
Resetting the password
To reset a user's password, you can create a new administrator account from which any password can be overwritten2. An easy way is to boot in single-user mode and run:
$ rm /var/db/.AppleSetupDone
which triggers the setup process at the next boot, allowing you to create a new admin account. But at this point, I decided it would be more fun to recover my actual password…
Cracking the password
First, we need to find the hash stored by the OS. I found a nice guide3 on cracking mac passwords: apparently, a user's password hash resides at /Volumes/<hard drive name>/var/db/dslocal/nodes/Default/users/<username>.plist. I grabbed the relevant plist and attempted to extract the hash:
$ sudo defaults read tanner.plist ShadowHashData | tr -dc 0-9a-f | xxd -r -p | plutil -convert xml1 - -o - 2> /dev/null Property List error: Unexpected character è at line 1 / JSON error: JSON text did not start with array or object and option to allow fragments not set.
No luck. I'm not sure why. I tried another method4 which happened to work:
$ dscl . read tanner.plist dsAttrTypeNative:ShadowHashData | xxd -r -p | plutil -convert xml1 - <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>SALTED-SHA512-PBKDF2</key> <dict> <key>entropy</key> <data> 96kq3nobm2G/jFtdoIsfgzP8m3SCYbGhnI1OdO0rv/ag+8UJOUDUZtTIaH4G MsBLMM8HFtqpH5jAKpJiQhDwmYNWZ64ZAYpV1hUt7UlswDK8HtnTOeZ1x5hI CHFkd0ZbGUziMPRGRuNUv64aOC2ihxFODsAkbuZnrvjsfWTCBZ0= </data> <key>iterations</key> <integer>36900</integer> <key>salt</key> <data> BQn1fkxpi15tLrI6sVCfZs8rVxxE+0q8W94cwIxj3wA= </data> </dict> <key>SRP-RFC5054-4096-SHA512-PBKDF2</key> <dict> <key>iterations</key> <integer>36900</integer> <key>salt</key> <data> YzJlukj/KbexF5fHhQzv8IViPl2/qtsVDJLjNeAUdo0= </data> <key>verifier</key> <data> UfW9fmB6SDEtcJHCd3jOewcOtqc3sg87U2KGKsd1znQh8DULayr4AZCoPJfv JOw+uzQulSIe/utd7g+Toogso6GW1BtfxUuY4wAkJoEAqMO1rsKzVE9BoGJa W18glTHgrbygkjMZEBoVrKXqBHeoWAVFsr2UzQDOfj4XmjHSLUbxfsVkgQSD ehm058nCkq7Sat3CqM1U0k2Lv9cmE+eg0QoOHaBG2PdcV11c0UAyuDOZkQrL rAKcTb51ylGWT8GH2DO0OgCYmMZnjqCgfqPL06omgs/BPxnbEL63epztC01y FriNbRyuOBgawzo9YW+/Z4I9XEYS7rK3d8zVaI+ZiC8WFDYRJFQxbuAVlcUt QIGnMy+DEUFmFIoZmjv+YLA6dngClbQ84MkOa9x3ApfopVNVhaA/20CDsB7s X+y/Z5YJ+MusJd264Yjx1ijR8qrrHSlrpcXo6tKZQsCL0yWDu3UQT8lK6Yst 8Evmx2KmIM9Oxm0DhvHB+djoQO7e6vnK582mh4uMSWsm/SAng+Evs7M0CQtN PhG0qi5MsLiDufjsgluPcksVCtgxev1n2+hzF0TMhb82d8FrRM7ixOIzFPy6 NdCSqIV2Rnxc3HY5DCoTkQvVzA44jGxtBzBkqZz6e6fNggXwn1cehtq4LLa1 evmkn++fUmbk+QzialJqMVg= </data> </dict> </dict> </plist>
Great! Now that we have the hash, we'll use hashcat to crack it. Hashcat expects input in base 16, so we'll need to convert the above hash/salt values which are in base 64. You can perform this conversion any way you want, so I just rewrote a popular script 5:
#!/usr/bin/python2.7 import base64 import sys entropy64 = '96kq3nobm2G/jFtdoIsfgzP8m3SCYbGhnI1OdO0rv/ag+8UJOUDUZtTIaH4GMsBLMM8HFtqpH5jAKpJiQhDwmYNWZ64ZAYpV1hUt7UlswDK8HtnTOeZ1x5hICHFkd0ZbGUziMPRGRuNUv64aOC2ihxFODsAkbuZnrvjsfWTCBZ0=' iterations = '36900' salt64 = 'BQn1fkxpi15tLrI6sVCfZs8rVxxE+0q8W94cwIxj3wA=' entropyRaw = base64.b64decode(entropy64) entropyHex = entropyRaw.encode("hex") saltRaw = base64.b64decode(salt64) saltHex = saltRaw.encode("hex") print("$ml$%s$%s$%s" %(iterations, saltHex, entropyHex))
I ran the script, and:
$ml$36900$0509f57e4c698b5e6d2eb23ab1509f66cf2b571c44fb4abc5bde1cc08c63df00$f7a92ade7a1b9b61bf8c5b5da08b1f8333fc9b748261b1a19c8d4e74ed2bbff6a0fbc5093940d466d4c8687e0632c04b30cf0716daa91f98c02a92624210f099835667ae19018a55d6152ded496cc032bc1ed9d339e675c7984808716477465b194ce230f44646e354bfae1a382da287114e0ec0246ee667aef8ec7d64c2059d
Now we're ready for hashcat. I decided to use a dictionary attack with 14 million of the most common passwords:
$ hashcat -a 0 -m 7100 hash.txt rockyou.txt -w 4 --potfile-path hash.pot hashcat (v5.1.0) starting... * Device #1: WARNING! Kernel exec timeout is not disabled. This may cause "CL_OUT_OF_RESOURCES" or related errors. To disable the timeout, see: https://hashcat.net/q/timeoutpatch OpenCL Platform #1: NVIDIA Corporation ====================================== * Device #1: NVIDIA GeForce GTX 960, 499/1998 MB allocatable, 8MCU Hashfile 'hash.txt' on line 1 ($ml$36...114e0ec0246ee667aef8ec7d64c2059d): Token length exception No hashes loaded. Started: Wed Dec 15 05:03:01 2021 Stopped: Wed Dec 15 05:03:01 2021
We got a "token length exception." It turns out that hashcat expects a 64-byte hash, but we gave it a 128-byte one6. We'll just remove 64 bytes from the hash and try again. Since each byte of the hash is represented in hash.txt in hexadecimal as two ASCII characters, each of which occupies one byte, we need to remove 64*2 bytes from hash.txt:
$ truncate -s=-64 hash.txt > hash-truncated.txt $ hashcat -a 0 -m 7100 hash-truncated.txt rockyou.txt --encoding-from=utf8 --encoding-to=ascii -w 4 --potfile-path ~/hash-truncated.pot hashcat (v5.1.0) starting... * Device #1: WARNING! Kernel exec timeout is not disabled. This may cause "CL_OUT_OF_RESOURCES" or related errors. To disable the timeout, see: https://hashcat.net/q/timeoutpatch OpenCL Platform #1: NVIDIA Corporation ====================================== * Device #1: NVIDIA GeForce GTX 960, 499/1998 MB allocatable, 8MCU Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates Rules: 1 Applicable optimizers: * Zero-Byte * Single-Hash * Single-Salt * Slow-Hash-SIMD-LOOP * Uses-64-Bit Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 256 Watchdog: Temperature abort trigger set to 90c * Device #1: build_opts '-cl-std=CL1.2 -I OpenCL -I /usr/share/hashcat/OpenCL -D LOCAL_MEM_TYPE=1 -D VENDOR_ID=32 -D CUDA_ARCH=502 -D AMD_ROCM=0 -D VECT_SIZE=1 -D DEVICE_TYPE=4 -D DGST_R0=0 -D DGST_R1=1 -D DGST_R2=2 -D DGST_R3=3 -D DGST_ELEM=32 -D KERN_TYPE=7100 -D _unroll' Dictionary cache built: * Filename..: ./rockyou.txt * Passwords.: 14329857 * Bytes.....: 139921497 * Keyspace..: 14329850 * Runtime...: 7 secs $ml$36900$0509f57e4c698b5e6d2eb23ab1509f66cf2b571c44fb4abc5bde1cc08c63df00$f7a92ade7a1b9b61bf8c5b5da08b1f8333fc9b748261b1a19c8d4e74ed2bbff6a0fbc5093940d466d4c8687e0632c04b30cf0716daa91f98c02a92624210f099:[REDACTED] Session..........: hashcat Status...........: Cracked Hash.Type........: macOS v10.8+ (PBKDF2-SHA512) Hash.Target......: $ml$36900$0509f57dbc698b5e6d2eb23ab150b066cf2b571c4...10f099 Time.Started.....: Wed Dec 15 23:37:35 2021 (1 min, 18 secs) Time.Estimated...: Wed Dec 15 23:38:53 2021 (0 secs) Guess.Base.......: File (./rockyou.txt) Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 3385 H/s (268.08ms) @ Accel:512 Loops:128 Thr:64 Vec:1 Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 262144/14329850 (1.83%) Rejected.........: 0/262144 (0.00%) Restore.Point....: 0/14329850 (0.00%) Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:36864-36899 Candidates.#1....: 123456 -> rayburn1 Hardware.Mon.#1..: Temp: 69c Fan: 17% Util:100% Core:1430MHz Mem:3004MHz Bus:16 Started: Wed Dec 15 23:37:20 2021 Stopped: Wed Dec 15 23:38:55 2021
We recovered the password in just over a minute!
Notes
To prevent leaking the recovered password, I flipped bits in both the salt and the hash. However, I used a well-known password dictionary and provided full output from hashcat, which means that the recovered password lies somewhere in the dictionary between the shown candidates (unless I'm lying). If I'm not, then my password could be recovered by hashing the candidates until a hash is found with low edit distance from the above hash. Happy hunting!