Стандартный алгоритм восстановления паролей к PWL-файлам
Внимание! А на "десерт" - самое вкусненькое ;) - то, что позволило в свое время программе PWLInside существенно увеличить скорость перебора и по этому параметру здорово "оторваться" от своих конкурентов.
Оказывается, для быстрой оценки - тот пароль или нет, можно пункты 10...14 не выполнять, а значит и не вызывать второй раз MD5, который (как уже было сказано) выполняется около 300 тактов, что дает прирост скорости еще на 20-25%!
Дело вот в чем.
Если внимательно присмотреться к процедуре декодирования ресурсов, то мы увидим, что с помощью массива M после пункта 9 стандартного алгоритма при проходе с правильным паролем мы декодируем:
Поэтому можно не выполнять операции над 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 < 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
{
//Однозначно пароль неверный, переходим на новый пароль
}
Как видим, переработка массива M все равно остается, но(!) - первые 32 итерации мы НИЧЕГО не XOR'им, а декодируем ТОЛЬКО на 33 и 34 итерациях адрес блока ресурсов.
Таким образом, зачем нам делать пункты 10...14 и проверять 16 байт, когда можно выполнить "упрощенный" вариант процедуры XorT и проверить всего 2 байта! ;)
При всем моем уважении к Microsoft, однако, это еще одна их "дыра" в реализации качественных методов хранения паролей пользователей!
Итак, что мы получили в среднем:
- ~40 тактов на инициализацию массива M.
- ~300 тактов на первый проход MD5.
- ~1000 тактов на проход RC4.
- ~150 тактов на все остальное (формирование массива Z, инкремент пароля, "упрощенная" функция XorT, проверки и пр.)
В итоге мы получаем суммарное время обработки одного пароля около 1500 тактов
на всех типах процессоров, что приведены в начале статьи, кроме процессора Pentium 4. :(
Дело в том, что микроархитектура P4 по сравнению с P-II/III была существенно переработана - увеличено время выполнения команды (до 20 стадий), изменились размеры строк кэша данных и кода и еще ряд "усовершенствований", поэтому этот код (в особенности, реализация алгоритма RC4) для P4 не является оптимальным и в следующей версии PWLInside будет модифицирован. При этом на процессорах AMD, даже последних, таких проблем не возникает - на Athlon'e XP 1700+ (1466МГц) с ядром ThoroughBred программа исправно выдает около миллиона паролей в секунду. Вот так AMD делает аналоги процессоров Intel в чем-то лучше, чем сама Intel. :)