Decrypting LSA Secrets

The LSA secrets store is a protected storage area used the the Local Security Authority (LSA) system in Windows to keep important pieces of information safe from prying eyes. As with Syskey, however, we will see that these secrets are only obfuscated, and once the mechanism is known, we can extract them from the registry with ease. Even without knowing the obfuscation algorithm, however, it is possible to read the values of this data using the LsarQuerySecret function exported by lsasrv.dll, but only if one has the LocalSystem privilege (for example, if the process is running under the SYSTEM account).


What kind of data is stored in these secrets? Here are a few that I've come across:


  • $MACHINE.ACC: has to do with domain authentication, see KB175468

  • DefaultPassword: password used to logon to Windows if auto-logon is enabled

  • NL$KM: secret key used to encrypt cached domain passwords

  • L$RTMTIMEBOMB_[...]: FILETIME giving the date when an unactivated copy of Windows will stop working


So it could clearly be of great use in some cases to be able to read these secrets. To take one example, if one wanted to know when the system was installed, one could look at the value of L$RTMTIMEBOMB, and then subtract 30 days (Windows gives you 30 days from the time you installed to activate the product before it stops working). And, of course, dumping the cached domain logon credentials requires us to be able to get the value of NL$KM.


LSA Secrets in the Registry


LSA secrets are stored in the SECURITY hive of the registry, in the key SECURITY\Policy\Secrets. Under that key, each secret has its own key, so NL$KM can be found at SECURITY\Policy\Secrets\NL$KM. Each secret key then has several subkeys associated with it: CurrVal, CupdTime, OldVal, OupdTime, and SecDesc. CurrVal and Oldval are the current and previous values of the secret, and CupdTime and OupdTime are the respective timestamps when the values were updated, as 64-bit FILETIMEs. SecDesc is assumed to be a security descriptor for the secret, but I have not verified this empirically.


The values of the CurrVal and OldVal keys are unnamed, and displayed as "(Default)" in regedit. Even once you obtain the values, however, you won't be able to make much sense of them. The reason for this is that as with the hashes in the SAM, LSA secrets are encrypted with a key that is specific to each machine (in fact, as we will see, it is based on the boot key used in SysKey encryption).


Decrypting the Secrets


The encrypted data of each secret has the following form:


12 bytes Variable len
[ Metadata ][ Encrypted Data ]

The initial metadata portion does not contain much of interest to us (among other things, it has the size of the block of data, which we already know, since it is specified in the key value data structure). In my code, I ignore it and only deal with the data from offset 0xC (decimal 12) onwards.


So to decrypt the secret's information, we will need two things: an encryption key, and knowledge of which algorithm was used. Luckily, a Chinese reverse engineer known to me only as Eyas[at]xfocus.org has done much of this work for us, and written about the process in an article on XFocus. Unfortunately, the article is in Chinese, but Google Translate does a reasonable job on it, and the included x_dialupass2.cpp is happily quite universal.


Perusing this code shows that the author obtained the secret decryption key by searching the memory space of lsass.exe for the string "\x10\x00\x00\x00\x10\x00\x00\x00"; this is because the key was known to be 16 bytes, and it is contained in a data structure whose first two fields are little-endian integers giving the current and maximum size of the contained data. Crude as this method is, it has worked in every case I have tried it, and it works well enough that the author of CacheDump uses it in his utility to decrypt the value of NL$KM.


The encryption algorithm used, however, is still somewhat mysterious. Both x_dialupass2.cpp and CacheDump both rely on a function named SystemFunction005 in advapi32 do perform the actual decryption, and this function is (naturally) entirely undocumented by Microsoft. So we will have to do a bit of work if we want to really understand how this works, and produce our own implementation of it.


SystemFunction005


Some Googling and poking through the Wine source code gives us some initial information: SystemFucntion005 apparently decrypts a variable-length block of data using DES in ECB mode (the relevant code can be found in dlls/advapi32/crypt_lmhash.c.


This description initially seems promising, and, indeed, if we decrypt the first 8 bytes of the secret data using the first 7 bytes of the LSA key, we get sensible-looking data that appears to be two little-endian integers that describe the length of the encrypted data. Unfortunately, past that, using that 7-byte key produces gibberish.


What went wrong? Well, notice that in our first attempt, we only used the first 7 bytes of a 16 byte key. As it turns out, although the Wine implementation does not support this, SystemFunction005 can use keys longer than 7 bytes. The first 7 bytes of the key decrypt the first 8-byte block of data, the next 7 bytes (key[7:14]) decrypt the second 8-byte block, and so on.


When we there are fewer than 7 bytes left in the key data, we start again at the beginning of the key, plus the number of bytes of key data we had left over. So for example, with the 16 byte LSA key, we use the following keys: key[0:7], key[7:14], key[2:9], key[9:16], key[0:7], ... and so on. The function that moves through the key data in advapi32 is called AdvanceKeyData, if you are inclined to verify that I've gotten everything correct.


The only other small wrinkle is that the decrypted data is actually an LSA_BLOB data structure:


typedef struct _LSA_BLOB {
DWORD cbData;
DWORD cbMaxData;
BYTE * szData;
} LSA_BLOB;

Thus, the data returned to the user does not include the first 8 bytes, and will be cbData bytes long.


Obtaining the LSA key


Although searching for the LSA key in memory works, it's a bit crude, and it leaves us with an incomplete picture of how the obfuscation algorithm works. Moreover, it won't work if we don't have access to the original system the secrets were stored on (e.g., if we only have access to the on-disk hives from the system). With the help of some information from Massimiliano Montoro (mao), author of Cain & Abel, we can do better.


As mao describes in a post to the Cain & Abel forums, the LSA key is derived from the registry key SECURITY\Policy\PolSecretEncryptionKey and the boot key used in SysKey. To calculate it, we first obtain an RC4 key from the MD5 hash of the boot key followed by 1000 instances of bytes 60 to 76 of the data in PolSecretEncryptionKey. In Python This looks like:


md5 = MD5.new()
md5.update(bootkey)
for i in range(1000):
md5.update(pol_sec_key[60:76])
rc4key = md5.digest()

We then use this key to decrypt 48 bytes of data from PolSecretEncryptionKey starting at offset 12 (that is, RC4(rc4key, pol_sec_key[12:60])). Bytes 0x10 through 0x20 of the resulting string are precisely the value of the LSA key we need! Using this key, we can decrypt all the LSA secrets as described above.


Final Words


The code that implements the algorithms described here is part of CredDump, and can be found in framework/win32/lsasecrets.py. I hope you've enjoyed this look at another obfuscation algorithm. In the next article, we'll be making use of the knowledge we gained here to tackle the problem of dumping the domain password hashes from a local client.

Comments

ktrader said…
This is an excellent article. Downloaded creddump and playing with it. Question...do you know what is the 12 bytes metadata format ? 1st 4 bytes is the size...
Unknown said…
Good stuff ... but it's no longer true for Vista.

For one thing, the PolSecretEncryptionKey is no longer present.

Secondly the format for the obfuscated secrets has apparently changed.

Popular posts from this blog

Someone’s Been Messing With My Subnormals!

SysKey and the SAM