Материал появился в результате анализа замечаний и предложений, поступивших от тех, кто прочитал статью “Кухня распределенных вычислений”. Отсутствие явного распределения задачи сервером между клиентами и отсутствие конкретной задачи в принципе заставили автора иначе подойти к изложению своих идей. Если предыдущая статья и прилагающийся к ней код были в какой-то степени концепцией, то в данном материале будет использоваться код приложения, реализующего в полной мере поставленные задачи. Однако не стоит ждать от приложения дополнительного оформления, коим являются GUI, конфигурационные файлы, продолжительные диалоги и тому подобные вещи, никак не относящиеся к цели.
Несколько слов о предмете статьи. В качестве объема работы, который будет распределяться, выступает перебор MD5-хеша (поиск коллизии) по диапазону символов. Задача достаточно актуальна и позволяет наглядно продемонстрировать методы своего распределения. Как результат, получится система распределенных вычислений типа «грид-сеть».
Немного терминологии
Грид вычисления – форма распределенных вычислений, в которой группа компьютеров, объединенных каналами связи (кластер), выполняет большой объем работ. Впервые эта технология была применена для решения научных, математических задач, требующих для решения значительных вычислительных ресурсов. Сейчас грид вычисления используются также и в коммерческой инфраструктуре для решения таких трудоёмких задач как экономическое прогнозирование, сейсмоанализ, разработка и изучение свойств новых лекарств. Но ничто не мешает нам организовать такую сеть для выполнения полезных для нас действий, требующих огромных вычислительных ресурсов. Одно из таких действий – перебор MD5-хеша.
Почему именно MD5? Ответ: потому что хеши этого алгоритма наиболее распространены в веб-инфраструктуре (уже чувствуются летящие в сторону автора камни). К тому же, ничто не мешает переписать участок кода, отвечающего за генерацию хеша по строке, для нужного вам алгоритма шифрования, благо – этот участок небольшой.
В качестве платформы был выбран Microsoft .NET, т.к. обладает широкими возможностями для проектирования сетевых приложений и позволяет сконцентрироваться на задаче, а не на коде. Наша цель – построение грид-сети, а не бот-сети. Этот факт освобождает от необходимости прятать клиента в системе и позволяет написать свой сервер. Плюс ко всему, автор таким способом решил изучить новые для себя технологии и язык программирования в том числе.
Cоздание базы будущей системы
Приложение типа «клиент-сервер» с, так называемым, «толстым клиентом», т.е. при получении задачи клиент сам определяет нужный для себя диапазон строк. Серверу остается только в соответствии с запрашиваемым диапазоном сгенерировать его, передать клиенту и учесть у себя, какие диапазоны перебираются и были перебраны.
Технология .NET Remoting освобождает программиста от возни с сокетами и открывает широкие возможности в распределенной вычислительной среде. Используем ее.
Приступим непосредственно к проектированию системы.
При первом запуске сервера требуется создать и зарегистрировать канал на определенном порту (используем порт с номером 39993), а также зарегистрировать класс для удаленной активизации, т.е. для предоставления этого класса клиентам. В этом, собственно, суть Ремоутинга: клиент создает у себя экземпляр класса, который расположен на удаленном сервере, и работает с этим экземпляром, как со своим. При этом все расчеты, которые используются в классе, выполняются на сервере, а клиенту передаются лишь результаты этих расчетов. Вся эта система работает через прозрачный (не видимый с точки зрения программиста) прокси-сервер. В общем, за деталями технологии к поисковику. Вот код, выполняющий наши требования:
//создать и зарегестрировать канал, используя порт с номером 39993
TcpServerChannel channel = new TcpServerChannel(39993);
ChannelServices.RegisterChannel(channel);
//зарегистрировать класс для удаленной активизации
RemotingConfiguration.RegisterWellKnownServiceType (
typeof(Bot),//регистрируемый класс
"Bot",//URI регистрируемого класса
WellKnownObjectMode.SingleCall);//режим активизации для каждого клиентского вызова
URI, он же Uniform Resource Identifier (унифицированный идентификатор ресурса) – параметр, который используется клиентом для активизации объекта, т.е. с помощью URI клиент укажет серверу, что требуется экземпляр именно класса Bot.
Клиент, в свою очередь, должен создать клиентский канал и зарегестрировать удаленный класс в локальном домене:
//создать и зарегестрировать клиентский канал
TcpClientChannel channel = new TcpClientChannel();
ChannelServices.RegisterChannel(channel);
//зарегистрировать удаленный класс в локальном домене
RemotingConfiguration.RegisterWellKnownClientType(
typeof(Bot),//удаленный класс
"tcp://localhost:39993/Bot");//URI удаленного класса
Здесь URI задает местоположение удаленного класса. Протокол (в данном случае TCP) соответствует протоколу каналов, зарегистрированных в доменах приложений. Идентификатор машины (localhost) задает сервер, экспортирующий класс Bot и таким образом указывает компьютер, на котором будет создан объект. Вместо localhost можно использовать IP-адрес или имя компьютера. Далее в строке URI через двоеточие указывается номер порта, соответствующий номеру порта, на котором сервер ожидает вызовы (порт с номером 39993).
Для того, чтобы класс Bot поддерживал удаленное взаимодействие необходимо использовать в качестве базового класса System.MarshalByRefObject:
public class Bot : MarshalByRefObject
{
...
}
Клиентская часть.
Перейдем к детальному рассмотрению клиентской части системы.
После регистрации канала для соединения с сервером и регистрации класса Bot в своем домене, для последующего использования ресурсов, предоставляемых этим классом, клиент должен создать экземпляр этого класса:
Bot bot = new Bot();
После этого, клиент может работать с объектом bot, как с локальным, но при этом все расчеты, которые выполняются в классе этого объекта, будут производиться сервером (отдаем дань уважения программистам Microsoft).
Затем клиент должен определить диапазон, который он «предпочитает» перебирать за один шаг. «Предпочтения» клиента, автор решил определять с помощью частоты процессора и количества ядер (процессоров) на компьютере. Решение это не случайно: основная нагрузка при переборе ложится на процессор, поэтому его характеристики будут играть важную роль при составлении диапазона. Итак, вот участок кода, в котором определяются характеристики процессора:
int Core = (Int32)System.Environment.ProcessorCount;//кол-во ядер
int Takt = (Int32)Registry.GetValue(//тактовая частота (МГц)
@"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\Cen tralProcessor\0", "~MHz", 0);
Далее составляем функцию, в результате которой получается число, равное числу строк для нашего клиента:
int RangeValue = Core * Takt * 9; //функция для расчета диапазона
Третий множитель автор определил из того, чтобы среднее время перебора на одной машине было не более 2-х минут. Приблизительно, на компьютере с характеристиками процессора: Core2Duo 2,4ГГц (соответственно, 2 ядра) перебор 80000 тысяч строк диапазона занимает 00:00:47. Цифры весьма условные (т.к. 32-разрядная ОС, особенности получения слова из строки диапазона и активное программное обеспечение также загружают процессор, что затрудняет расчеты), поэтому автор предлагает не “зацикливаться” на этом коэффициенте (вынесем его в настройки клиента) и перейти к следующему важному участку кода, отвечающему за перебор.
EnemyHash = EnemyHash.ToUpper();//перевести все символы в верхний регистр
//перебрать каждую строку
int i = 0;
string CurrentLine = string.Empty;
Range += "\0";
while (Range[i] != '\0')
{
if (Range[i] == '\n')
{
Console.WriteLine(CurrentLine);
string CurrentHash = string.Empty; //сгенерировать MD5-хеш
foreach (byte b in new MD5CryptoServiceProvider().ComputeHash(Encoding.De fault.GetBytes(CurrentLine)))
{
CurrentHash += b.ToString("X2"); }
//сравнить хеши
if (CurrentHash == EnemyHash)
{
bot.Finish(CurrentLine);
Console.ReadKey();
return;
}
CurrentLine = string.Empty;
}
else CurrentLine = CurrentLine + Range[i];
i++;
}
Описывать действие строк (и без этого откомментированных) автор не собирается, а отправит любопытного читателя к своей мини-статье, в которой детально рассмотрен данный код и процесс перебора в целом (“Раскалываем MD5”).
В случае, если клиент не нашел пароль, он отправит пустую строку серверу, которая будет означать, что в перебранном клиентом диапазоне строк не содержится пароль.
На этом рассмотрение основного функционала нашей системы завершается. Теперь необходимо приступить к описанию удаленного класса, который сервер предоставляет своим клиентам и который, по сути, является поставщиком диапазонов для клиентской части.
Удаленный класс Bot.
Данный класс является ядром системы и именно в его методах вычисляется диапазон строк для каждого клиента и производится учет уже перебранных диапазонов, чтобы не происходило «лишней» работы. Рассмотрим наиболее интересные, по моему мнению, участки кода.
Для начала, необходимо сделать класс удаленно доступным для клиентов, т.е. дать клиентам возможность создавать у себя экземпляры этого класса. Плюс технологии Remoting заключается в том, что все расчеты в методах созданного экземпляра будут производиться на сервере (благодаря прозрачному прокси-серверу).
Метод GetJob(int RangeValue) (см. исходные коды bot.cs в прилагающемся проекте) фактически содержит в себе код, отвечающий за перебор, за учет перебранных диапазонов, за выдачу диапазона длинной RangeValue, которую определяет клиент для себя сам. В проекте, который прилагается к статье автор постарался не оставить без внимания каждую деталь, поэтому убедительно просит читать комментарии.
Рассмотрим процесс генерации диапазона:
for (int PassLength = 1; PassLength <= MaxPassLength; PassLength++)
{
char[] Temp = new char[PassLength];//символы, соответствующие номерам
int[] Number = new int[PassLength];//номера символов
bool p=false;
while ((Counter <= RangeValue + Marker) && (p == false))
{
CurrentString = string.Empty;
if (Counter <= Marker+1)
{
//начиная с младшего символа проверяем, не равен ли он размеру множества (проверка на переход в следующий разряд)
for (int i = 0; i < PassLength; i++)
{
if ((Number[i] == CharSet.Length) && (i != PassLength - 1))
{
Number[i + 1]++; //если это так, то младший разряд равен 0, а следующий +1
Number[i] = 0;
}
//если последний разряд подошел к концу, то выйти из программы
if ((i == PassLength - 1) && (Number[i] == CharSet.Length))
{
p = true;
}
}
Number[0]++; // увеличиваем младший разряд на 1 (переход к следующей комбинации)
Counter++;
}
else
{
//начиная с младшего символа проверяем, не равен ли он размеру множества (проверка на переход в следующий разряд)
for (int i = 0; i < PassLength; i++)
{
if ((Number[i] == CharSet.Length) && (i != PassLength - 1))
{
Number[i + 1]++; //если это так, то младший разряд равен 0, а следующий +1
Number[i] = 0;
}
//если последний разряд подошел к концу, то выйти из программы
if ((i == PassLength - 1) && (Number[i] == CharSet.Length))
{
p = true;
}
}
if (p == true)
break;
//составление строки
for (int i = PassLength - 1; i >= 0; i--)
//генерация строки (пароль)
{
Temp[i] = CharSet[Number[i]];//конвертировать позицию в соответствующий символ
CurrentString += Temp[i];
}
range += CurrentString + "\n";
Number[0]++; // увеличиваем младший разряд на 1 (переход к следующей комбинации)
Counter++;
}
}
}
Множество символов CharSet, по которому идет перебор, представляется как биективное (взаимооднозначное) отображание во множество чисел (работа с n-мерной системой счисления, где n = length(CharSet)), т.е. при генерации новой строки происходит инкрементация текущего числа на 1.
Под занавес.
Cтатья, которая планировалась как продолжение, стала отдельным материалом, описывающим основы создания приложения типа «грид-сеть» с использованием технологии .NET Remoting. Брутфорс (a. k. a. перебор паролей) по диапазонам становится более актуальным, ведь не всегда удача на нашей стороне, и очередной хеш может не оказаться в базе данных. Каждый уважающий себя взломщик/pen-тестер/security-консультант (нужное подчеркнуть) должен иметь в своем инструментарии приложение, реализующее распределенные вычисления, и набор производительных серверов для его работы. Второе нам не занимать – верно?
Система распределенного перебора паролей, построенная на основе материала данной статьи.