openwall / john

John the Ripper jumbo - advanced offline password cracker, which supports hundreds of hash and cipher types, and runs on many operating systems, CPUs, GPUs, and even some FPGAs
https://www.openwall.com/john/
Other
10.15k stars 2.08k forks source link

mozilla2john.py: Add support for key4.db format #5160

Open AlbertVeli opened 2 years ago

AlbertVeli commented 2 years ago

Description

The mozilla2john.py script included with john supports key3.db files but I noticed mozilla has switched to a new scheme and the new file is called key4.db (an SQLite 3.x database).

Steps to reproduce

I have not investigated if it is possible to extract a hash from this file but if it's possible it could be added to mozilla2john.py or maybe into a separate script.

Hints

This python program imports libnss and checks the master password using that lib. A dive into libnss is needed to investigate what is really happening. A first look suggest to investigate the PK11_CheckUserPassword() function inside libnss. If I find something more out I will add a comment below.

AlbertVeli commented 2 years ago

I created a minimal C-program that only checks the master password for a profile. It uses the same libnss3 calls as the python program.

The C-program might be helpful if anybody else is trying to work this out too. I have not been able to jump into the PK11* functions yet with gdb. No debugging info available for those and for some reason I don't have the libnss3-dbg package available in the package manager.

AlbertVeli commented 2 years ago

After manually building libnss3-dbg and an hour of debugging I found out the magic happens in sftkdb_CheckPassword(). The following sqlite query is issued against key4.db (if it has a master password set):

SELECT ALL * FROM metaData WHERE id="password";

This query returns two binary blobs, the first is the salt (20 bytes) and the second is a ciphertext which in my case was 133 bytes long.

Then the variable key is calculated as sha1($salt + $pw) In my case the salt was 5858a2b6279718549276016132dd0965d8739be9 and the password iloveyou which gave the digest 7c644e61d3b47a0cc3ce7d1335a75f0ec646d6b0, stored in key->data and key->len is 20.

Then the following command is run:

rv = sftkdb_DecryptAttribute(keydb, key, CK_INVALID_HANDLE, CKT_INVALID_TYPE, value, &result);

This is tricky but at least value is from blob2 in the sql query result. After this result gets the value "password-check" if the given password was correct.

The sftkdb_DecryptAttribute function does a number of things which I haven't quite figured out yet. But at least it calls sftkdb_decodeCipherText to decode the value from blob2 in the sql query and puts the result in cipherValue. Then it runs:

*plain = nsspkcs5_CipherData(cipherValue.param, passKey, &cipherValue.value, PR_FALSE, NULL);

where passKey is the sha1 sum (of salt + pw). If plain gets the value "password-check" then all is fine. But there seems to be a number of different algorithms that can be used depending on cipherValue.param. If I interpreted it correctly the algorithm was in my case PBKDF2-SHA256.

I'll continue to look at it this weekend.

AlbertVeli commented 2 years ago

The ciphertext in item2 in the database is DER encoded. I managed to decode it online by pasting a hexdump of the 133 bytes here. Input:

308182306e06092a864886f70d01050d3061304206092a864886f70d0105
0c303504204afcec35e5d9cc2f1be46e2dc0fe2fcfb832effc227b05d6f7
758af6311079d502022710020120300a06082a864886f70d0209301b0609
60864801650304012a040ec034fceccedfa46498879e8a601c0410855dde
00d5742024462a040ceb75092b

Output:

SEQUENCE (2 elem)
Offset: 0
Length: 3+130
(constructed)
Value:
(2 elem)
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.5.13 pkcs5PBES2 (PKCS #5 v2.0)
    SEQUENCE (2 elem)
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.5.12 pkcs5PBKDF2 (PKCS #5 v2.0)
        SEQUENCE (4 elem)
          OCTET STRING (32 byte) 4AFCEC35E5D9CC2F1BE46E2DC0FE2FCFB832EFFC227B05D6F7758AF6311079D5
          INTEGER 10000
          INTEGER 32
          SEQUENCE (1 elem)
            OBJECT IDENTIFIER 1.2.840.113549.2.9 hmacWithSHA256 (RSADSI digestAlgorithm)
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC (NIST Algorithm)
        OCTET STRING (14 byte) C034FCECCEDFA46498879E8A601C
  OCTET STRING (16 byte) 855DDE00D5742024462A040CEB75092B

It looks like this information is then used for decryption together with the key (which is sha1(salt + pw)). The only thing that changes between each password candidate guess is the key. Everything else can be precalculated by the script. I will continue to reverse engineer this during the weekend.

EDIT. This seems to be wrong. While debugging for instance the 14 byte octet string next to last is part of the iv. The full IV is 16 bytes: 040EC034FCECCEDFA46498879E8A601C. The number 10000 is the number of iterations. 32 is the key length. The 32-byte string beginning with 4AFC is another salt and so on. The last 16 byte octet string is the decoded cipherValue at least. But more work is needed to decode the item2 field from the database. Btw, this all happens in the function sftkdb_decodeCipherText().

AlbertVeli commented 2 years ago

I think I solved it. At least for the specific ciphers used in my key4.db. Now the check is also implemented in the python file. It should be moved out of the python file and into john. And the script should be modified to output something that john understands. But at least all the values are correct now for my test key4.db I created. mozilla_key4_2john.zip

Algorithm

There seems to always be two bytes missing from the IV. Those are found directly in front of the rest of the IV bytes in the ciphertext. Now I just assumed that it will always be this way.

magnumripper commented 2 years ago

Good stuff. Our current mozilla2john.py just blindly dumps data at hardcoded offsets - I wonder if we could amend this new script to support both types. The older key3.db is "Berkeley DB 1.85" according to file(1).

magnumripper commented 2 years ago

The older key3.db is "Berkeley DB 1.85" according to file(1).

Perhaps that's a false positive - I can't read it with db_dump(1)

AlbertVeli commented 2 years ago

Good stuff. Our current mozilla2john.py just blindly dumps data at hardcoded offsets - I wonder if we could amend this new script to support both types. The older key3.db is "Berkeley DB 1.85" according to file(1).

Good idea. I'll first of all try to find an old firefox that creates these older database files.

AlbertVeli commented 2 years ago

The older key3.db is "Berkeley DB 1.85" according to file(1).

Perhaps that's a false positive - I can't read it with db_dump(1)

It's too old, but you can use db_dump185 from the libdb1-compat package to dump the information.

Unfortunately it is too old for python to read too so I guess using hard-coded offsets is the best that can be done.