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

Статья [C/C++] Сбор информации о системе.

shellcode_

HDD-drive
Пользователь
Регистрация
21.03.2024
Сообщения
36
Реакции
8
Гарант сделки
1
Порой при разработке malware продукта у кодера возникает задача - собрать информацию о конфигурации системы. В данной статье мы поговорим как раз об этом.

Первое, что нам нужно - получить юзернейм пользователя и имя компьютера:
C++:
LPCSTR getUsername()
{
    char *username = new char[MAX_PATH]{'\0'};
    DWORD size = MAX_PATH;
    GetUserName(username, &size);
    return username;
}

LPCSTR getPCName()
{
    char *pcName = new char[MAX_PATH]{'\0'};
    DWORD size = MAX_PATH;
    GetComputerName(pcName, &size);
    return pcName;
}

Здесь все достаточно просто - в WinAPI уже есть функции, которые возвращают эти значения.
Далее мы хотим узнать, сколько оперативной памяти доступно в системе и какой процессор:
C++:
LPCSTR getCPUName()
{
    int cpuInfo[4] = {0, 0, 0, 0};
    __cpuid(cpuInfo, 0x80000002);
    char *cpuBrand = new char[48]{'\0'};
    memcpy(cpuBrand, cpuInfo, sizeof(cpuInfo));
    return (LPCSTR)cpuBrand;
}

DWORDLONG getRAMSize()
{
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    GlobalMemoryStatusEx(&memInfo);
    return memInfo.ullTotalPhys;
}
Опять же, никаких трудностей. С помощью __cpuid мы можем получить модель процессора, а GlobalMemoryStatusEx возвращает информацию об оперативной памяти.

Можем получить основной язык операционной системы:
C++:
LANGID getLangID()
{
    LCID lcid = GetSystemDefaultLCID();
    LANGID langId = PRIMARYLANGID(lcid);
    return langId;
}
Тут мы получаем LANGID (он же WORD) - идентификатор языка системы Windows. На MSDN можно скачать .pdf файл, где описаны идентификаторы и языки, к которым они принадлежат.

Получаем время работы (uptime) системы в секундах:
C++:
DWORD getUptimeInSeconds()
{
    return GetTickCount64() / 1000;
}
Функция GetTickCount64 возвращает uptime в миллисекундах, поэтому делим на 1000, чтобы получить значение в секундах.

Получаем информацию о разделах диска:
C++:
LPCSTR getDisksInfo()
{
    LPCSTR disks_info = new char[1024]{'\0'};
    DWORD drives = GetLogicalDrives();
    for (char drive = 'A'; drive <= 'Z'; drive++)
    {
        if (drives & 1 << (drive - 'A'))
        {
            char drive_name[4] = {drive, ':', '\\', '\0'};
            ULARGE_INTEGER FreeBytesAvailable = {0};
            ULARGE_INTEGER TotalNumberOfBytes = {0};
            ULARGE_INTEGER TotalNumberOfFreeBytes = {0};
            if (GetDiskFreeSpaceExA(drive_name, &FreeBytesAvailable, &TotalNumberOfBytes, &TotalNumberOfFreeBytes))
            {
                unsigned long long freeAvail = FreeBytesAvailable.QuadPart;
                unsigned long long total = TotalNumberOfBytes.QuadPart;
                unsigned long long totalfree = TotalNumberOfFreeBytes.QuadPart;
                char infoBuf[1024] = {'\0'};
                sprintf(infoBuf, "%s %llu MB free, %llu MB total; \t", drive_name, freeAvail / 1024 / 1024, total / 1024 / 1024);
                strcat((char *)disks_info, infoBuf);
            }
        }
    }
    return disks_info;
}
Функция GetLogicalDrives возвращает маску, с помощью которой можно определить, какие диски доступны в системе. Маску используем в цикле, перебирающем буквы алфавита. GetDiskFreeSpaceExA позволяет узнать полный объем диска, а также количество свободного пространства.

Чтобы обнаружить GPU, можно воспользоваться d3d9.h:
C++:
LPCSTR getGPUName()
{
    IDirect3D9 *d3d = Direct3DCreate9(D3D_SDK_VERSION);
    if (d3d)
    {
        D3DADAPTER_IDENTIFIER9 adapter;
        if (SUCCEEDED(d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapter)))
        {
            char *name = new char[sizeof(adapter.Description)]{'\0'};
            memcpy(name, adapter.Description, sizeof(adapter.Description));
            return name;
        }
        d3d->Release();
    }
    return nullptr;
}
Функция getGPUName вернет модель GPU или nullptr, если GPU не обнаружено.

Получим информацию о дисплеях:
C++:
LPCSTR getDisplayInfo()
{
    int monitorCount = GetSystemMetrics(SM_CMONITORS);

    LPCSTR monitorsInfo = new char[1024]{'\0'};
    DISPLAY_DEVICE displayDevice;
    ZeroMemory(&displayDevice, sizeof(displayDevice));
    displayDevice.cb = sizeof(displayDevice);

    int deviceIndex = 0;
    while (EnumDisplayDevices(NULL, deviceIndex, &displayDevice, 0))
    {
        if (!(displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
        {
            DEVMODE devMode;
            ZeroMemory(&devMode, sizeof(devMode));
            devMode.dmSize = sizeof(devMode);

            if (EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &devMode))
            {
                char buf[1024] = {'\0'};
                sprintf_s(buf, "%s: %dx%d, %dHz\t", displayDevice.DeviceName, devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmDisplayFrequency);
                strcat_s((char *)monitorsInfo, 1024, buf);
            }
        }
        deviceIndex++;
    }

    return monitorsInfo;
}
Функция вернет разрешения дисплеев и их частоту обновления.

Вернем все процессы, которые запущены в системе на данный момент:
C++:
LPCSTR getRunningProcesses()
{
    LPCSTR processes = new char[4096]{'\0'};
    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (Process32First(snapshot, &entry))
    {
        do
        {
            char buf[1024] = {'\0'};
            DWORD bufLen = sprintf_s(buf, "%s\t", entry.szExeFile);
            strcat((char *)processes, buf);
        } while (Process32Next(snapshot, &entry));
    }
    CloseHandle(snapshot);
    return processes;
}
Тут итерируем сквозь все процессы и записываем имена .exe файлов в буфер.

Очень много полезной информации может содержаться в реестре Windows. Напишем функцию для чтения:
C++:
LPCSTR readRegistry(HKEY reg, char *subKey, char *valueName)
{
    HKEY phkResult = NULL;

    if (RegOpenKeyExA(reg, subKey, 0, KEY_READ, &phkResult) == ERROR_SUCCESS)
    {
        LPCSTR totalData = new char[2048]{'\0'};
        DWORD dwIndex = 0;
        LPSTR szValueName = new char[MAX_PATH]{'\0'};
        DWORD dwValueNameLen = MAX_PATH;
        BYTE *lpData = new BYTE[MAX_DATA_LEN]{'\0'};
        DWORD lpDataLen = MAX_DATA_LEN;

        while (RegEnumValueA(phkResult, dwIndex, szValueName, &dwValueNameLen, NULL, NULL, lpData, &lpDataLen) == ERROR_SUCCESS)
        {
            if ((valueName != nullptr && strcmp(szValueName, valueName) == 0) || valueName == nullptr)
            {
                char buf[1024] = {'\0'};
                sprintf_s(buf, "Name: %s; Value: %s\t", szValueName, lpData);
                strcat((char *)totalData, buf);
            }
            ZeroMemory((void *)szValueName, MAX_PATH);
            ZeroMemory(lpData, MAX_DATA_LEN);
            dwValueNameLen = MAX_PATH;
            lpDataLen = MAX_DATA_LEN;
            dwIndex++;
        }
        RegCloseKey(phkResult);
        return totalData;
    }

    return nullptr;
}

Функция readRegistry возвращает пары вида параметр-значение.
На вход принимаются следующие параметры:
- reg : раздел реестра, откуда хотим считать информацию (к примеру - HKEY_LOCAL_MACHINE)
- subKey : строка-путь до интересующего нас ключа
- valueName : имя параметра, который нужно получить. Если valueName == nullptr, функция вернет все параметры и их значения из указанного ключа.

Что же можно получить из реестра? Вот примеры:
C++:
LPCSTR getCPUNameRegistry()
{
    // получаем модель CPU
    return readRegistry(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString");
}

LPCSTR getRun()
{
    // получаем список программ, которые запускаются вместе с системой
    return readRegistry(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
}

LPCSTR getWindowsBuild()
{
    // получаем номер сборки Windows
    return readRegistry(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber");
}

LPCSTR getWinDefExclusions()
{
    // получаем пути исключений Windows Defender
    return readRegistry(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Defender\\Exclusions\\Paths");
}

LPCSTR getWindowsEdition()
{
    // получаем издание Windows
    return readRegistry(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "EditionID");
}

LPCSTR getTimezone()
{
    // получаем часовой пояс
    return readRegistry(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", "StandardName");
}

LPCSTR getMotherboardInfo()
{
    // получаем модель материнской платы
    return readRegistry(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\BIOS", "BaseBoardProduct");
}
Будьте внимательны, чтение некоторых ключей из реестра требуют прав администратора.

Можно считать определенные ключи реестра, чтобы получить список установленных в системе программ:
C++:
LPCSTR getInstalledPrograms()
{

    HKEY hKey;
    LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", 0, KEY_READ, &hKey);
    if (result == ERROR_SUCCESS)
    {
        LPCSTR totalData = new char[4096]{'\0'};
        DWORD index = 0;
        CHAR keyName[MAX_PATH];
        DWORD keyNameSize = MAX_PATH;
        while (RegEnumKeyExA(hKey, index, keyName, &keyNameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
        {
            HKEY subKey;
            if (RegOpenKeyExA(hKey, keyName, 0, KEY_READ, &subKey) == ERROR_SUCCESS)
            {
                CHAR displayName[MAX_PATH];
                DWORD displayNameSize = MAX_PATH;
                DWORD type;
                if (RegQueryValueExA(subKey, "DisplayName", NULL, &type, (LPBYTE)displayName, &displayNameSize) == ERROR_SUCCESS)
                {
                    char buf[1024] = {'\0'};
                    sprintf_s(buf, "%s\t", displayName);
                    strcat_s((char *)totalData, 4096, buf);
                }
                RegCloseKey(subKey);
            }
            keyNameSize = MAX_PATH;
            index++;
        }
        return totalData;
        RegCloseKey(hKey);
    }
    return nullptr;
}

Где это может быть полезно?

- В стилерах. тут все итак понятно.
- В обходе песочниц и виртуальных машин. Некоторые песочницы можно обнаружить по ключевым признакам. Большинство виртуальных машин можно обнаружить по присутствию определенных ключей в реестре. Если условия совпали (мы находимся в виртуалке или песочнице) - выходим из процесса
- В file-based лодырях. Из реестра получаем путь одного из исключений windef - и дропаем исполняемый файл по этому пути (это в теории, и по любому нужны права администратора)

На этом все, о чем я хотел рассказать. Это моя первая статья, так что просьба особо не придираться к ее оформлению. Исходный код можете скачать в прикрепленном файле (из main функции запускаются все проверки, приведенные выше).
 

Вложения

  • wininfo.zip
    3.7 КБ · Просмотры: 21
Последнее редактирование:


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