When a Windows computer is joined to a domain, authentication of users is performed not against the local SAM database, but by querying the domain controller. From this description, we might be tempted to conclude that there won't be any useful credentials stored in the registry on a machine that is part of a domain; the users and their hashes don't actually exist on the local machine but rather on the domain controller.
As it turns out, however, by default Windows does store domain credentials on client machines. The reason for this is simple: if the domain controller is unavailable for some reason, users would still like to be able to log into their machines using their credentials; for this reason Windows caches domain credentials of the last (by default) 10 users to log on to the machine. The exact number of cached logons is controlled by the value "CachedLogonCount" of HKLM\Software\Microsoft\Windows NT\CurrentVersion\WinLogon.
Cached Credentials in the Registry
The cached credentials are stored in the SECURITY hive, as with LSA secrets; specifically, they can be found in the values of HKLM\Security\Cache. This key has a number of values, named NL$1 for the first cached account, NL$2 for the second, and so on. However, as we have come to expect in these matters, the data there is not immediately usable.
The first public tool capable of dumping these credentials was CacheDump, by Arnaud Pilon. He was also kind enough to provide a pretty decent explanation of how the whole process worked, in addition to the code, which makes our job at this point much easier.
As Pilon explains, the data in the NL$?? keys is formatted as follows:
[ metadata ][ CH ][ T ][ Encrypted Data ]
64 bytes 16 16 > 100 bytes
The metadata section is cleartext, and contains such information as:
- Length of the username
- Length of the domain
- Length of the full domain name
The actual username, domain name, and hash data are all stored in the encrypted data block. The CH portion of the data is an apparently random 16 byte key that is used to generate the decryption key for the encrypted data.
Decrypting the Cached Data
The decryption process is actually quite simple. First, we obtain the value of NL$KM from the LSA secrets database and decrypt it, as described in an earlier article. Then we generate an RC4 key by taking the HMAC of CH and use NL$KM as the key for the hash function. In Python this looks like:
hmac_md5 = HMAC.new(nlkm,ch)
rc4key = hmac_md5.digest()
The key is then used to decrypt the data in the NL$?? key. (As an aside, this may be the simplest of the obfuscation techniques we have looked at so far.
Once we have the data decrypted, we can find the domain cached password as the first 16 bytes of the encrypted data. The username, domain, and domain name then appear in a contiguous block starting at offset 72 in the encrypted data. We can use the lengths of these fields from the metadata block to extract the strings; note however that they are aligned to 4 bytes, so if the length is not a multiple of 4, it will be padded with nulls.
Wasn't that easy?
And that's really all that there is to it. For the record, the hash algorithm used on the plaintext password is:
MD4(MD4(password) + username)
where both password and username are little-endian UTF-16 strings.
This concludes our brief tour of the various mechanisms used to obscure credentials in Windows. As we have seen, all of the algorithms, once reverse engineered, can be implemented offline; this is precisely what CredDump does. If you have any lingering questions about how all this works, feel free to check out the code, or shoot me an e-mail. Next time we'll be diving right back into memory analysis, and I'll describe how these same techniques we've explored in the past few articles can be used on what portions of the hive remain in memory on Windows systems.