При правильном подходе к распределению трудоемкой задачи между имеющимися
вычислительными мощностями экономятся время (деньги), ресурсы, а для кого-то и
нервные клетки. Именно для этого предназначена система распределенных
вычислений, созданием и организацией которой мы займемся в этой статье.

Сети распределенных вычислений впервые нашли свое применение в науке.
Моделирование сложных процессов, обработка большого объема данных и тому
подобные задачи требуют вычислительных мощностей, которые зачастую не способны
предоставить суперкомпьютеры. При этом здесь мы не затрагиваем финансовую
составляющую. Как альтернативу огромным вычислительным комплексам ученые решили
взять "с миру по нитке", и сейчас мы можем наблюдать продукты их побочной
деятельности: начиная от обычных кластеров и заканчивая ботнетами, которые, в
подавляющем большинстве случаев, используются в корыстных целях. Но тема
бот-сетей в нашем журнале была раскрыта неоднократно, как в виде конкретных
примеров работающих ботов, так и в виде концептов. Наша задача - рассмотреть
"светлую" сторону систем распределенных вычислений, при этом абстрагируясь от
типа решаемой задачи (будь то поиск внеземных цивилизаций, лекарства от эпидемии
нового вируса или хэша от "непреступной" комбинации символов). По этой причине
мы не будем скрывать клиентскую часть нашей системы на компьютерах
пользователей. Антивирусы и файрволы мы обходить также не планируем, что, тем не
менее, не приведет к упрощению нашей задачи.

 

Закладываем фундамент

Традиционно, прежде чем приступить к кодингу, необходимо ознакомиться с
теоретической частью вопроса. В нашем случае теория проста до безобразия. Что, к
сожалению, не мешает ей скрывать некоторые подводные камни. Обо всем по порядку.

Грид-вычисления (от англ. "grid" - сеть, решетка) - форма распределенных
вычислений, в которой группа компьютеров, объединенных каналами связи (кластер),
выполняет большой объем работ. В свою очередь, сеть этих компьютеров называется
"грид". Данный тип сетей в настоящее время нашел свое применение в коммерческой
инфраструктуре для решения таких трудоёмких задач, как экономическое
прогнозирование, сейсмоанализ, разработка и изучение свойств новых лекарств.
Спускаясь с облаков на землю, скажу, что и хакеры данной технологии находят
множество применений.

Ни для кого не секрет, что современные системы авторизации (например,
встроенные в операционную систему или находящиеся на сайте) хранят пароли
пользователей в виде так называемых "хешей" - строк фиксированной длины,
соответствующих паролю. При осуществлении авторизации переданная комбинация
символов отображается в хэш и сверяется с хешем, хранящимся в базе системы.
Хранение паролей в виде хешей отчасти гарантирует их бесполезность в руках
хакера, получившего доступ к базе. Ему ничего не остается, кроме как "в лоб"
перебирать все возможные комбинации символов и сверять их хэши с целевым, то
есть, искать коллизию. При высокой "стойкости" пароля, то есть при сложной (с
математической точки зрения) комбинации символов, шанс подобрать заветную
комбинацию символов за актуальное время стремится к нулю. По крайней мере, на
одном компьютере ;). А если распределять задачу на несколько машин, то время
перебора будет сокращаться пропорционально количеству рабочих станций. Самое
время вспомнить о завалявшихся дедиках (dedicated servers) из прошлогодней
коллекции ;).

В качестве типа хэшей, который мы будем "потрошить", выберем MD5, в силу его
распространенности в веб-инфраструктуре, но хочу напомнить, что нам важна не
задача, а важен процесс ее выполнения. А теперь приступим к выбору инструмента.

 

Зачем скальпель? Тащи кран!

В нашем журнале неоднократно рассказывалось о прелестях программирования под
платформу Microsoft .NET на разработанном специально для нее языке C#. С
нововведениями MS программирование все больше стало напоминать процесс сбора
конструктора, а справочник MSDN – отличной инструкцией к его сборке.

В качестве инструмента для разработки программного обеспечения относительно
больших масштабов и распределенной архитектуры, .NET окажется как нельзя кстати.
Причина - не только в скорости разработки приложений и внесения изменений в код,
относительной мультиплатформенности и простоте создания сервисов и "облачного"
ПО. Помимо всего перечисленного, в рамках .NET специалисты Майкрософт
разработали множество мелких технологий, полезность которых осознаешь
непосредственно в боевых условиях. Мы рассмотрим одну из таких технологий,
получившую название .NET Remoting. Очень удивил тот факт, что в огромном
количестве клиент-серверных приложений, написанных на C#, до сих пор
используются сокеты, полезность которых заметна в специфичном программном
обеспечении, ориентированном на работу с сетью (сниферы, анализаторы пакетов,
работа с портами и т.п.).

Ремоутинг, в свою очередь, освобождает программиста от возни с сокетами,
открывая широкие возможности для организации распределенной вычислительной
среды.

 

Что нам стоит грид построить?

Для начала, разберемся в устройстве сети, посмотрев на соответствующую
картинку.


Структура грид-сети

Система имеет в основе клиент-серверную архитектуру с, так называемым,
"толстым" клиентом - то есть, клиентская часть берет на себя все необходимые
данные для расчетов у сервера и затем обращается к нему только с определенным
результатом. Задача сервера: корректно обработать запросы клиентов и
синхронизировать имеющиеся данные между ними, при этом правильно выводя
результаты администратору сети. То есть, нам. Тем, кто уже приступил к созданию
сокета и формированию пакета для отправки, я с радостью продемонстрирую
технологию .NET Remoting в действии.

При первом запуске серверной части систему требуется создать и
зарегистрировать канал на определенном порту (в качестве примера используем порт
с номером 39993), а также зарегистрировать класс для удаленной активизации, то
есть - для предоставления этого класса клиентам. В этом как раз заключается суть
"Ремоутинга": клиент создает у себя экземпляр класса, который расположен на
удаленном сервере, и работает с этим экземпляром, как со своим. Особо
внимательный читатель заметит, что это, по своей сути, сервис: серверная часть
может предоставлять вычислительные ресурсы своим клиентам, а те, в свою очередь,
получают лишь результаты расчетов. В нашем случае все происходит с точностью до
наоборот: сервер должен использовать результаты работы клиентов. Разработчики
Microsoft таким образом стерли грань между клиентской и серверной частями: любой
объект становится общедоступным и методы, принадлежащие ему, могут выполняться
на любой из сторон. Применительно к нашему случаю: все вычисления, которые
осуществляются в объектах класса, выполняются на сервере, а клиенту передаются
лишь результаты этих расчетов. Вся эта система работает через прозрачный,
невидимый для программиста, прокси-сервер.

//создать и зарегистрировать канал на порту 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);
//зарегистрировать удаленный класс в локальном\n домене
RemotingConfiguration.RegisterWellKnownClientType(
typeof(Bot),//удаленный класс
//URI удаленного класса
"tcp://localhost:39993/Bot");

Здесь URI задает местоположение удаленного класса. Протокол (в данном случае,
TCP) соответствует протоколу каналов, зарегистрированных в доменах приложений.
Идентификатор машины (localhost, но в реальных условиях - IP-адрес или имя
компьютера) задает сервер, экспортирующий класс Bot и таким образом указывает
компьютер, на котором будет создан объект. Далее в строке URI через двоеточие
указывается номер порта, на котором сервер ожидает вызовы (в нашем случае, порт
с номером 39993).

И напоследок: для того, чтобы класс Bot поддерживал удаленное взаимодействие,
необходимо использовать в качестве базового класса System.MarshalByRefObject:

public class Bot:MarshalByRefObject
{

}

 

Кто не работает - тот завис

Построив теоретический фундамент, рассмотрим особенности функционала
клиентской части системы, подкрепляя рассуждения кодом.
Чтобы ресурсы удаленного класса стали доступными для клиента, он должен создать
экземпляр этого класса:

Bot brain = new Bot();

Далее работа с объектом brain будет происходить, как с локальным, но при этом
все расчеты, которые выполняются в классе этого объекта, будут производиться
сервером.

Как не странно, наша система распределенных вычислений должна грамотно
распределить (простите за тавтологию) задачу между ресурсами клиентов. Напомню,
что задачей у нас является диапазон всевозможных комбинаций символов, из
которых, по мнению пользователя (он определяет алфавит, на основе которого
генерируется диапазон), может состоять пароль.

Пусть каждая рабочая станция в нашей грид-сети сама определит диапазон строк,
который она сможет перебрать за адекватное время. "Предпочтения" рабочей станции
мы будем определять с помощью частоты процессора и количества ядер
(процессоров):

//кол-во ядер
int Core=(Int32)System.Environment.ProcessorCount;
//тактовая частота (МГц)
int Takt=(Int32)Registry.GetValue(
@"HKEY_LOCAL_MACHINE\n\HARDWARE\DESCRIPTION\System\Cen tralProcessor",
"~MHz", 0);

Составим простейшую функцию, в результате работы которой получится число,
равное числу строк (диапазон) для нашего клиента:

int RangeValue = Core * Takt * 9; //функция для расчета диапазона

Изменяя значение третьего коэффициента, мы будем изменять среднее время
перебора адекватного (установленного на основе моих экспериментов) диапазона
строк.

Процесс перебора состоит из трех простых шагов:

  1. Чтение строки из диапазона;
  2. Генерация хеша текущей строки;
  3. Сравнение сгенерированнго хеша с целевым хешем. Если равны – отправить
    результат (строку) серверу в виде сообщения о найденном пароле. Если не
    равны – выполнить шаги 1-3;
  4. В случае конца диапазона отправить результат взять новый диапазон для
    перебора или завершить работу.

Код, выполняющий перебор, здесь рассматривать не будем, так как и он и
подробные комментарии к нему ждут тебя на
диске
.

 

Товарищ командир

Несмотря на тот факт, что сервер в "распределенке" является одной из
важнейших ее частей, самый трудный этап реализации уже позади. Так уж
получилось, что "толстый" клиент оказался еще и "умнее" сервера: и ширину
диапазона для себя он определяет и пароль брутит. Однако на серверную часть
ложатся не менее ответственные задачи генерации строк и учета перебранных
диапазонов. Не углубляясь в технические детали, опишу процесс генерации
диапазонов строк.

Администратор сети определяет множество символов, которые составляют алфавит
для генерации строк. Например, в роли алфавита могут быть спецсимволы, цифры,
комбинация "abcde39#" и тому подобные комбинации символов. Далее происходит
взаимооднозначное соответствие между строкой алфавита и множеством чисел (строке
"!zxcv4M" соответствует множество "1234567"). Серверная часть работает со
строкой символов как с n-мерной системой счисления, то есть при генерации новой
строки происходит инкрементация текущего числа на 1. Прикладная математика может
быть полезной ;).

Весь функционал серверной части предоставляется клиентам в методах удаленного
класса Bot. Рассмотрим метод GetJob(int <количество_строк_диапазона>). Если
клиенту потребуется получить задание, достаточно в уже созданном объекте brain
вызвать метод GetJob:

brain.GetJob("ширина диапазона");

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

 

Будущее рядом, но не всем доступно

Становятся заметны перспективы создания "облачных" приложений: например игр с
потрясающей графикой, расчет элементов которой полностью ложатся на сервер, а
клиенты лишь выводят игроку результаты в виде красивой картинки на экран
монитора.

В частности, разработанная нами система может оказаться полезной, когда
очередной хэш не находится ни в одном из онлайн-хранилищ "слепков" паролей, а
так же не поддается Джону Потрошителю a.k.a John the Ripper. Для таких случаев
каждый уважающий себя взломщик/pen-тестер/security-консультант (нужное
подчеркнуть) должен иметь в своем инструментарии приложение, реализующее
распределенные вычисления.

Кстати, раз уж мы упомянули сервисы "всуе", то почему бы не сделать из нашей
сети сервис, который будет полезен не только нам, но и другим пользователям?
Только представь: отчаявшийся хэш-крэкер заходит на наш сайт, вбивает в формочку
неприступный хеш (номер кошелька ;)) и ждет результатов, зависящих только от
масштабов нашей сети. Нам даже не нужно трогать клиентскую часть: достаточно
доставить серверу любым из способов целевой хэш, а дальше он "разберется"
самостоятельно. Звучит заманчиво, а ведь это только распределенка, выполняющая
брут MD5-хешей. Трудно представить, сколько еще задач ждут своего распределения!

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии