Discussion:
dmg2john's dmg file header parsing
(too old to reply)
Solar Designer
2016-06-10 18:53:03 UTC
Permalink
Dhiru, magnum -

Our dmg2john.c and dmg2john.py assume that the lengths of some dmg file
header fields are fixed, even though the actual lengths are encoded in
the header and are propagated into the output "hash". I got a couple of
dmg header v2 files where:

kdf_salt_len = 20
blob_enc_iv_size = 8
encrypted_keyblob_size = 64

However, dmg2john.py blindly assumes blob_enc_iv_size = 32 and
encrypted_keyblob_size = 48. dmg2john.c appears to similarly assume the
former, but not the latter (so it encodes 32 and 64 instead of 8 and 64).
kdf_salt_len appears to be handled correctly by both.

I have no idea whether any such assumptions are also present in the
actual dmg crackers. Either way, we clearly have this bug in dmg2john*,
where they blindly output erroneous "hashes" instead of processing the
input files correctly or reporting an error.

For dmg2john.py, fixing this problem correctly would require changing
the approach it uses. Right now, it parses the entire header with:

v1_header_fmt = '> 48s I I 48s 32s I 296s I 300s I 48s 484s'
v1_header_size = struct.calcsize(v1_header_fmt)
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'
v2_header_size = struct.calcsize(v2_header_fmt)

It will need to separately parse the length fields and the actual data.
In my case, the "48s" in v2_header_fmt should become "64s", but it would
probably be wrong to patch it in the v2_header_fmt line, because some
other file could in fact need "48s" or something else.

Dhiru, will you fix this properly, please? Thanks!

Alexander
Solar Designer
2016-06-10 19:06:08 UTC
Permalink
Post by Solar Designer
Our dmg2john.c and dmg2john.py assume that the lengths of some dmg file
header fields are fixed, even though the actual lengths are encoded in
the header and are propagated into the output "hash". I got a couple of
kdf_salt_len = 20
blob_enc_iv_size = 8
encrypted_keyblob_size = 64
However, dmg2john.py blindly assumes blob_enc_iv_size = 32 and
encrypted_keyblob_size = 48. dmg2john.c appears to similarly assume the
former, but not the latter (so it encodes 32 and 64 instead of 8 and 64).
kdf_salt_len appears to be handled correctly by both.
Upon a second thought, maybe padding the IV to 32 bytes with zeroes, and
reporting its size in the "hash" as 32, is actually correct for our
cracker?

The obvious bug is different output by dmg2john.c vs. dmg2john.py for
encrypted_keyblob_size = 64, where .py would replace 16 bytes that are
non-zero in the dmg file with zeroes.
Post by Solar Designer
I have no idea whether any such assumptions are also present in the
actual dmg crackers. Either way, we clearly have this bug in dmg2john*,
where they blindly output erroneous "hashes" instead of processing the
input files correctly or reporting an error.
Per the above, I am not so sure about dmg2john.c's handling of IV.

encrypted_keyblob_size other than 48 appears to be supported in the
cracker, as there are two checks like this:

if (cur_salt->encrypted_keyblob_size == 48)
AES_set_decrypt_key(aes_key_, 128, &aes_decrypt_key);
else
AES_set_decrypt_key(aes_key_, 128 * 2, &aes_decrypt_key);

I am not sure if my specific case is supported, though.
Post by Solar Designer
For dmg2john.py, fixing this problem correctly would require changing
v1_header_fmt = '> 48s I I 48s 32s I 296s I 300s I 48s 484s'
v1_header_size = struct.calcsize(v1_header_fmt)
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'
v2_header_size = struct.calcsize(v2_header_fmt)
It will need to separately parse the length fields and the actual data.
In my case, the "48s" in v2_header_fmt should become "64s", but it would
probably be wrong to patch it in the v2_header_fmt line, because some
other file could in fact need "48s" or something else.
Since the cracker seems to only support one other value besides 48,
maybe an acceptable workaround would be to support only 48 and 64 in
dmg2john.py, and error out on anything else. This is a much smaller
change from the current code.
Post by Solar Designer
Dhiru, will you fix this properly, please? Thanks!
Alexander
Solar Designer
2016-06-13 15:05:02 UTC
Permalink
Post by Solar Designer
Upon a second thought, maybe padding the IV to 32 bytes with zeroes, and
reporting its size in the "hash" as 32, is actually correct for our
cracker?
Reviewing dmg_fmt_plug.c and opencl_dmg_fmt_plug.c, I see that they use
only first 8 bytes of IV anyway, and this appears correct (it's IV for
DES, which then gets replaced by 20 bytes of HMAC-SHA1 output and that
is finally used as IV for AES). The IV length specified in the "hash"
is only used to determine how many bytes to decode from hex. valid()
checks for it to be no more than 32, and there's room only for up to 32.
Perhaps we should enhance valid() to check that ivlen is also no less
than 8.

memcpy(iv, cur_salt->iv, 8);

In other words, it should not matter whether it's ivlen=8 followed by 8
bytes, or ivlen=32 followed by 32 bytes where the first 8 are correct.
It would be cleaner to only output 8 bytes into the "hash", though.

The issue with encrypted_keyblob_size 48 vs. 64 in dmg2john.py for v2
headers still looks like a critical bug in that Python script to me.

Dhiru, would you take care of these issues, please?

And we need a .dmg file that we know the passphrase for to experiment
with this. Do we have one with encrypted_keyblob_size = 64? Perhaps
that's what (more) recent OS X uses by default now, so it should be easy
to generate such samples? Can someone try, please? magnum, Dhiru?
(I don't have recent OS X.)

Alexander

Loading...