1#!/usr/bin/env python
2
3# Written by Dhiru Kholia <dhiru at openwall.com> in March, 2013
4# My code is under "Simplified BSD License"
5
6import sys
7import os
8import struct
9from binascii import hexlify
10
11# header structs are taken from vilefault and hashkill projects
12v1_header_fmt = '> 48s I I 48s 32s I 296s I 300s I 48s 484s'
13v1_header_size = struct.calcsize(v1_header_fmt)
14# v2_header_fmt = '> 8s I I I I I I I 16s I Q Q 24s I I I I 32s I 32s I I I I I 48s'
15# encrypted_blob_size can be 64, handle such cases properly too, a value of 48
16# is already handled well
17v2_header_fmt = '> 8s I I I I I I I 16s I Q Q 24s I I I I 32s I 32s I I I I I 64s'
18v2_header_size = struct.calcsize(v2_header_fmt)
19
20PY3 = sys.version_info[0] == 3
21PMV = sys.version_info[1] >= 6
22
23if PY3 or PMV:
24    exec('MAGICv1=b"cdsaencr"')
25    exec('MAGICv2=b"encrcdsa"')
26else:
27    MAGICv1 = "cdsaencr"
28    MAGICv2 = "encrcdsa"
29
30
31def process_file(filename):
32    cno = 0
33    data_size = 0
34
35    headerver = 0
36    try:
37        fd = open(filename, "rb")
38    except IOError:
39        e = sys.exc_info()[1]
40        sys.stderr.write("%s\n" % str(e))
41        return
42
43    buf8 = fd.read(8)
44    if len(buf8) != 8:
45        sys.stderr.write("%s is not a DMG file!\n" % filename)
46        return
47
48    if buf8 == MAGICv2:
49        headerver = 2
50    else:
51        fd.seek(-8, 2)
52        buf8 = fd.read(8)
53        if len(buf8) != 8:
54            sys.stderr.write("%s is not a DMG file!\n" % filename)
55            return
56
57        if buf8 == MAGICv1:
58            headerver = 1
59
60    if headerver == 0:
61        sys.stderr.write("%s is not an encrypted DMG file!\n" % filename)
62        return
63
64    sys.stderr.write("Header version %d detected\n" % headerver)
65
66    if headerver == 1:
67        fd.seek(- v1_header_size, 2)
68        data = fd.read(v1_header_size)
69        if len(data) != v1_header_size:
70            sys.stderr.write("%s is not a DMG file!\n" % filename)
71            return
72
73        data = struct.unpack(v1_header_fmt, data)
74
75        (_, kdf_iteration_count, kdf_salt_len, kdf_salt, _,
76                len_wrapped_aes_key, wrapped_aes_key, len_hmac_sha1_key,
77                wrapped_hmac_sha1_key, _, _, _) = data
78
79        sys.stdout.write("%s:$dmg$%d*%d*%s*%d*%s*%d*%s*%d::::%s\n" % \
80            (os.path.basename(filename), headerver, kdf_salt_len,
81             hexlify(kdf_salt)[0:kdf_salt_len * 2].decode("ascii"),
82             len_wrapped_aes_key,
83             hexlify(wrapped_aes_key)[0:len_wrapped_aes_key * 2].decode("ascii"),
84             len_hmac_sha1_key,
85             hexlify(wrapped_hmac_sha1_key)[0:len_hmac_sha1_key * 2].decode("ascii"),
86             kdf_iteration_count, filename))
87    else:
88        fd.seek(0, 0)
89        data = fd.read(v2_header_size)
90        if len(data) != v2_header_size:
91            sys.stderr.write("%s is not a DMG file!\n" % filename)
92            return
93
94        data = struct.unpack(v2_header_fmt, data)
95        (sig, version, enc_iv_size, _, _, _, _,
96                unk5, uuid, blocksize, datasize, dataoffset, filler1,
97                kdf_algorithm, kdf_prng_algorithm, kdf_iteration_count,
98                kdf_salt_len, kdf_salt, blob_enc_iv_size, blob_enc_iv,
99                blob_enc_key_bits, blob_enc_algorithm, blob_enc_padding,
100                blob_enc_mode, encrypted_keyblob_size, encrypted_keyblob) = data
101
102        fd.seek(dataoffset, 0)
103        cno = ((datasize + 4095) // 4096) - 2
104        data_size = datasize - cno * 4096
105        data_size = int(data_size)
106        if data_size < 0:
107            sys.stderr.write("%s is not a valid DMG file! \n" % filename)
108            return
109        if kdf_salt_len > 32:
110            sys.stderr.write("%s is not a valid DMG file. salt length " \
111                             "is too long!\n" % filename)
112            return
113
114        # read starting chunk(s)
115        fd.seek(dataoffset + int(cno * 4096), 0)
116        chunk1 = fd.read(data_size)
117        if len(chunk1) != data_size:
118            sys.stderr.write("%s is not a DMG file!\n" % filename)
119            return
120
121        # read last chunk
122        fd.seek(dataoffset, 0)
123        chunk2 = fd.read(4096)
124        if len(chunk2) != 4096:
125            sys.stderr.write("%s is not a DMG file!\n" % filename)
126            return
127
128        # output hash
129        sys.stdout.write("%s:$dmg$%d*%d*%s*32*%s*%d*%s*%d*%d*%s*1*%s*%d::::%s\n" % \
130                (os.path.basename(filename), headerver,
131                kdf_salt_len,
132                hexlify(kdf_salt)[0:kdf_salt_len*2].decode("ascii"),
133                hexlify(blob_enc_iv)[0:64].decode("ascii"),
134                encrypted_keyblob_size,
135                hexlify(encrypted_keyblob)[0:encrypted_keyblob_size*2].decode("ascii") + \
136                 "0" * (encrypted_keyblob_size * 2 - \
137                len(encrypted_keyblob) * 2),
138                cno, data_size, hexlify(chunk1).decode("ascii"),
139                hexlify(chunk2).decode("ascii"),
140                kdf_iteration_count, filename))
141
142if __name__ == "__main__":
143    sys.stderr.write("Using 'dmg2john' instead of this program (%s) is recommended!\n\n" % sys.argv[0])
144
145    if len(sys.argv) < 2:
146        sys.stderr.write("Usage: %s [DMG files]\n" % sys.argv[0])
147        sys.exit(-1)
148
149    for i in range(1, len(sys.argv)):
150        process_file(sys.argv[i])
151