Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better lmPwdHistory and ntPwdHistory decryption #1619

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

almandin
Copy link
Contributor

/!\ This pull request might be a WIP

This PR has to do with secretsdump and the way it extracts hashed passwords histories

What are the changes I made:

  • I added the AES decryption support for the lmPwdHistory attribute. I saw that this attribute is completely AES encrypted nowadays though secretsdump do not use AES for this specific attribute I don't know why;
  • I changed the CRYPTED_HASHW16 structure to indicate that the previously Unknown bit is a Length field for AES decrypted stuff. When decrypting AES encrypted histories (NT and LM), the decrypted blob often has a padding block (16 * \x10), which is DES decrypted and displayed as a hash even though it is not at all ! Using the length field can prevent that (this or removing the 16 * \x10 suffix you can see at the end of the AES decrypted blob);
  • Changed the way histories are displayed : it used to eliminate the first hash in the history (LM and NT), I guess considering that the first is always the same as the legitimate NT/LM hash already displayed anyway. However, I explain below that the first LM hash history can be interesting even though the dbcdpwd attribute contains an "empty" hash. I changed this to display every entry of the histories;
  • I removed the check on the NoLMHash policy to display or not LM hashes, as this policy does not dictate if LM hashes are present or not in thie lmPwdHistory field.

Explainations and context:

I spent a few days fighting against a test environment and extracting many NTDS files with and without the NoLMHash policy, reseting passwords and dumping ntds with my ntdsdotsqlite utility. I found inconsistencies in my software and checked if impacket was impacted because I reused (shame on me) impacket code to decrypt these bits.

I found LM hashes stored in lmPwdHistory that I knew (set myself without the nolmhash policy) that I could not break even with a full hashcat -i -m3000 -a3 ?a?a?a?a?a?a?a attack. My conclusions are the following: Wether the NoLmHash policy is set or not, the lmPwdHistory attribute is updated on password change. When the policy is not set, the LM hashed password is added to the history, when the policy is set, random bytes are set and encrypted in the lmPwdHistory. However, when the policy is set, old passwords are not cleared from the history. It means that it is completely possible to find LM hashes encrypted in the lmPwdHistory attribute even though the policy is set. It means that even though th dbcsPwd attribute contains an "empty" hash, the history can contain useful LM hashes.

On my way to discover this, I managed to find the few bits I mentioned before (aes encrypted lmpwdhistory attribute, the padding block being treated like an encrypted hash, etc...).

With that knowledge, it is possible to see the following problem : it is impossible to tell wether the lmPwdHistory contains random data (the nolmhash policy is and was always set), or if it contains valid lm hashes. The only way to say is to try and crack them.


Why I put a "warning this is WIP" at the beginning

when using drsuapi and not vss to dump data, it only performs ARC4 decryption and not AES, it seems fishy to me but i don't know how to do the proper changes (lines 2365 to 2378 and 2420 to 2437).

if attId == LOOKUP_TABLE['dBCSPwd']:
    if attr['AttrVal']['valCount'] > 0:
        encrypteddBCSPwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
        encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd)
        LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)
    else:
        LMHash = ntlm.LMOWFv1('', '')
elif attId == LOOKUP_TABLE['unicodePwd']:
    if attr['AttrVal']['valCount'] > 0:
        encryptedUnicodePwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
        encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd)
        NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid)
    else:
        NTHash = ntlm.NTOWFv1('', '')
if attId == LOOKUP_TABLE['lmPwdHistory']:
        if attr['AttrVal']['valCount'] > 0:
            encryptedLMHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
            tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory)
            for i in range(0, len(tmpLMHistory) // 16):
                LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)
                LMHistory.append(LMHashHistory)
        else:
            LOG.debug('No lmPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1])
    elif attId == LOOKUP_TABLE['ntPwdHistory']:
        if attr['AttrVal']['valCount'] > 0:
            encryptedNTHistory = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
            tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory)
            for i in range(0, len(tmpNTHistory) // 16):
                NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)
                NTHistory.append(NTHashHistory)
        else:
            LOG.debug('No ntPwdHistory for user %s' % record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1])

Tell me what you think of all this :-)

@almandin
Copy link
Contributor Author

It should fix issue #849 .

@almandin
Copy link
Contributor Author

I did this work with @jindavoll who helped a lot and did at least half the work on this !

@almandin almandin marked this pull request as ready for review September 26, 2023 07:27
@anadrianmanrique anadrianmanrique added the in review This issue or pull request is being analyzed label Sep 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in review This issue or pull request is being analyzed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants