PWL-файлів (або так звані парольні кеші Windows) — це файли з именамипользователей комп’ютера і з розширенням *.PWL (наприклад, Master.PWL,Sales.PWL тощо), які знаходяться в директорії Windows. Це файли, в яких зберігаються різні акаунти (тобто поєднання логін/пароль)користувача, тобто паролі до всіх ресурсів, при першому підключенні ккоторым у вікні введення пароля був включений прапорець «Запам’ятати пароль».Таким чином, в ньому зберігаються паролі до «розшарені» ресурсів мережі,паролі на підключення до NetWare/NT-серверів, паролі на Dial-Up-з’єднання тощо

Функція запам’ятовування паролів Windows реалізована давно і була введена з«благою» метою полегшення роботи користувачів. І дійсно — зачемвводить кожен раз при підключенні до провайдера пароль «q6Rf8UI24dq» :),коли можна один раз ввести його з папірця, а потім забути про цей парольвообще. Таким чином, Windows збирає всі паролі користувача, шифруетих з допомогою логіна (ім’я користувача) і поточного пароля користувача(тобто того пароля, що вводиться при завантаженні Windows)і зберігає в цьому PWL-файлі.

Природно, всі ці дані зашифровані певними алгоритмами, і для ихдешифрования необхідно знати пароль користувача — а це фактично парольна вхід в Windows. А так як людям властиво забувати свої паролі, то подборпароля, по-перше, дозволяє його «пригадати», а по-друге, позволяетпросмотреть список акаунтів користувачів, які Windows зберігає цьому PWL-файлі, наприклад, там може бути і забутий пароль на підключення кпровайдеру. 😉

У даній статті будуть описані ті методи оптимізації при підборі паролів кPWL-файлів, які були використані при створенні программыPWLInside і которыепозволили зайняти програмі лідируюче місце по швидкості роботи средианалогичных програм.

І відразу обмовлюся, що мова піде тільки про PWL-файлів операційних системWindows’OSR2/98/ME, т. к. в PWL-файлів ОС Windows’3.11/95 методи шифрованиягораздо простіше і підбір пароля будь-якої складності — справа однієї години роботи любойпрограммы по відновленню до них паролів, в тому числі і PWLInside.

Вся інформація в тексті про час виконання фрагментів коду в тактах дана дляследующих типів процесорів:
1. Intel Pentium MMX/II/III і все Celeron’и з цих сімейств.
2. AMD K6-II/III, всі Duron’и і Athlon’и.

Така інформація дається як усереднене час виконання на всіх цих типахпроцессоров.

Приклади коду, наведені нижче, дано в синтаксисі Microsoft Visual З++ v6.0 иМАЅМ v7.0.Формат PWL-файлів Windows’OSR2/98/МЕИнформация про формат PWL-файлів компанією Microsoft ніде і ніколи недокументировалась, і, хоча в Інтернеті можна знайти багато різних документовпо форматів PWL-ок, вся ця інформація взята з практичного использованияэтих файлів і в основному написана авторами програм, аналогічнихPWLInside.

Докладно розглянемо в якості прикладу наступний PWL-файл:

0000: E3 82 85 96 03 00 00 00 02 00 01 00 00 00 00 00
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0100: 00 00 00 00 00 00 00 00 00 0D 03 FF FF FF FF FF
0110: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0120: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0130: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0140: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0150: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0160: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0170: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0180: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0190: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01A0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01B0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01C0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01D0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01E0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01F0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0200: FF FF FF FF FF FF FF FF 52 02 00 00 00 00 00 00
0210: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
0220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0240: 01 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00
0250: 00 00 88 51 47 58 2C 74 13 7C 6F D7 9E 9D 58 59
0260: 0B 3A A5 22 85 22 94 80 58 F9 61 00 B6 51 72 28
0270: D5, D7 3A 58 23 03 DD BC A7 4B E7 E2 71 65 66 CE
0280: 3F 58 FC 59 76 02 F0 12 8E 5F 79 94 39 E0 36 29
0290: 13 B9 8E 3B A1 F3 74 D4 78 38 01 E0 B5 FE DE A3
02A0: 80 CC 4E B7 67 1D 7C 36 7B C5 AA 76 4C D0 8E EE
02B0: 28 78 8A BB 7A 5A 81 2C AB 29 79 97 33 89 60 79
02C0: F7 6C 1C 72 1B 33 0A 09 F2 7E E4 3A A6 BE F4 C6
02D0: AE 06 DE 31 67 BB EA 7B D5 CA AE 01

Тепер уважно подивимося, з яких полів складається файл:

1. Відразу додам наступні обмеження PWL-файлів: у файлі можетнаходится максимум 16 блоків ресурсів. Максимальноеколичество ресурсів у файлі — 255. Це обмеження самої Windows. У каждомблоке може розташовуватися будь-яку кількість ресурсів, але їх суммарноеколичество не може бути більше 255. І ще одне обмеження PWL-файлів – те, що він не може бути більше 64кБ.

2. Отже, перше, що ми бачимо — це сигнатура (тобто перші 4 байти файлу),яка дорівнює 0x968582E3, відразу ж зауважу, що у PWL-файлів отWindows’3.11/95 сигнатура інша — 0x4E464DB0.

3. По зсуву 0x4 слід DWORD з невідомим вмістом.

4. По зсуву 0x8 слід WORD. Він визначає загальну кількість воресурсов у файлі. У нашому прикладі — 2 ресурсу.

5. Починаючи з адреси 0x00A до адреси 0x109 располагаетсястранная таблиця розміром 255 байт. Яку вона містить інформацію,відомо лише Microsoft. Є припущення, що там зберігаються номераресурсов, хоча ця таблиця для декодування даних з файлу впринципі не потрібна.

6. Починаючи з адреси 0x109 до адреси 0x208 находитсядругая таблиця (теж розміром 255 байт), у якій зберігається такаяинформация: будь байт з цієї таблиці дорівнює i (крім 0xFF)означає, що i-й блок містить ресурси. Кількість однакових байтравных i в даній таблиці відображає кількість ресурсів в i-му блоці.У нашому прикладі — 1-й байт в таблиці показує, що у нас имеютсяресурсы в 13-му (0x0D) блоці, а 2-й байт в таблиці показує, що у нас є ресурси в 3-му блоці ресурсів.

7. За адресою 0x208 знаходиться DWORD (у нас він дорівнює 0x00000252),який визначає зміщення CryptoSign. До речі, я в цьому полі інших значенийне бачив ні в одній PWL-ке!

8. З адреси 0x20C за 0x24C розташований масив CryptoSeed[16],він необхідний для декодування всіх 16 блоків ресурсів.

9. За адресою 0x24C розташовується CheckSeed (DWORD), з якого иначинается декодування PWL-файлів.

10. Далі йдуть два нульових байта. Яку вони несуть функцію — невідомо.

11. За адресою 0x252 розташований масив CryptoSign (16 байт).

12. За адресою 0x262 розташований масив CheckSign (16 байт) — цей масив разом з CryptoSign є «контрольним значенням»для визначення правильності пароля. Їх застосування розглянемо нижче.

13. За адресою 0x272 розташований масив з 15 WORD’ів — це адреси 15блоков ресурсів, починаючи з другого. Адреса першого ресурсу завжди один итот ж і становить 0x290. Ці адреси вже є зашифрованими.Подивимося, що це будуть за байти після декодування правильним паролем:

0270:…… 92 02 94 02 96 02 B2 02 B4 02 B6 02 B8 02
0280: BA 02 BC 02 BE 02 C0 02 C2 02 C4 02 D8 02 DA 02

Як ми бачимо — дійсно там знаходяться адреси! Ці адреси ніколи не можуть бути однаковими і виходить, що якщо блок ресурсів порожній, то він всеодно займає 2 байти — це ми бачимо на початкових адрес: 0x292,0x294 значення посилаються на «сміття», ресурси жев цьому файлі знаходяться за адресами 0x296… 0x2B2 і 0x2C4… 0x2D8 — це видно тому, що різниця між цими сусідніми адресами більше двох байт і т. до. ми ужеотмечали, у нас 3-й і 13-й блок мають ресурси (див. пункт 6).

14. А з адреси 0x290 починаються безпосередньо ресурси. Ці дані такжезашифрованы. Після дешифрування ми побачимо, що з адрес 0x296 і 0x2C4 действительноесть щось, схоже на ресурси 🙂

0290: 4C 9C 50 94 C9 82 1A 00 0A 00 08 00 01 03 43 52 |L. P………..CR
02A0: 49 53 54 49 41 4E 5C 44 68 65 6C 6C 67 61 74 65 |ISTIANDhellgate
02B0: E3 A8 CF DD 80 8A 8D 9A 1B 97 6B B9 BD F0 AE 9A|….?…..k…..
02C0: 5C D4 20 88 12 00 05 00 05 00 00 04 4D 41 50 49 |. ………MAPI
02D0: 00 4D 41 50 49 00 28 F3 1D B2 90 80 |.MAPI.(….?

Як правильно декодувати ресурси та їх формат буде розглянуто нижче.Опис алгоритмів, використовуваних для шифрування PWL-файловНиже докладно опишемо ті алгоритми, які використовуються при шифруванні даних вPWL-файли. Т. к. докладні описи RC4 і MD5 можна знайти в різних джерелах, то яопишу їх поверхнево, оскільки вважаю, що читач знає принципи работыэтих алгоритмів, або сам зможе знайти в Інтернеті докладні їх опису,хоча б в тих же RFC.RC4Короткий опис: на вході маємо ключ розміром 16 байт, на виході отримуємо массивиз 256 байт, у якому рівномірно і псевдовипадково розподілені байти від 0 до 255,причому їх розподіл залежить від вхідного ключа:

byte t; //Тимчасова осередок
byte A = 0; //Тут будемо отримувати новий псевдовипадковий індекс
byte M[256]; //Наш формується масив
byte Key[16]; //Вихідний ключ, з допомогою нього будемо формувати масив M

for (int i = 0; i M[i] = i; //Послідовно заповнюємо масив значеннями від 0 до 255

for (int i = 0; i {
A += (M[i] + Key[i % 16]); //Обчислюємо новий індекс для обміну байтами
t = M[i];
M[i] = M[A]; //Міняємо місцями i-й байт байт і по обчисленому індексом A
M[A] = t;
}

Далі за цим алгоритмом байтами з масиву M з допомогою завершальної процедури RC4 іззастосуванням операції XOR розшифровуються всі необхідні дані.MD5MD5 — це не що інше, як операція згортки 64 байт до 16.
Подивимося, як ця функція описана в офіційному документі — RFC1321 з невеликими спрощеннями і нашимикомментариями:


#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21

/* F, G, H and I are basic MD5 functions */
/* Основні макроси перетворення */

#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))

/* ROTATE_LEFT rotates x left n bits */
/* Цей макрос — це лише елементарна команда циклічного зсуву ROLна Асме! Оригінальний варіант ротації на С працює швидше на процесорах сархитектурой RISC. Для x86 процесорів краще використовувати команду ROL */

#define ROTATE_LEFT(x, n) (((x) > (32-(n))))

/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.Rotation is separate from addition to prevent recomputation */
/* Основні макроси трансформації значень a, b, c і d */

#define FF(a, b, c, d, x, s, ac) {
(a) += F ((b), ©, (d)) + (x) + (UINT4)(ac);
(a) = ROTATE_LEFT ((a), (s));
(a) += (b);
}

#define GG(a, b, c, d, x, s, ac) {
(a) += G (b), ©, (d)) + (x) + (UINT4)(ac);
(a) = ROTATE_LEFT ((a), (s));
(a) += (b);
}

#define HH(a, b, c, d, x, s, ac) {
(a) += H ((b), ©, (d)) + (x) + (UINT4)(ac);
(a) = ROTATE_LEFT ((a), (s));
(a) += (b);
}

#define II(a, b, c, d, x, s, ac) {
(a) += I ((b), ©, (d)) + (x) + (UINT4)(ac);
(a) = ROTATE_LEFT ((a), (s));
(a) += (b);
}

/* MD5 basic transformation. Transforms state based on block */
/* Безпосередньо сам алгоритм MD5 */

static void MD5Transform (state, block)
{
UINT4 a,b,c,d,state[4], x[16];

a = state[0] = 0x67452301;
b = state[1] = 0xefcdab89;
c = state[2] = 0x98badcfe;
d = state[3] = 0x10325476;

/* Round 1 */
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

/* Round 2 */
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG (a, d, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG (a, d, b, c, x[10], S22, 0x2441453); /* 22 */
GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG (a, d, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG (a, d, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

/* Round 3 */
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */

/* Round 4 */
II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */

state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}

У підсумку отримуємо з вхідного масиву x (16 DWORD’ів) масивstate (всього 4 DWORD’a).Завершальна процедура XorT алгоритму RC4Ця частина RC4 (не описана вище), яка декодує необхідні данныес допомогою масиву M:


//Масив M переробляється за допомогою RC4

byte t; //Тимчасова осередок
byte A = 0; //Тут будемо отримувати новий псевдовипадковий індекс

for (byte i = 1; i {
A += M[i]; //Обчислюємо новий індекс для обміну байтами
t = M[i];
M[i] = M[A]; //Міняємо місцями i-й байт байт і по обчисленому індексом A
M[A] = t;
//
t = M[i] + M[A]; //Обчислюємо ще один індекс
Data[i — 1] ^= M[t]; //Декодируем 32 байта масиву Data
}Стандартний алгоритм відновлення паролів до PWL-файлів1. Зчитуємо з вихідного PWL-файлів по зсуву 0x24C — CheckSeed (DWORD).
2. -//- по зміщенню 0x252 — CryptoSign (16 байт).
3. -//- по зміщенню 0x262 — CheckSign (16 байт).
4. Формуємо масив X (розміром 64 байта) наступним чином:
– 0xFFFFFFFF (DWORD)
– Логін у верхньому регістрі
– 0x0 (1 байт)
– CheckSeed (DWORD)
– 0x80 (1 байт)
– 0x0 (адреса 0x37 включно)
– За адресою 0x38 записуємо (0x48 + (довжина логіна – 0x0 (до кінця масиву)
5. Виконуємо MD5 (масив X), отримуємо масив Cnt (16 байт), тобто виробляємо згортку логіна.
6. Формуємо масив Y (розміром 64 байта) наступним чином:
– Логін у верхньому регістрі
– 0x0 (0x11 байт)
– 0x80 (1 байт)
– 0x0 (адреса 0x37 включно)
– За адресою 0x38 записуємо (0x88 + (довжина логіна – 0x0 (до кінця масиву)
7. Формуємо масив Z (розміром 64 байта) наступним чином:
– Пароль
– 0x0 (1 байт)
– Cnt (16 байт)
– 0x80 (1 байт)
– 0x0 (адреса 0x37 включно)
– За адресою 0x38 записуємо (0x88 + (довжина пароля – 0x0 (до кінця масиву)
8. Виконуємо MD5 (масив Z), отримуємо масив Key (16 байт), тобто виробляємо згортку пароля.
9. Виконуємо RC4, використовуючи в якості ключа Key.
10. Копіюємо під тимчасовий буфер Temp (32 байти):
– CryptoSign (16 байт)
– CheckSign (16 байт)
11. Виконуємо процедуру XorT (масив M, масив Temp), отримуємо модифікований масив Temp.
12. Копіюємо перші 16 байт з масиву Temp в буфер Y з адреси (довжина логіна + 1)
13. Виконуємо MD5 (масив Y), отримуємо масив TempKey (16 байт).
14. Порівнюємо 16 байт масиву TempKey і другі 16 байт з масиву Temp і якщо вони не рівні,то інкремент пароля і повернення на пункт 7, інакше — пароль вірний!
Алгоритм декодування ресурсів з PWL-файлів з правильним паролем1. Після знаходження правильного пароля проганяємо функцію XorT з индексамине 1…33, а 33…63. Таким чином ми декодируем 15 адрес блоків з ресурсами.Повинні вийти значення типу 0x292, 0x294 і т. д. Як ми пам’ятаємо, адреса 1-гоблока завжди дорівнює 0x290. Таким чином, у нас буде масив Res[17] типу WORD,перше значення — 0x290, далі 15 декодованих адрес, а останній WORD — це розмір файлу (в наведеному вище прикладі це буде значення 0x2DC).
2. Далі слід цикл на 16 (перевірка блоків з ресурсами), на початку еговычисляем різницю між сусідніми адресами N — якщо різниця між нимиравна 2, то перехід на наступну адресу.
3. Якщо N > 2, то даний i-й блок містить ресурси.
4. Формуємо новий масив X (розміром 64 байта) наступним чином:
– 0xFFFFFFFF (DWORD)
– Логін у верхньому регістрі
– 0x0 (1 байт)
– CryptoSeed[i] (DWORD) — це значення беремо з масиву за адресою 0x20C, причому i — це номер блоку ресурсів.
– 0x80 (1 байт)
– 0x0 (адреса 0x37 включно)
– За адресою 0x38 записуємо (0x48 + (довжина логіна – 0x0 (до кінця масиву)
5. Виконуємо MD5 (масив X), отримуємо масив Cnt (16 байт), тобто виробляємо згортку логіна з потрібним CryptoSeed.
6. Формуємо масив Z (розміром 64 байта) наступним чином:
– Пароль
– 0x0 (1 байт)
– Cnt (16 байт)
– 0x80 (1 байт)
– 0x0 (адреса 0x37 включно)
– За адресою 0x38 записуємо (0x88 + (довжина пароля – 0x0 (до кінця масиву)
7. Виконуємо MD5 (масив Z), отримуємо масив Key (16 байт), тобто виробляємо згортку пароля.
8. Виконуємо RC4, використовуючи в якості ключа Key.
9. І тепер отриманим масивом M починаємо декодувати весь блок з ресурсамидлиной N процедурою, аналогічною XorT. Причому починаємо використовувати масив Мтакже з 1-го значення (не з нульового(!)) до 255, якщо ресурс більше 255символов, то i «перевалює» байтовую кордон і вже масив Мначинает використовуватися з нуля, а не з одиниці.
10. Подивимося на наведеному вище прикладі структуру першого з наших декодованих ресурсів:

0290:……………… 1A 00 0A 00 08 00 01 03 43 52 |…………..CR
02A0: 49 53 54 49 41 4E 5C 44 68 65 6C 6C 67 61 74 65 |ISTIANDhellgate

Її формат такий:
довжина ресурсу (WORD), в нашому прикладі — 26(0x1A) байт.
довжина логіна (WORD), в нашому прикладі — 10 символів.
довжина пароля (WORD), в нашому прикладі — 8 символів.
BYTE, призначення якого поки точно не відомо.
тип зберігається ресурсу (BYTE): 1 = NT Domain
2 = NT Server
3 = Shared
4 = MAPI
6 = Dial-Up
18 = NetWare
19 = WWW
Далі розташовуються логін, а після нього — пароль.
У нашому прикладі — тип ресурсу «Shared»логін «CRISTIAND», пароль «hellgate».(Примітка: для Shared-ресурсів запис CRISTIAND буде означати наступне:CRISTIAN — ім’я комп’ютера, а D — ресурс, наданий для загального користування.)
11. Далі аналізуємо поточний блок з ресурсами, поки не перевалили за N,«поглядаючи» в таблицю за адресою 0x109, т. к. в PWL-файлів междублоками ресурсів дуже часто буває «сміття» (несповідимі путиMicrosoft), а в цій таблиці буде точна вказівка — в якому блоці сколькоресурсов.Оптимізація алгоритмів відновлення паролейВот і починається найцікавіше. 😉
Подивимося, що ж ми можемо зробити для підвищення швидкодії вищенаведених алгоритмів.RC4 (1-я частина)1. Відразу ж впадає в очі ініціалізація масиву значеннями від 0 до 255, котороепроисходит при кожному новому значенні ключа (тобто фактично при кожному новому пароль).Як же можна зробити її більш ефективною?

Вихід є — ініціалізувати масив не побайтно (256 команд), а, наприклад,використовуючи 32-бітні регістри процесора, по 4 байти за 64 команди — і дійсно, виграш в часі буде в 4 рази (звичайно ж, еслимассив M вирівняний мінімум за межі DWORD’a). А чи є ще більш«широкі» регістри, щоб за одну команду «махом»записувати більшу кількість байт? Регістри FPU відпадають, оскільки операції з нимивыполняются дуже довго, залишаються, звичайно ж, MMX-регістри:

.DATA
qwInit DQ 0706050403020100h
qwAdd DQ 0808080808080808h

.CODE
mov edi,offset M+128
movq mm0,qword ptr [qwInit]
movq mm1,qword ptr [qwAdd]
movq [qword ptr edi-128],mm0

paddb mm0,mm1
movq [qword ptr edi-120],mm0
paddb mm0,mm1
movq [qword ptr edi-112],mm0

paddb mm0,mm1
movq [qword ptr edi+112],mm0
paddb mm0,mm1
movq [qword ptr edi+120],mm0

Щоб не писати 31 однаковий фрагмент коду, набагато простіше использоватьзарезервированный макрос Асемблера IRP, тоді останні рядки кодаможно замінити на наступну конструкцію:

IRP i,
paddb mm0,mm1
movq [qword ptr edi+(i*8)],mm0
ENDM

Разом отримуємо — на ініціалізацію масиву з 256 байт витрачаємо 66 командпроцессора, які виконуються за ~35-40 тактів, т. к. команди PADDB і MOVQ выполняютсясинхронно.
Неважко здогадатися, ЩО навернув би на місці цього циклу будь-компілятор С,якщо цей код писати не на Асме. 😉

У читача, напевно, виникло питання — чому ми ініціалізуємо EDI не наначало масиву M, а на його середину?
Просто справа в тому, що при такому варіанті програми додаткове зміщення кEDI буде приводити до збільшення довжини команди MOVQ всього на один байт(знаковий діапазон -128 до+127) та команда отримує довжину 4 байта.А якщо, наприклад, додати до EDI +256, то зсув буде розширено до DWORD’аі довжина команди збільшиться до 7 байт. Використання більш коротких командявляется кращим, оскільки йде більш «туге» заповнення буферапредвыборки команд і більше оптимальне їх декодування процесорами.

І ще — вдумливий читач скаже, що є ще більш «широкі»XMM-регістри — ті, які використовуються в наборах команд SSE (які, до речі,підтримують і Athlon’и) і SSE2 від Intel. Використовуючи їх, можна оперувати спамятью блоками по 16 байт за такт!

Дійсно, це так. У PWLInside не була включена підтримка ЅЅЕтолько з причини недостатнього поширення таких процесорів в той час, коли створювалася програма. Цілком можливо, що в следующейверсии програми підтримка SSE буде присутній.RC4 (2-я частина)Тепер розглянемо «перетасовку» байт в масиві M, використовуючи вхідний ключ.
Тут виходить така картина — на обробку одного байта масиву M необходимоминимум 7 команд, покажемо це на прикладі однієї ітерації:

xor eax,eax; в AL будемо зберігати індекс A
mov ebp,offset Key
mov edi,offset M

add al,byte ptr[ebp+i] ;A += Key[i % 16]
mov dl,byte ptr [edi+i]; Зчитуємо M[i]
add al,dl ;A += M[i]
movzx esi,al
mov bl,byte ptr [edi+esi]; Зчитуємо M[A]
mov byte ptr [edi+esi],dl
mov byte ptr [edi+i],bl

Причому така конструкція має ряд особливостей:

1. Використання команди MOVZX ESI,AL усуває наступний конфлікт на процесорах Intel:звернення до 32-бітного регістру відразу після модифікації його молодшої (16 – або 8-бітної)частини. У цьому випадку до часу виконання команди додається кілька тактів штрафу.До речі, на процесори від AMD таких штрафів немає. 🙂

2. Конфлікти з читання/запису в одну кеш-лінійку процесора.
Відомо, що при зверненні до комірки пам’яті процесор зчитує з пам’яті (або кэша2-го рівня) не одну клітинку, а цілий рядок (наприклад, 32 байти), яку иразмещает в кеш 1-го рівня. При цьому ВСЯ ця рядок позначається як доступна либотолько для читання або запису. Тому, якщо прочитати байт за адресою X, а потім записати байт за адресою (X + 1), то незважаючи на те, що байт за адресою (X + 1) вже в кеші(!), процесор після виконання першої команди повинен спочатку «звільнити»всю рядок, а лише потім знову її завантажити, але вже для запису в неї, що,природно, призводить до штрафів. Але, оскільки алгоритм формує рівномірне распределениебайт, то такі конфлікти виникають нечасто.

3. Можливо, є ще ряд конфліктів саме по роботі з пам’яттю, т. к. такі алгоритми — це для процесорів не найбільш «зручний» код 🙂

У підсумку така конструкція виконується в середньому за 5 тактів, т. до. все этикоманды прості і на процесорах P-II/III від Intel може декодуватися усіма 3-мядекодерами. Таким чином, декодування може відбуватись по 3 команди за такт, чточастично компенсує вищеописані штрафи.
А на процесори від AMD цифра 5 тактів виходить з-за того, що деякі з этихкоманд не залежать один від одного і після декодування наступна команда начинаетвыполняться, коли попередня ще закінчує своє виконання у конвеєрі.

Разом на весь алгоритм RC4 йде: 5 x 256 = приблизно 1280-1300 тактів.
Звичайно ж, мається на увазі, що ніяких циклів немає і код весь «розгорнутий».

Начебто нічого з цим вдіяти вже не можна, але допитливий розум підказує, що ітут можна «рибку половити». 😉

І дійсно, розглянемо ту частину коду, де обчислюваний індекс Асуммируется з байтом з масиву Key.

Індекс — це ж один байт(!), який підсумовується з поточним байтом M[i].
А потім підсумовується з байтом з масиву Key.

А якщо відразу підсумувати два байти (однією командою) для 2-х різних ключів і,відповідно, 2-х різних паролів не в 8-бітному регістрі, а в 16-бітному?Сказано — зроблено.

В результаті швидкість впала.
Да-а-а, значить приріст від цього не перекрив збільшення «накладныхрасходов» з-за того, що тепер у програмі формується два масиви Z,два масиву M та ін. Плюс штрафи від використання 16-бітних команд в 32-битномкоде.

Ну що ж, а якщо одночасно 4 байти для 4-х паролів? 😉
Та-а-ак, тут вже відносно початкового (однопарольного) варіанти естьприрост продуктивності на 3-5%.

А якщо ще «ширше»? 🙂
«Звичайно ж, MMX!» — скажете Ви і будете праві! :))

І ось ми приходимо до того варіанту, який і був реалізований в PWLInsideі дав приріст швидкості щодо початкового варіанту на 20-25%. Все просто:

1. Формуємо 8 масивів M, які розташовуються в пам’яті таким чином:
— 0-й байт 1-го масиву
— 0-й байт 2-го масиву

— 0-й байт 8-го масиву
— 1-й байт 1-го масиву
— 1-й байт 2-го масиву

— 1-й байт 8-го масиву
і так далі.
2. Формуємо 8 паролів, що йдуть підряд» при переборі обраним алфавітом.
3. На основі цих восьми паролів через виклик MD5 формуємо 8 масивів Key в такому ж порядку:
— 0-й байт масиву Key від 1-го паролю
— 0-й байт масиву Key від 2-го паролю

— 0-й байт масиву Key від 8-го паролю
— 1-й байт масиву Key від 1-го паролю
— 1-й байт масиву Key від 2-го паролю

— 1-й байт масиву Key від 8-го паролю
і так далі.
4. І ось що отримуємо — тепер всі 8 індексів для різних паролів зберігаються в одному(!) MMX-регістрі, в різних його 8-бітних частинах.
Пам’ятайте рядок:

A += (M[i] + Key[i % 16]);

Тепер цей фрагмент коду для 8-ми паролів і ключів, відповідно) выполняетсядвумя MMX-команд:

mov edi,offset M
mov ebp,offset Key

pxor mm0,mm0; Початкові значення обнуляем

; Ось цей фрагмент:
paddb mm0,qword ptr [edi]; Всі вісім A += M[i]
paddb mm0,qword ptr [ebp]; Всі вісім A += Key[i % 16]

Ось у чому незаперечна гідність MMX-регістрів — можливість оперироватьс 8-, 16 – або 32-бітними частинами всього регістру незалежно один від одного!

5. Після цього виділяємо байти з MM0 будь-яким способом, формуємо адреси, міняємо байти в масивах M і далі все те ж саме, що вище.

6. І, звичайно ж, ініціалізація масивів M буде така ж, що наведена вище — по 40 тактів на один пароль. Але 8 разів.

Такий прийом призвів до того, що на прохід алгоритму RC4 на один ключ/пароль йде~980…1000 тактів, що означає в середньому менше 4-х тактів на обробку одного байта вмассиве M!

А якщо використовувати XMM-регістри, то можна аналізувати 16 паролів одночасно і этодаст ще приріст швидкості, але тільки на тих процесори, які підтримують наборкоманд SSE.

Спробуйте, може такий «прорив» в області восстановленияпаролей до PWL-файлів зробите саме Ви! 😉MD5Перший момент в оптимізації цього алгоритму — це заміна следующихмакросов з 4-ма логічними операціями:

#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))

На макроси з 3-ма логічними операціями:

#define F(x, y, z) (((( y) ^ (z)) & (x)) ^ (z))
#define G(x, y, z) (((( x) ^ (y)) & (z)) ^ (y))

Звичайно ж, це дасть невеликий, але все-таки приріст в швидкості.

Другий момент полягає в наступному: придивимося уважно до пункту 7стандартного алгоритму і побачимо, що якщо ми обмежимо пароль для перебору 16-тьюсимволами (т. к. на велику глибину перебір вже непотрібний), то при формуванні буфераZ у нас залишаються кілька DWORD’ів, які заповнені нулями. Подивимося, які саме:

16 символів пароля + 1 байт нуля + Cnt (16 байт) + 1 байт (0x80) = разом 34 байта або 9 DWORD’ів.

Далі у нас йдуть нулі, до кінця ж масиву зайнятий ще тільки один DWORD — x[14].

І фактично порожні DWORD’и — це x[9], x[10], x[11], x[12], x[13] та x[15].

Тому, у всіх 64 ітераціях (4 блоки по 16 рядків) алгоритму MD5, скрізь, де крезультату додається якийсь з цих DWORD’ів, то його можна не додавати взагалі — там же нуль!

Цей нехитрий маневр збільшує швидкість даної конкретної реалізації MD5 ще на 8-10%.

До речі, цю ідею можна розвинути і далі:
1. При довжині пароля 8..12 символу ігнорувати x[8].
2. При довжині пароля 4..8 символу ігнорувати x[7] та x[8].
3. При довжині пароля 1..4 символу ігнорувати x[6], x[7] та x[8].

Що ще трохи, звичайно, але все-таки збільшить швидкість перебору.

Разом — після всіх цих маніпуляцій час виконання алгоритму MD5 стало займати ~280-320тактов, тобто в середньому близько 5 тактів на кожну з 64 ітерацій. Непогано. 🙂

Але, вже створюючи цю статтю, у мене з’явилися певні думки, як можна ещеоптимизировать цей код. ;)Цілком можливо, що в наступній версії PWLInside буде ще щось новенькоев плані оптимізації MD5!

Я також сподіваюся, що вищенаведена інформація по оптимізації RC4 і MD5, можливо,допоможе кому-небудь, хто використовує ці алгоритми у своїх розробках і дозволить сминимальными «витратами» зробити ці програми ще більш швидкими 🙂Стандартний алгоритм відновлення паролів до PWL-файлівУвагу! А на «десерт» — саме смачненьке ;), що дозволило свого часу програмі PWLInside істотно збільшити швидкість перебораи по цьому параметру здорово «відірватися» від своїх конкурентів.

Виявляється, для швидкої оцінки — той пароль, можна пункти 10…14 не виконувати,а значить і не викликати вдруге MD5, який (як вже було сказано) виконується близько 300тактов, що дає приріст швидкості ще на 20-25%!

Справа ось в чому.

Якщо уважно придивитися до процедури декодування ресурсів, то ми побачимо, що з допомогою масиву M після пункту 9 стандартного алгоритму при проході з правильним паролем ми декодируем:

— початковий буфер Temp — це не що інше, як 32 байта початкового PWL-файлів з адреси 0x252,
— зміщення (адреси) блоків ресурсів з адреси 0x272,
— і, нарешті, самі ресурси.

Тому не можна виконувати операції над CryptoSign і CheckSign (для перевірки правильності пароля), апросто перевірити після XOR a адресу першого блоку ресурсів, який знаходиться за адресою 0x272.

Якщо він лежить у діапазоні 0x292…(довжина PWL-файлу), то є ймовірність, що цей пароль вірний!Для остаточної ж перевірки правильності пароля потрібно виконати пункты10…14, оскільки тільки в цьому випадку (коли співпадуть всі 16 байт масиву Temp), можна бытьуверенным, що це саме вірний пароль.

І, відповідно, навпаки — якщо попередня перевірка показала, чтоадрес першого блоку ресурсів декодировался невірно, то однозначно невірний пароль иможно сміливо йти на перебір наступного. 🙂

Практика показала, що відсоток «помилкових» влучень, тобто коли після XOR а первогоадреса отримали зсув, схоже на правду, вкрай низький, і часом выполненияповторных повних «перевірок» можна знехтувати.

Тому код попередньої оцінки пароля в спрощеному вигляді буде виглядати так:


//Масив M переробляється за допомогою RC4

byte t; //Тимчасова осередок
byte A = 0; //Тут будемо отримувати новий псевдовипадковий індекс
WORD Offset = (WORD *)&lpFile[0x272]; //Зашифров. адреса 1-го блоку ресурсів
WORD Len = XXX; //Довжина вихідного PWL-файлу
//
for (int i = 1; i {
A += M[i]; //Обчислюємо новий індекс для обміну байтами
t = M[i];
M[i] = M[A]; //Міняємо місцями i-й байт байт і по обчисленому індексом A
M[A] = t;
//
t = M[i] + M[A]; //Обчислюємо ще один індекс
if (i == 33)
((byte *)&Offset)[0] ^= M[t]; //Декодируем 1-й байт адреси
if (i == 34)
((byte *)&Offset)[1] ^= M[t]; //Декодируем 2-й байт адреси
}
//
if ((Offset > 0x291) && (Offset {
//Є ймовірність, що пароль вірний,
//робимо повну перевірку по пунктам 10…14
}
else
{
//Однозначно пароль невірний, переходимо на новий пароль
}

Як бачимо, переробка масиву M все одно залишається, але(!) — перші 32 ітерації ми НІЧОГО неXOR’їм, а декодируем ЛИШЕ на 33 і 34 ітераціях адресу блоку ресурсів.

Таким чином, навіщо нам робити пункти 10…14 і перевіряти 16 байт, коли можновыполнить «спрощений» варіант процедури XorT і перевірити всього 2 байта! 😉

При всій моїй повазі до Microsoft, однак, це ще одна «діра» в реализациикачественных методів зберігання паролів користувачів!

Отже, що ми отримали в середньому:

1. ~40 тактів на ініціалізацію масиву M.
2. ~300 тактів на перший прохід MD5.
3. ~1000 тактів на прохід RC4.
4. ~150 тактів на все інше (формування масиву Z, інкремент пароля, «спрощена» функція XorT, перевірки тощо)

В результаті ми отримуємо сумарний час обробки одного пароля близько 1500 тактівна всіх типах процесорів, що наведені на початку статті, крім процесора Pentium 4. 🙁

Справа в тому, що мікроархітектура P4 порівняно з P-II/III була істотно перероблена — збільшено час виконання команди (до 20 стадій), змінилися розміри рядків кеша даних і коду ще ряд «удосконалень», тому цей код (особливо, реалізація алгоритму RC4)для P4 не є оптимальним і в наступній версії PWLInside буде модифіковано. При цьому на процесорах AMD, навіть останніх, таких проблем не виникає — на Athlon’e XP 1700+ (1466МГц)з ядром ThoroughBred програма справно видає близько мільйона паролів в секунду. Ось так AMDделает аналоги процесорів Intel в чомусь краще, ніж сама Intel. :)ЗаключениеВот і підійшло до кінця наше опис. Сподіваюся, тепер по роботі з PWL-файламиот Windows’OSR2/98/ME ні у кого не залишилося «темних плям». Видно, що данныеалгоритмы можна було б прооптимизировать ще більше із застосуванням командсовременных процесорів, але — операційні системи цих поколінь вже йдуть«в минуле» — відсоток цих ОС і так вже невисокий, з часом знижується дедалі більше і більше, і відновлення паролів до PWL-файлів вже не так актуально,як кілька років тому.

Хоча, можливо, варто ще попрацювати в цій області. 😉

Зараз основний відсоток ОС сімейства Windows — Windows’2000, Windows’XP,а тепер вже і Windows’2003. Так як у всіх цих систем ядро інше (на базеNT), то і методи зберігання, шифрування, а також відновлення 😉 паролейпользователей теж інші.

В них інформація про паролі користувачів зберігається в SAM-файлах, которыепредставляют собою гілку «HKEY_LOCAL_MACHINESAM» реєстру Windows, і паролизашифрованы іншими алгоритмами, ніж у PWL-файлів.

Але(!) — незважаючи на всі спроби Microsoft створити максимально надежныймеханизм авторизації, всі їхні методи чомусь мають явні огріхи — этонаглядно демонструють як методики, описані вище, так і методи храненияи шифрування паролів в SAM-файли.

В операційних системах Windows’NT, починаючи з 6-го сервіс-пака, Windows’2000,Windows’XP (а мабуть і в Windows’2003) застосовуєтьсядодаткове шифрування хешэй користувачів (які і так полученыпутем шифрування паролів певними алгоритмами) з использованиемутилиты Syskey.

Цей механізм успішно проіснував декілька років. Його багаторазово пыталисьвзломать, але всі спроби були безуспішними, поки цією проблемою незаинтересовались ми з Ocean’ом. 😉

І механізм був «зламаний»! Це наочно демонструє программаSAMInside.

Але про SAM-файли і методи відновлення до них паролів розповім як-нибудьв інший раз. 🙂

Особливу подяку висловлюю Ocean’у,т. к. тільки наша з ним спільна діяльність у цій галузі призвела до появи на світ таких програм як PWLInside, SAMInsideі ряду інших.

Удачі всім!