Microsoft Windows Password Storage and Encryption

A lot of articles were written about cracking windows passwords, but they all boiled down to using some kind of software, or superficially described the encryption methods LM and NT, and completely superficially described syskey. I will try to correct this drawback by describing all the details about where passwords are located, in what form, and how the syskey utility converts them.

There are 2 ways to get a password - through the registry, or by gaining direct access to the registry file files. In any case, you will need either SYSTEM user privileges or theft of treasured files, for example, loading from another OS. Here I will not describe the possibilities of gaining access, but for the purposes of research it will be clearer to choose the first option, this will allow not to focus on the structure of the registry bush. A utility will help us start from the systempsExec from sysinternals. Of course, for these purposes, you can use the vulnerabilities of windows, but the article is not about that.

V-block



Windows before Vista by default stored the password in two different hashes - LM and NT. In whist and above, the LM hash is not stored. First, let's see where to look for these hashes, and then we'll figure out what they are.

User passwords, as well as a lot of other useful information, are stored in the registry at HKLM \ SAM \ SAM \ Domains \ Account \ users \ [RID] \ V
, known as the V-block. The SAM section is located in the corresponding file c: \ Windows \ System32 \ config \ SAM . RID - a unique user identifier, you can find it, for example, by looking at the branch HKLM \ SAM \ SAM \ Domains \ Account \ users \ names \ <username>(Default parameter, field - parameter type). For example, the RID of the Administrator account is always 500 (0x1F4), and that of the Guest user is 501 (0x1f5). Access to the SAM section by default is possible only for the SYSTEM user, but if you really want to see it, run regedit with system rights:

PsExec.exe -s -i -d regedit.

To watch the V-block in a convenient form, you can, for example, export it to a text file (File-Export in Regedit).
Here's what we'll see there:

From 0x0 to 0xCC are the addresses of all the data that are in the V-block, their sizes and some additional information about the data. To get the real address you need to the address that we find add 0xCC. Addresses and sizes are stored according to the BIG ENDIAN principle, i.e. you need to invert bytes. 4 bytes are allocated for each parameter, but virtually all parameters fit in one or two bytes. Here's where to look:

Username address - 0xC Username
length - 0x10
LM hash address - 0x9s
LM hash length - 0xa0
NT hash address - 0xa8 NT hash
length - 0xac

In this case, the username is found at offset 0xd4 + 0xcc and its length will be 0xc bytes.
The NT hash will be located at offset 0x12c + 0xcc and its size (always the same) = 0x14.

Another detail regarding the storage of passwords is that 4 bytes are always added to both the NT and LM hashes, the purpose of which is a mystery to me. Moreover, 4 bytes will be present even if the password is disabled. In this case, it is clear that the length of the LM hash = 4 and if you look at its address, you can see these 4 bytes despite the fact that there is no LM hash.
Therefore, when searching for hash offsets, we safely add 4 bytes to the address, and when taking sizes into account, we subtract it. If it’s more convenient to read the code, the search for addresses will look something like this, taking into account the inversion, extra four bytes and adding the starting offset 0xcc (C # code) userVblock - the value HKLM \ SAM \ SAM \ Domains \ Account \ users \\ V as an array of bytes.

int lmhashOffset = userVblock[0x9c] + userVblock[0x9d] * 0x100 + 4 + 0xcc;
int nthashOffset = userVblock[0xa8] + userVblock[0xa9] * 0x100 + 4 + 0xcc;
int lmhashSize = userVblock[0xa0] + userVblock[0xa1] * 0x100 - 4;
int nthashSize = userVblock[0xac] + userVblock[0xad] * 0x100 - 4;
int usernameOffset = userVblock[0xc] + userVblock[0xd] * 0x100 + 0xcc;
int usernameLen = userVblock[0x10] + userVblock[0x1a] * 0x100;


You can read more about the V-block here .

Algorithms



Now we will understand encryption algorithms.
Formation of an NT-hash :
1. The user password is converted to a Unicode string.
2. An MD4 hash is generated based on this string.
3. The resulting hash is encrypted with the DES algorithm, the key is compiled based on the user's RID.
Generating an LM hash :
1. The user password is converted to upper case and padded with zeros to a length of 14 bytes.
2. The resulting string is divided into two halves of 7 bytes and each of them individually is encrypted with the DES algorithm. As a result, we get a hash of 16 bytes in length (consisting of two independent halves of 8 bytes in length).
3. The resulting hash is encrypted with the DES algorithm, the key is compiled based on the user's RID.

4.In windows 2000 and higher, both received hashes are additionally encrypted with the RC4 algorithm using a key known as the "system key" or bootkey generated by the syskey utility, and are encrypted in a rather tricky way.

Consider the general sequence of actions for obtaining the initial password and each step separately
1. We get the bootkey, generate keys for RC4 based on it, decrypt the hashes using RC4
2. We get the keys for DES from the user RIDs, decrypt the DES
3 hashes . Received hashes are brute force attack.

Bootkey



The system key (bootkey) is divided into 4 parts and lies in the following registry keys:

HKLM \ System \ CurrentControlSet \ Control \ Lsa \ JD
HKLM \ System \ CurrentControlSet \ Control \ Lsa \ Skew1
HKLM \ System \ CurrentControlSet \ Control \ Lsa \ GBG
HKLM \ System \ CurrentControlSet \ Control \ Lsa \ Data


The system section is located in the file c: \ Windows \ System32 \ config \ system

It should be noted that the CurrentControlSet section is a link to one of the controlset sections and is created when the system boots. This means that it cannot be found in the system file if the system is inactive. If you decide to search for a key in a file, you need to find out the default ContolSet value in HKLM \ SYSTEM \ Select \ default.
for example, if HKLM \ SYSTEM \ Select \ default = 1 - insteadWe look for HKLM \ System \ CurrentControlSet \ in HKLM \ System \ controlset001 \

Each registry key has a hidden attribute, known as a "class". Regedit won’t show it that easily, but you can see it, for example, if you export these registry keys to text files. In winapi there is a RegQueryInfoKey function to get this attribute .
Fragments are stored in a string representation of hexadecimal numbers, moreover, according to the BIG ENDIAN principle (that is, not a string backwards, but a number).
For example, we found these records: The key assembled from four parts will be an array of bytes: Next, the elements of this array are rearranged based on some constant array p

Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\JD
Class Name: 46003cdb = {0xdb,0x3c,0x00,0x46}
Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Skew1
Class Name: e0387d24 = {0x24,0x7d,0x38,0xe0}
Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\GBG
Class Name: 4d183449 = {0x49,0x34,0x18,0x4d}
Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Data
Class Name: 0419ed03 = {0x03,0xed,0x19,0x04}




scrambled_key = {0xdb,0x3c,0x00,0x46,0x24,0x7d,0x38,0xe0,0x49,0x34,0x18,0x4d,0x03,0xed,0x19,0x04};



int[] p = { 0xb, 0x6, 0x7, 0x1, 0x8, 0xa, 0xe, 0x0, 0x3, 0x5, 0x2, 0xf, 0xd, 0x9, 0xc, 0x4 };
Elements in this array define positions for permutations, i.e. In our example, we get an array: this array is the so-called bootkey . Only it will not take encryption of passwords, but a certain hash based on bootkey, fragments of the f-block and some constants. Let's call it Hashed bootkey.

key[i] = scrambled_key[p[i]];



key[] = {0x4d,0x38,0xe0,0x3c,0x49,0x18,0x19,0xdb,0x46,0x7d,0x00,0x04,0xed,0x34,0x03,0x24 };



Hashed bootkey


to get Hashed bootkey we need 2 string constants (ASCII): We also need the user F-block (HKLM \ SAM \ SAM \ Domains \ Account \ users \\ F), namely its 16 bytes: F [0x70: 0x80] Based of these values, glued into one large array, we form an MD5 hash, which will be the key for RC4 encryption. The last step to get the hashed bootkey is rc4 encryption (or decryption - in rc4 it is the same function) obtained key of the F-block fragment F [0x80 : 0xA0] ; Hashed bootkey in our hands, it remains to learn how to handle it correctly.

string aqwerty = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0";
string anum = "0123456789012345678901234567890123456789\0";





rc4_key = MD5(F[0x70:0x80] + aqwerty + bootkey + anum).



hashedBootkey = RC4(rc4_key,F[0x80:0xA0])



Decrypt passwords with Hashed Bootkey


for LM and NT passwords, we need 2 more string constants - as well as the user's RID in the form of 4 bytes (padded with zeros) and the first half of the Hashed Bootkey ( hashedBootkey [0x0: 0x10] ); All this is glued into a single byte array and MD5 is considered according to the rules: the received md5 hash is the key for rc4, which LM and NT hashes are encrypted in the user's V-block. At this stage, we received the user passwords in the form in which they would be stored without syskey encryption , we can say that the most difficult is behind. Go to the next step.

string almpassword = "LMPASSWORD";
string antpassword = "NTPASSWORD";




rc4_key_lm = MD5(hbootkey[0x0:0x10] +RID + almpassword);
rc4_key_nt = MD5(hbootkey[0x0:0x10] +RID + antpassword);




userLMpass = RC4(rc4_key_lm,userSyskeyLMpass);
userNTpass = RC4(rc4_key_lm,userSyskeyNTpass);



DES


Based on the four bytes of the user's RID, using some permutations and bitwise operations, we create 2 DES keys. Here are the functions that perform obfuscation (C #): Well, there’s nothing much to comment on except the des_set_odd_parity (ref key) function - this is one of the functions of the openssl library , the task of which is to add some “odd bits”, is used to increase the resistance of the key to attacks. Next, we split the NT (or LM) hash into 2 parts of 8 bytes and decrypt it with DES, one half is encrypted with the key generated by the sid_to_key1 function, the second is sid_to_key2. After gluing the two halves, we get the md4 hash in the case of NT, or LanMan (DES) in the case of LM. The resulting hash is completely ready for brute force attack. By the way, md4 hash from an empty password -
private byte[] str_to_key(byte[] str) {
byte[] key = new byte[8];
key[0] = (byte)(str[0] >> 1);
key[1] = (byte)(((str[0] & 0x01) << 6) | (str[1] >> 2));
key[2] = (byte)(((str[1] & 0x03) << 5) | (str[2] >> 3));
key[3] = (byte)(((str[2] & 0x07) << 4) | (str[3] >> 4));
key[4] = (byte)(((str[3] & 0x0F) << 3) | (str[4] >> 5));
key[5] = (byte)(((str[4] & 0x1F) << 2) | (str[5] >> 6));
key[6] = (byte)(((str[5] & 0x3F) << 1) | (str[6] >> 7));
key[7] = (byte)(str[6] & 0x7F);
for (int i = 0; i < 8; i++) {
key[i] = (byte)(key[i] << 1);
}
des_set_odd_parity(ref key);
return key;
}

private byte[] sid_to_key1(byte[] rid) {
byte[] s = new byte[7];
s[0] = (byte)(rid[0] & 0xFF);
s[1] = (byte)(rid[1] & 0xFF);
s[2] = (byte)(rid[2] & 0xFF);
s[3] = (byte)(rid[3] & 0xFF);
s[4] = s[0];
s[5] = s[1];
s[6] = s[2];

return str_to_key(s);
}

private byte[] sid_to_key2(byte[] rid) {
byte[] s = new byte[7];
s[0] = (byte)((rid[3]) & 0xFF);
s[1] = (byte)(rid[0] & 0xFF);
s[2] = (byte)((rid[1]) & 0xFF);
s[3] = (byte)((rid[2]) & 0xFF);
s[4] = s[0];
s[5] = s[1];
s[6] = s[2];

return str_to_key(s);
}





obfskey_l = userNTpass[0x0:0x7]
obfskey_r = userNTpass[0x8:0xF]
byte[] deskey1 = sid_to_key1(RID);
byte[] deskey2 = sid_to_key2(RID);
byte[] md4hash_l = DES(obfskey_l, deskey1);
byte[] md4hash_r = DES(obfskey_r, deskey2);



31d6cfe0d16ae931b73c59d7e0c089c0 The

study was conducted based on the source code ophcrack-3.3.1 , as well as the article Push the Red Button: SysKey and the SAM

Also popular now: