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

Статья Ищем хуки в системных вызовах

Azrv3l

win32kfull
Эксперт
Регистрация
30.03.2019
Сообщения
215
Реакции
539
Можно быстро определить, какие вызовы Windows API перехватываются EDR, используя технику встроенного исправления, когда инструкция jmp вставляется в начало перехватываемой заглушки системного вызова.

Введение
Функция до установки хука

Ниже показана заглушка для NtReadVirtualMemory в системе без EDR, что означает, что системный вызов NtReadVirtualMemory не перехватывается:

1.png


Мы видим, что заглушка системного вызова NtReadVirtualMemory начинается с инструкций:
Код:
00007ffc`d6dcc780 4c8bd1          mov     r10,rcx
00007ffc`d6dcc783 b83f000000      mov     eax,3Fh
...

Вышеупомянутое относится к большинству функций, начинающихся с Zw, то есть ZwReadVirtualMemory тоже.

Вот как эти инструкции выглядят в байткоде:
Код:
4c 8b d1 b8

2.png


4c 8b d1 b8 — важны для этой статьи, запомните их — мы скоро вернемся к этому в разделе «Проверка наличия хуков».

Функция после установки хука
Ниже показан пример того, как выглядит заглушка системного вызова NtReadVirtualMemory, когда она перехвачена EDR:

3.png


Обратите внимание, что в этом случае первая инструкция — это инструкция jmp, перенаправляющая выполнение кода куда-то еще (другой модуль в памяти процесса):
Код:
jmp 0000000047980084

вот как она выглядит в байткоде:
Код:
e9 0f 64 f8 c7

e9 - опкод для ближнего прыжка
0f64f8c7- смещение относительно адреса текущей инструкции, куда перейдет код


Проверка наличия хуков
Зная, что интересные функции/системные вызовы (которые часто используются в вредоносных программах), названия которых начинаются с Nt | Zw, до установки хука, начнинаются с опкодов: 4c 8b d1 b8, мы можем определить, перехвачена ли данная функция или нет, следуя этому алгоритму:
  1. Перебрать все экспортированные функции ntdll.dll
  2. Прочитать первые 4 байта заглушки системного вызова и проверить, начинаются ли они с 4c 8b d1 b8
    1. Если да - то хук не установлен
    2. Если нет, функция, скорее всего, перехвачена (за парой исключений, упомянутых под спойлером "Ложные срабатывания").
Ниже приведен упрощенный визуальный пример, в который объясняет описанный выше процесс:
  1. NtReadVirtualMemory начинается с кода операции e9 0f 64 f8, а не 4c 8b d1 b8, что означает, что она скорее всего перехвачена
  2. NtWriteVirtualMemory запускается с опкодами 4c 8b d1 b8, что означает, что она не была перехвачена.

4.png


Хотя этот метод очень эффективен при обнаружении функций, перехваченных с помощью встроенных исправлений, он возвращает несколько ложных срабатываний при поиске перехваченных функций внутри ntdll.dll, например:
  • NtGetTickCount
  • NtQuerySystemTime
  • NtdllDefWindowProc_A
  • NtdllDefWindowProc_W
  • NtdllDialogWndProc_A
  • NtdllDialogWndProc_W
  • ZwQuerySystemTime

Код
Ниже приведен код, который мы можем скомпилировать и запустить на конечной точке с AV/EDR, чтобы найти API, которые, скорее всего, были перехвачены:
C++:
#include <iostream>
#include <Windows.h>
int main()
{
    PDWORD functionAddress = (PDWORD)0;
   
    // Получение адресса ntdll
    HMODULE libraryBase = LoadLibraryA("ntdll");
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)libraryBase;
    PIMAGE_NT_HEADERS imageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)libraryBase + dosHeader->e_lfanew);
    // Поиск таблицы экспортов
    DWORD_PTR exportDirectoryRVA = imageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    PIMAGE_EXPORT_DIRECTORY imageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)libraryBase + exportDirectoryRVA);
    // Смещения к списку названий и адресов экспортированных функий
    PDWORD addresOfFunctionsRVA = (PDWORD)((DWORD_PTR)libraryBase + imageExportDirectory->AddressOfFunctions);
    PDWORD addressOfNamesRVA = (PDWORD)((DWORD_PTR)libraryBase + imageExportDirectory->AddressOfNames);
    PWORD addressOfNameOrdinalsRVA = (PWORD)((DWORD_PTR)libraryBase + imageExportDirectory->AddressOfNameOrdinals);
    // Итерация по функциям  Iterate through exported functions of ntdll
    for (DWORD i = 0; i < imageExportDirectory->NumberOfNames; i++)
    {
        // Получение имени
        DWORD functionNameRVA = addressOfNamesRVA[i];
        DWORD_PTR functionNameVA = (DWORD_PTR)libraryBase + functionNameRVA;
        char* functionName = (char*)functionNameVA;
       
        // Получение адреса
        DWORD_PTR functionAddressRVA = 0;
        functionAddressRVA = addresOfFunctionsRVA[addressOfNameOrdinalsRVA[i]];
        functionAddress = (PDWORD)((DWORD_PTR)libraryBase + functionAddressRVA);
        // Образец первых четырёх байтов функции
        char syscallPrologue[4] = { 0x4c, 0x8b, 0xd1, 0xb8 };
        // Функции начинающиеся только с Nt | Zw
        if (strncmp(functionName, (char*)"Nt", 2) == 0 || strncmp(functionName, (char*)"Zw", 2) == 0)
        {
            // Сравнение начала функции с образцом\
            if (memcmp(functionAddress, syscallPrologue, 4) != 0) {
                printf("Potentially hooked: %s : %p\n", functionName, functionAddress);
            }
        }
    }
    return 0;
}

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

5.png


Дополнение
После того, как я разместил эту заметку в своем твиттере, я получил следующий ответ от Дерека Ринда:

6.png


Дерек предлагает проверить, не перехвачена ли сама инструкция системного вызова. Подпрограмму обработчика системных вызовов (отвечающую за поиск функций в соответствии с номером системного вызова) можно найти, прочитав специальный регистр модели (MSR) по адресу 0xc0000082 и подтвердив, что хранящийся там адрес указывает на nt!KiSystemCall64Shadow.

Ниже показано, как это можно сделать вручную в WinBDG:
Код:
lkd> rdmsr c0000082
msr[c0000082] = fffff803`24a13180
lkd> u fffff803`24a13180
nt!KiSystemCall64Shadow:
fffff803`24a13180 0f01f8          swapgs
fffff803`24a13183 654889242510900000 mov   qword ptr gs:[9010h],rsp

7.png


От ТС
Эта статья является переводом, оригинал доступен тут
Небольшая, но полезная заметка, особенно для новичков. Решил перевести для форума.
Если есть какие-то ошибки, пишите в лс - исправлю!

Перевод:
Azrv3l cпециально для xss.pro
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Я когда то давно писал про что-то похожее, не то чтобы мы в процессе что-то особое новое узнали про аверов, но все равно было интересно, тот же доктор веб своими перехватами удивил: https://xss.pro/threads/43097/
 


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