Версия документа: 1.0
Скачать статью в виде ZIP-архива
Содержание
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.
Вся информация в тексте о времени выполнения фрагментов кода в тактах дана для
следующих типов процессоров:
Информация о формате 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 |
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 |
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 |ISTIAN\Dhellgate 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-файлах. Т.к. подробные описания 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 < 256; i++) M[i] = i; //Последовательно заполняем массив значениями от 0 до 255 for (int i = 0; i < 256; i++) { A += (M[i] + Key[i % 16]); //Вычисляем новый индекс для обмена байтами t = M[i]; M[i] = M[A]; //Меняем местами i-й байт и байт по вычисленному индексу A M[A] = t; } |
MD5
MD5 - это ни что иное, как операция свертки 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) << (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), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define GG(a, b, c, d, x, s, ac) { \ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define HH(a, b, c, d, x, s, ac) { \ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define II(a, b, c, d, x, s, ac) { \ (a) += I ((b), (c), (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 (d, a, 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 (d, a, 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 (d, a, 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 (d, a, 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 < 33; 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-файлам
Алгоритм декодирования ресурсов из PWL-файла с правильным паролем
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 |ISTIAN\Dhellgate |
Вот и начинается самое интересное. ;)
Посмотрим, что же мы можем сделать для повышения быстродействия вышеприведенных алгоритмов.
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 |
IRP i,<-15,-14, ... ,14,15> paddb mm0,mm1 movq [qword ptr edi+(i*8)],mm0 ENDM |
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 ... |
A += (M[i] + Key[i % 16]); |
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] |
MD5
Первый момент в оптимизации этого алгоритма - это замена следующих
макросов с 4-мя логическими операциями:
#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) |
#define F(x, y, z) ((((y) ^ (z)) & (x)) ^ (z)) #define G(x, y, z) ((((x) ^ (y)) & (z)) ^ (y)) |
Стандартный алгоритм восстановления паролей к PWL-файлам
Внимание! А на "десерт" - самое вкусненькое ;) - то, что позволило в свое
время программе PWLInside существенно увеличить скорость перебора
и по этому параметру здорово "оторваться" от своих конкурентов.
Оказывается, для быстрой оценки - тот пароль или нет, можно пункты 10...14 не выполнять,
а значит и не вызывать второй раз MD5, который (как уже было сказано) выполняется около 300
тактов, что дает прирост скорости еще на 20-25%!
Дело вот в чем.
Если внимательно присмотреться к процедуре декодирования ресурсов, то мы увидим, что с
помощью массива M после пункта 9 стандартного алгоритма при проходе с правильным паролем мы декодируем:
... //Массив M перерабатывается с помощью RC4 ... byte t; //Временная ячейка byte A = 0; //Здесь будем получать новый псевдослучайный индекс WORD Offset = (WORD *)&lpFile[0x272]; //Зашифров. адрес 1-го блока ресурсов WORD Len = XXX; //Длина исходного PWL-файла // for (int i = 1; i < 35; 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 < Len)) { //Есть вероятность, что пароль верный, //делаем полную перепроверку по пунктам 10...14 } else { //Однозначно пароль неверный, переходим на новый пароль } |
Вот и подошло к концу наше описание. Надеюсь, теперь по работе с PWL-файлами
от Windows'OSR2/98/ME ни у кого не осталось "темных пятен". Видно, что данные
алгоритмы можно было бы прооптимизировать еще больше с применением команд
современных процессоров, но - операционные системы этих поколений уже уходят
"в прошлое" - процент этих ОС и так уже невысокий, со временем снижается все
больше и больше, и восстановление паролей к PWL-файлам уже не столь актуально,
как несколько лет назад.
Хотя, возможно, стоит еще поработать в этой области. ;)
Сейчас основной процент ОС семейства Windows - это Windows'2000, Windows'XP,
а теперь уже и Windows'2003. Так как у всех этих систем ядро другое (на базе
NT), то и методы хранения, шифрования, а также восстановления ;) паролей
пользователей тоже другие.
В них информация о паролях пользователей хранится в SAM-файлах, которые
представляют собой ветку "HKEY_LOCAL_MACHINE\SAM" реестра Windows, и пароли
зашифрованы другими алгоритмами, нежели в PWL-файлах.
Но(!) - несмотря на все попытки Microsoft создать максимально надежный
механизм авторизации, все их методы почему-то имеют явные огрехи - это
наглядно демонстрируют как методики, описанные выше, так и методы хранения
и шифрования паролей в SAM-файлах.
В операционных системах Windows'NT, начиная с 6-го сервис-пака, Windows'2000,
Windows'XP (а по всей видимости и в Windows'2003) применяется
дополнительное шифрование хешэй пользователей (которые и так получены
путем шифрования паролей определенными алгоритмами) с использованием
утилиты Syskey.
Данный механизм успешно просуществовал несколько лет. Его многократно пытались
взломать, но все попытки были безуспешны, пока этой проблемой не
заинтересовались мы с Ocean'ом. ;)
И механизм был "взломан"! Это наглядно демонстрирует программа
SAMInside.
Но про SAM-файлы и методы восстановления к ним паролей расскажу как-нибудь
в другой раз. :)
Особую благодарность выражаю Ocean'у,
т.к. только наша с ним совместная деятельность в этой области привела
к появлению на свет таких программ как PWLInside, SAMInside
и ряда других.
Удачи всем!
Copyright (c) 2003 PolASoft
www.InsidePro.com
обсудить | все отзывы (0) | |
[21919; 12; 7.75] |
|
|