A lot of articles were written about breaking windows passwords, but they all boiled down to using some kind of software, or superficially described LM and NT encryption methods, and completely superficially described syskey. I will try to fix this lack of balance by describing all the details about where the passwords are, in what form, and how the syskey utility converts them.
There are 2 ways to get a password - through the registry, or by getting direct access to registry hive files. In any case, you will need either the privileges of the user SYSTEM, or the theft of the cherished files, for example, by booting from another OS. Here I will not describe the possibilities of obtaining access, but for research purposes it will be clearer to choose the first option, this will allow not to focus on the structure of the registry hive. And the
psExec utility from sysinternals will help us launch from the system. Of course, for these purposes you can use windows vulnerabilities, but the article is not about that.
V-block
')
Windows to Vista, by default, stored the password in two different hashes - LM and NT. In whist and above LM-hash is not stored. First, let's see where to look for these hashes, and then we will understand what they are.
User passwords, as well as many other useful information is stored in the registry at
HKLM \ SAM \ SAM \ Domains \ Account \ users \ [RID] \ Vknown as V-block. The SAM section is located in the corresponding file
c: \ Windows \ System32 \ config \ SAM . RID is a unique user identifier, you can find it out, for example, by looking in the
HKLM \ SAM \ SAM \ Domains \ Account \ users \ names \ <username> branch (Default parameter, field - parameter type). For example, the “Administrator” account RID is always 500 (0x1F4), and 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, start regedit with system privileges:
PsExec.exe -s -i -d regedit.
To observe the V-block in a convenient way, for example, you can export it to a text file (File-Export to Regedit).
This is what we will see there:

From 0x0 to 0xCC are the addresses of all the data that are in the V-block, their size 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. it will be necessary to invert bytes. Each parameter is allocated 4 bytes, but in fact all the parameters fit in one or two bytes. Here is where to look:
User Name Address - 0x
User name length - 0x10
LM Hash Address - 0x9s
LM hash length - 0xa0
NT hash address - 0xa8
NT hash length - 0xac
In this case, the user name is at offset 0xd4 + 0xcc and its length will be 0xc bytes.
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 the front of 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 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 boldly add 4 bytes to the address, and when considering the sizes, we subtract. If it is more convenient to read the code, here’s what the address search will look like, taking into account the inversion, the extra four bytes and the addition of the starting offset 0xcc (C # code)
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;
userVblock - the value of HKLM \ SAM \ SAM \ Domains \ Account \ users \\ V as an array of bytes.
More about the V-block can be read
here .
Algorithms
Now let's look at encryption algorithms.
NT hash generation :
1. The user's password is converted to a Unicode string.
2. MD4-hash is generated based on this string.
3. The resulting hash is encrypted using the DES algorithm, the key is generated based on the user's RID.
LM hash generation :
1. The user's password is converted to uppercase and padded with zeros up to 14 bytes in length.
2. The resulting string is divided into two halves of 7 bytes, and each of them is separately 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 using the DES algorithm, the key is generated based on the user's RID.
4. In windows 2000 and higher, both received hashes are additionally encrypted by the RC4 algorithm using a key known as the “system key” or bootkey generated by the syskey utility and encrypted in a rather tricky way.
Consider the general sequence of actions for obtaining the original password and each step separately
1. Get bootkey, generate keys for RC4 based on it, decrypt hashes with RC4
2. Get keys for DES from RID users, decrypt hashes with DES
3. Obtain hashes by 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 \ DataThe system partition is located in the file c: \ Windows \ System32 \ config \ system
It should be noted that the CurrentControlSet section is a reference to one of the controlset sections and is created at the time of the system boot. This means that you will not be able to find it in the system file if the system is inactive. If you decide to search for a key in a file, you need to know the default ContolSet value in HKLM \ SYSTEM \ Select \ default.
for example, if HKLM \ SYSTEM \ Select \ default = 1 - instead of
HKLM \ System \ CurrentControlSet \ look in
HKLM \ System \ controlset001 \Each registry key has a hidden attribute known as a “class”. Regedit won't show it that easy, but you can see it, for example, if you export these registry keys to text files. In winapi, to get this attribute, there is a
RegQueryInfoKey function.
Fragments are stored in a string representation of hexadecimal numbers, and according to the BIG ENDIAN principle (that is, not a string backwards, but a number).
For example, we found the following records:
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}
The four-part key will be an array of bytes:
scrambled_key = {0xdb,0x3c,0x00,0x46,0x24,0x7d,0x38,0xe0,0x49,0x34,0x18,0x4d,0x03,0xed,0x19,0x04};
Further, the elements of this array are rearranged on the basis of some constant array p
int[] p = { 0xb, 0x6, 0x7, 0x1, 0x8, 0xa, 0xe, 0x0, 0x3, 0x5, 0x2, 0xf, 0xd, 0x9, 0xc, 0x4 };
The elements in this array define the positions for the permutations, i.e.
key[i] = scrambled_key[p[i]];
In our example, we get an array:
key[] = {0x4d,0x38,0xe0,0x3c,0x49,0x18,0x19,0xdb,0x46,0x7d,0x00,0x04,0xed,0x34,0x03,0x24 };
This array is the so-called
bootkey . Only in the encryption of passwords will not he but a certain hash based on the bootkey, fragments of the f-block and some constants. Let's call it Hashed bootkey.
Hashed bootkey
to get the hashed bootkey, we need 2 string constants (ASCII):
string aqwerty = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0";
string anum = "0123456789012345678901234567890123456789\0";
You will also need the user's F-block (HKLM \ SAM \ SAM \ Domains \ Account \ users \\ F), namely, its 16 bytes:
F [0x70: 0x80]Based on these values, glued into one large array, we form an MD5 hash, which will be the key for RC4 encryption
rc4_key = MD5(F[0x70:0x80] + aqwerty + bootkey + anum).
The final step to get the hashed bootkey is rc4 encryption (or decryption - in rc4 is the same function) received by the F-block fragment key
F [0x80: 0xA0] ;
hashedBootkey = RC4(rc4_key,F[0x80:0xA0])
Hashed bootkey in our hands, it remains to learn how to handle it correctly.
We decrypt passwords with Hashed Bootkey
for LM and NT passwords, we will need another 2 string constants -
string almpassword = "LMPASSWORD";
string antpassword = "NTPASSWORD";
as well as the user 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 together into one array of bytes and is considered MD5 according to the rules:
rc4_key_lm = MD5(hbootkey[0x0:0x10] +RID + almpassword);
rc4_key_nt = MD5(hbootkey[0x0:0x10] +RID + antpassword);
received md5 hash key for rc4, which LM and NT hashes are encrypted in the user's V-block
userLMpass = RC4(rc4_key_lm,userSyskeyLMpass);
userNTpass = RC4(rc4_key_lm,userSyskeyNTpass);
At this stage, we received the user's passwords in the form in which they would be stored without syskey encryption, we can say that the most difficult thing is behind. Go to the next step
Des
On the basis of the four bytes of the RID user, with the help of some permutations and bitwise operations, we create 2 DES keys. Here are the functions that obfuscate (C #):
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);
}
Well, there is nothing special to comment here, except the function
des_set_odd_parity (ref key) - this is one of the functions of the
openssl library, whose task is to add some “odd bits”, which is used to increase the key's resistance to attacks.
Then we split the NT (or LM) hash into 2 parts by 8 bytes and decrypt it with DES, one half is encrypted with the key formed by the sid_to_key1 function, the second is sid_to_key2.
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);
After pasting the two halves, we get 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 -
31d6cfe0d16ae931b73c59d7e0c089c0The study was conducted on the basis of the source code
ophcrack-3.3.1 , as well as articles
Push the Red Button: SysKey and the SAM