Антивиртуализация.

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Привет, дамага, в этой относительно небольшой статье я хочу рассмотреть техники антивиртуализации, в случае если наш малварь пытаются запустить на виртуальной машине. Не нашел ничего подобного на форуме, поэтому напишу :)

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


Проверка ЦПУ.
Проверка ЦПУ выполняется с использованием GetSystemInfo WinAPI. Данная функция возвращает структуру SYSTEM_INFO, которая содержит всю информацию о системе, в том числе количество процессоров.
SYSTEM_INFO SysInfo = { 0 };
Код: Скопировать в буфер обмена
Код:
GetSystemInfo(&SysInfo);
if (SysInfo.dwNumberOfProcessors < 2) {
}

Проверка оперативной памяти.
Проверку доступной на данный момент оперативной памяти можно выполнить с помощью GlobalMemoryStatusEx WinAPI. Функция возвращает структуру MEMORYSTATUSEX, которая содержит информацию о состоянии на данный момент физической и виртуальной памяти в системе. Объем ОЗУ можно найти через ullTotalPhys, содержит количество текущей физической памяти в байтах.
Код: Скопировать в буфер обмена
Код:
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.

Код: Скопировать в буфер обмена
Код:
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);
}

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

Антивиртуализация через характеристики аппаратного обеспечения.
Предыдущие коды объединяются все в одну функцию IsVenvByHardwareCheck, она возвращает TRUE, если она обнаруживает виртуализированное окружение.
Код: Скопировать в буфер обмена
Код:
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, представленной ниже.
Код: Скопировать в буфер обмена
Код:
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.

Код: Скопировать в буфер обмена
Код:
// Функция обратного вызова, вызываемая всякий раз, когда '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;
}

Антивиртуализация через имя файла.
Функция ExeDigitsInNameCheck, представленная ниже, осуществляет подсчет цифр в текущем имени файла, применяя метод классификации песочниц. Для этого она использует функцию GetModuleFileNameA для извлечения полного имени файла с путем и функцию PathFindFileNameA для выделения имени файла из пути.

Затем функция проверяет каждый символ в имени файла с использованием функции isdigit, чтобы определить, являются ли они цифрами. Если количество цифр в имени файла превышает 3, ExeDigitsInNameCheck считает, что это песочница, и возвращает значение TRUE.
Код: Скопировать в буфер обмена
Код:
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.
Код: Скопировать в буфер обмена
Код:
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;
}

Антивиртуализация через взаимодействие с пользователем.
Песочницы часто функционируют в безголовом режиме, лишены визуального интерфейса и периферийных устройств, таких как клавиатура и мышь. Безголовые окружения, как правило, поддается автоматизации и управляются сценариями или другими инструментами. Отсутствие взаимодействия с пользователем может служить признаком потенциальной песочничной среды. Например, вредоносное ПО может проверять, не производилось ли ни одного клика мыши или нажатия клавиши в течение определенного временного интервала.
Для этой цели может использоваться функция MouseClicksLogger. Если в течение 20 секунд она не регистрирует более 5 кликов мыши, предполагается, что система работает в песочничной среде.
Код: Скопировать в буфер обмена
Код:
// Мониторинг щелчков мыши в течение 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, приведенной ниже, отслеживается интервал времени между последним взаимодействием пользователя с клавиатурой или мышью. Если этот интервал слишком короткий, это может являться признаком использования системы в виртуализированной среде.
Код: Скопировать в буфер обмена
Код:
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;
}

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

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

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

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

Пример функции задержки выполнения может выглядеть следующим образом:
Код: Скопировать в буфер обмена
Код:
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 успешно задерживает выполнение на указанное время.
Код: Скопировать в буфер обмена
Код:
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, который можно применять для задержки выполнения, является MsgWaitForMultipleObjectsEx. Этот API выполняет ту же задачу, что и WaitForSingleObject, и был также рассмотрен в предыдущих статьях.

Функция DelayExecutionVia_MWFMOEx использует аналогичную логику, представленную в предыдущем разделе, с тем отличием, что здесь используется WinAPI MsgWaitForMultipleObjectsEx. Функция принимает один параметр, ftMinutes, который представляет собой время задержки выполнения в минутах. Если MsgWaitForMultipleObjectsEx успешно задерживает выполнение на указанное время, функция возвращает TRUE.
Код: Скопировать в буфер обмена
Код:
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 показан ниже.
Код: Скопировать в буфер обмена
Код:
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 успешно задерживает выполнение на указанное время.
Код: Скопировать в буфер обмена
Код:
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 предназначен для задержки выполнения кода с целью синхронизации. Этот метод подобен NtWaitForSingleObject, за исключением того, что он не требует объектного дескриптора для ожидания. Функциональность NtDelayExecution аналогична функции Sleep, приостанавливающей выполнение текущего цикла кода. Пример использования NtDelayExecution представлен ниже.
Код: Скопировать в буфер обмена
Код:
NTSTATUS NtDelayExecution(
    IN BOOLEAN              Alertable,      // Можно ли доставить оповещение, когда объект находится в ожидании
    IN PLARGE_INTEGER       DelayInterval   // Указатель на структуру LARGE_INTEGER, указывающую время ожидания
);

NtDelayExecution использует такты для параметра DelayInterval. Функция DelayExecutionVia_NtDE, показанная ниже, использует вызов NtDelayExecution чтобы задержать выполнения на указанное время ftMinutes, представляющее время ожидания в минутах. Функция возвращает TRUE, если NtDelayExecution задерживает выполнение на указанное время.
Код: Скопировать в буфер обмена
Код:
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 для задержки выполнения программы. Это может затруднить отслеживание стека вызовов в работающих потоках и обеспечить скрытие злонамеренных вызовов. Процесс API Hammering включает в себя вызов случайных функций WinAPI для задержки выполнения. В данном примере используются функции WinAPI: CreateFileW, WriteFile и ReadFile. Эти функции работают с временными файлами, создаваемыми в папке временных файлов Windows.

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

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

Эти функции были выбраны благодаря их способности потреблять значительное время при работе с большими объемами данных, что делает их подходящими для 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.
Код: Скопировать в буфер обмена
Код:
// Имя файла для создания
#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.
Код: Скопировать в буфер обмена
Код:
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;
}

Преобразование секунд в циклы.
Фрагмент кода, который я показал ниже, показывает функцию, использующую ранее упомянутую технику.
Код: Скопировать в буфер обмена
Код:
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.
Код: Скопировать в буфер обмена
Код:
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;
}

Фух.
 
Сверху Снизу