Разделение хакерского труда_4epen(xdiman@mail.ru) Хакер, номер #100, стр. 120 Пишем утилиту для адаптирования программ под распределенные вычисления СВЕДЕНИЯ О ЛАБЕ Студент: Пукаленко Дмитрий Университет: МГТУ им. Баумана Курс: II Факультет: ИУ (информатика и системы управления) Тема: изучение протокола TCP/IP Цель лабораторной: написание программы-сервера, рассылающего нескольким клиентам данные из файла, вдохновила нас на распределение таким образом паролей для переборщика John The Ripper. Потребовалось связать программу с клиентом, что и было сделано. Об этом и рассказывается в статье. В последнее время особую популярность приобрели различные программы-переборщики паролей с возможностью распределенной работы на нескольких компьютерах. К написанию этой статьи меня побудило то, что мой любимый переборщик John The Ripper такой возможностью не обладает. Чтобы не реализовывать каждый раз алгоритм распределения в очередном самописном брутфорсере, я решил написать утилиту, позволяющую адаптировать к распределенной работе почти любую программу, и хочу поделиться с тобой опытом ее написания. Итак, приступим! Конфигурация сервера Естественно, программа такого типа должна состоять из серверной и клиентской частей. Давай определим возможности серверной части и напишем конфигуратор. Прежде всего назначим в конфигурационном файле основные настройки: Конфигурация сервера [global] password = changeme clients = 5 port = 31337 log = adapt.log wait = yes data = 0 output = result.txt [global] Все опции в секции global - основные настройки сервера, не зависящие от набора входных данных. Опция clients отвечает за количество клиентов, которые должны быть подключены к серверу, прежде чем он начнет отсылать им входные данные; wait определяет, должен ли сервер ждать окончания работы всех клиентов (в случае если мы подбираем один пароль на многих компьютерах), а о назначении остальных настроек ты можешь догадаться интуитивно :). Остановимся на опции data. Так как сервер у нас многофункциональный, я решил вынести настройки преобразования входных данных из источника в отдельные секции конфига - [dataset]. Параметр data указывает как раз на то, какая секция [dataset] должна быть использована сервером. Я сделал это для того, чтобы можно было быстро переключать набор входных данных в зависимости от поставленной задачи и не переписывать весь конфигурационный файл. Самое важное в конфигурации сервера - это как раз та самая секция [dataset]. Она определяет источники данных, например список паролей и преобразование данных, то есть подгонку их под спецификацию входных данных программы-клиента: [dataset] input =pass.txt skip = limit =symbol symbol=10 size =0 send =10 trans =yes input_format =: output_format=%2 end_data =eof end_value= [dataset] Расскажу подробнее о вышеуказанных настройках. Опция input - путь к файлу, из которого будут считываться данные. Данные могут быть считаны и из stdin, если значение опции будет пустым. Следующие три опции предназначены для разграничения независимых частей данных. Первая указывает тип разграничения: по отдельному символу или по размеру части данных в байтах; остальные две содержат этот символ или размер. В нашем случае конфигурация предусматривает разделение входного потока данных по строкам. Параметр send - это разграничивающий символ между отправляемыми клиенту частями данных, у нас это 10 (\n), то есть отправляем также по строкам. Два последних параметра позволяют определить конец входных данных - по EOF или же по лимитированному количеству частей. Оставшиеся три опции отвечают за подгонку данных под специфический формат программы-клиента. Для разнообразия можешь оформить конфигурационный файл в виде XML и разбирать его с помощью готовой библиотеки libxml, либо же юзай самописный код, как сделал я :). Код разбора опций ты можешь найти во врезке. Подгонка данных Наш адаптер должен обеспечивать взаимодействие с программами-клиентами любого типа, с различным форматом входных данных. Для этой цели я написал преобразующую функцию trans(), принцип работы которой заключается в следующем. Параметр input_format содержит набор разделителей, записанных через пробел (ты можешь использовать любой другой символ). В зависимости от них считанная часть данных разделяется на более мелкие пронумерованные части. Мною была написана функция find, которая отсекает часть строки до указанной подстроки. Ее следует выполнять в цикле по заданным разделителям: char *find(char **data, char *delim) { char *ptr, *str; str = (char*)calloc(1, 1); for(ptr=*data;strncmp(ptr, delim, strlen(delim)) && *ptr!='\0';ptr++) add_symbol(&str, ptr); (*data) += strlen(str)+strlen(delim); return str; } Параметр output_format - это просто строка, в которую могут быть вставлены scanf-подобные форматные спецификаторы: %1, %2, %3 и т.д. При преобразовании, вместо форматного спецификатора %n, в строку подставляется n’ая из пронумерованных частей. Такое нехитрое преобразование дает возможность подогнать формат входных данных, например, для многих популярных брутфорсеров. При реализации подобного рода алгоритмов, работающих со строковыми переменными, нередко возникает потребность динамически изменять строку и добавлять к ней символ или другую строку. В качестве примера я приведу функцию добавления символа: void add_symbol(char **str, char *c) { *str = (char*)realloc(*str, strlen(*str)+2); strncat(*str, c, 1); } Ее несложно будет переписать для добавления к строке другой строки :). Также весьма удобна в использовании функция asprintf(), которая динамически выделяет память под результирующую строковую переменную. Код функции, заменяющей форматные спецификаторы нужными пронумерованными частями ты можешь найти во врезке или в исходнике на диске. Сетевая часть сервера Я полагаю, что сетевое взаимодействие - самая простая в реализации часть нашего проекта. Логичнее всего разбить код на три процедуры - accept_conn(), send_data() и get_result(), назначение которых интуитивно понятно. Рассмотрим работу вышеуказанных функций. Процедура accept_conn() ждет входящих подключений от клиентов. При подключении она получает пароль, сравнивает его с заданным в конфиге и помещает сокет клиента в массив clients. При настройке мы указали фиксированное число клиентов, поэтому в случае возникновения ошибки (клиент дисконнектился, неправильный пароль) в цикле уменьшаем счетчик цикла и ждем повторного подключения. После выполнения вышеописанной процедуры вызывается функция send_data(). Она и является, по сути, ключевой процедурой в нашей программе. Для начала определяем входной поток и считываем из него данные частями, используя методы разграничения частей, заданные в файле конфигурации. Если необходимо, применяем преобразование (в приведенном примере строки вида «логин:пароль» преобразовываются в строки, содержащие только пароли). После этого организуем два вложенных цикла: до конца входных данных и цикл по всем клиентам, в котором мы отправляем часть данных и разграничивающий символ i’тому клиенту. Такой подход позволяет серверу более-менее равномерно распределить данные между клиентами. После того как все данные отправлены, можно отключать всех клиентов и ждать результатов. Функция get_result() ожидает входящее подключение, проводит аутентификацию, получает от клиента результат и, в зависимости от параметра wait, либо прекращает работу, либо ожидает завершения работы всех клиентов. Теперь нам осталось собрать воедино модуль для разбора опций, модуль для подгонки данных и сетевой модуль. Сервер готов. Конфигурация клиента Основная задача клиента - обеспечение взаимодействия с программой. Для начала, как и положено, создадим конфигурационный файл. Я, к примеру, использовал в нем следующие опции: файл с входными данными для программы, файл с результатом работы, shell-команду для запуска программы, лог-файл, а также сетевую часть - хост, порт и пароль для подключения к серверу. Помимо этого, для удобства я вставил возможность пропуска нескольких байт или строки в начале результирующего файла. Это было сделано по одной причине - некоторые программы, например John The Ripper, выводят в файл результата строку с указанием количества загруженных паролей и используемого алгоритма шифрования. Клиент же будет состоять из двух основных частей: парсера конфигурационного файла (такого же, как для сервера) и сетевой части. Принцип работы клиента Основную работу клиента выполняют две функции - get_data() и send_result(). Логика их работы проста - подключение к серверу, аутентификация, получение набора входных данных и отправка результатов. Входные данные записываются в отдельный файл, который передается в качестве параметра программе. Далее, мы можем расширить функциональность клиента, либо каждые n секунд проверяя размер файла на изменение и в случае последнего отсылая новые результаты серверу, либо же дожидаясь окончания программы и отсылая серверу готовый файл (как сделал я :)). Больше ничего сложного в реализации клиентской части нет. Расширение возможностей: последовательный перебор Все прекрасно работает, но ты, скорее всего, спросишь: «Как быть с последовательным перебором?» Специально для расширения возможностей я написал дополнительную программу, генерирующую последовательность строк вида «логин:пароль» или строк, содержащих лишь пароль во всех возможных комбинациях. В начале статьи было сказано, что сервер может получать входные данные не только из файла, но и из stdin. Оставив опцию входного потока данных в конфиге сервера пустой, мы получим возможность запустить сервер следующим образом: # ./bruteforce | server Для расширения функциональности я использовал следующие опции. Опция -login login добавляет к выводимым паролям строку вида login:. -start и -end позволяют задать начальное и конечное число знаков в пароле, а -type (которая может принимать значения all, dig, let) определяет тип символов, содержащихся в пароле. Наконец, опция -ucase позволяет использовать только прописные латинские буквы, а -lcase - только строчные. Таким образом, чтобы сгенерировать строки вида «admin:пароль», где пароль может содержать только строчные латинские буквы и цифры и имеет длину от 4 до 13 символов, даем следующую команду: # ./bruteforce -login admin -start 4 -end 13 -type all -lcase | server Программа bruteforce запишет в выходной поток все необходимые комбинации, которые затем будут переданы клиентам. Реализация bruteforce очень проста. Основная функция - gen_passwords(int), принимающая в качестве аргумента количество символов в пароле и генерирующая все пароли с указанными настройками. Это делается так: создается строка заданной длины и инициализируется первым символом из заданного набора. В цикле мы меняем последний символ на следующий из набора и, в случае достижения последнего в наборе символа, меняем его на первый и увеличиваем предыдущий символ в строке. Цикл останавливается по достижении последней комбинации. Расширение возможностей: перебор по словарю Перебор с использованием файла со списком паролей тоже может быть расширен. Как известно, многие пользователи любят пароли - слова, записанные транслитом или в другой раскладке. С помощью специальной программы, добавляющей к списку паролей в ворд-листе дополнительные значения, мы можем это учесть; взаимодействие программы и сервера осуществляется вышеуказанным методом. Преобразование паролей возможно с помощью создания таблиц, в которых, например, одному символу соответствует этот же символ в другой раскладке. роверку имени пользователя как пароля, паролей с перепутанным регистром, паролей с повторяющимся словом и т.д. можно сделать интересной фичей. Предоставляю тебе возможность написать такую программу самостоятельно :). Итог работы Итак, в итоге у нас получилась утилита, способная адаптировать большинство популярных программ-брутфорсеров к распределенной работе на нескольких компьютерах. Вся задача по генерации входных данных переносится на сервер. Можно использовать как перебор по словарю, так и последовательный перебор. «У нас получилась утилита, способная адаптировать большинство популярных программ-брутфорсеров к распределенной работе на нескольких компьютерах» Естественно, программа далеко не совершенна, и многое еще предстоит реализовать. Интересной возможностью может стать отправка тестового набора данных клиентам и распределение последующих входных данных клиентам в зависимости от их производительности. Также хорошо было бы улучшить алгоритм преобразования входных данных, используя не разделители, а scanf-подобные спецификаторы - %s, %i и т.д. Еще неплохо было бы сделать режим принудительного завершения работы всех программ-клиентов, если нужный пароль уже найден. Дело за тобой :). Примерный парсер опций конфига int get_options(char *config) { FILE *conf; int i, j = 0, n = 0, k; char temp[500], value[500]; // открываем файл конфигурации conf = fopen(config, "r"); while(!feof(conf)) { // обнулим temp memset(temp, 0, 500); // получаем строку из файла fgets(temp, 499, conf); // убираем пробелы и символы табуляции strip_spaces(temp); // проверяем на пустую строку if(!strcmp(temp, "")) continue; // добавляем в список asprintf(&(opts[n++]), "%s", temp); } // цикл по всем строкам for(i=0;i<n;i++) { // проверяем на global if(!strcmp(opts[i], "[global]")) { i++; while(i<n) { // обнулим переменные memset(temp, 0, 500); memset(value, 0, 500); // считываем параметр и значение for(j=0;opts[i][j]!='='&&opts[i][j]!='\0';j++) temp[j]=opts[i][j]; if(!strcmp(temp, "[global]")) break; j++; for(k=0;opts[i][j]!='\0';j++) value[k++]=opts[i][j]; // проверяем параметр и значение if(!strcmp(temp, "option")) asprintf(&(parameter), "%s", value); i++; } } } return 0; } Замена форматных спецификаторов void replace_format(char **str, int k, char* substr) { int i; char *num; char *ptr; char *temp; asprintf(&num, "%d", k); for(ptr=*str;*ptr!='\0';ptr++) { if(!strncmp(ptr, "%%", 2)) ptr++; if(*ptr=='%' && !strncmp(ptr+1, num, strlen(num))) { i = ptr - (*str); // скопируем часть после форматного спецификатора *str = (char*)realloc((*str), strlen(*str)+strlen(substr)+2); ptr = *str + i; asprintf(&temp, "%s", ptr+strlen(num)+1); // вставим подстроку и окончательную часть strcpy(ptr, substr); strcat(*str, temp); ptr += strlen(substr); free(temp); } } free(num); } CD На компакт-диске лежат полные исходные коды адаптера. Ты можешь скомпилировать их с помощью GCC. WWW Также ты можешь взять исходные коды адаптера с сайта www.xakep.ru. |