Можно быстро определить, какие вызовы Windows API перехватываются EDR, используя технику встроенного исправления, когда инструкция jmp вставляется в начало перехватываемой заглушки системного вызова.
Введение
Функция до установки хука
Ниже показана заглушка для
Мы видим, что заглушка системного вызова
Вышеупомянутое относится к большинству функций, начинающихся с
Вот как эти инструкции выглядят в байткоде:
4c 8b d1 b8 — важны для этой статьи, запомните их — мы скоро вернемся к этому в разделе «Проверка наличия хуков».
Функция после установки хука
Ниже показан пример того, как выглядит заглушка системного вызова
Обратите внимание, что в этом случае первая инструкция — это инструкция jmp, перенаправляющая выполнение кода куда-то еще (другой модуль в памяти процесса):
вот как она выглядит в байткоде:
e9 - опкод для ближнего прыжка
0f64f8c7- смещение относительно адреса текущей инструкции, куда перейдет код
Проверка наличия хуков
Зная, что интересные функции/системные вызовы (которые часто используются в вредоносных программах), названия которых начинаются с
Код
Ниже приведен код, который мы можем скомпилировать и запустить на конечной точке с AV/EDR, чтобы найти API, которые, скорее всего, были перехвачены:
Демо
Ниже приведен фрагмент вывода программы, скомпилированной из приведенного выше исходного кода и запущенной в системе с присутствующим EDR. Он показывает некоторые интересные функции (отображены не все), которые, скорее всего, перехвачены, за исключением
Дополнение
После того, как я разместил эту заметку в своем твиттере, я получил следующий ответ от Дерека Ринда:
Дерек предлагает проверить, не перехвачена ли сама инструкция системного вызова. Подпрограмму обработчика системных вызовов (отвечающую за поиск функций в соответствии с номером системного вызова) можно найти, прочитав специальный регистр модели (MSR) по адресу
Ниже показано, как это можно сделать вручную в WinBDG:
От ТС
Эта статья является переводом, оригинал доступен тут
Небольшая, но полезная заметка, особенно для новичков. Решил перевести для форума.
Если есть какие-то ошибки, пишите в лс - исправлю!
Перевод:
Azrv3l cпециально для xss.pro
Введение
Функция до установки хука
Ниже показана заглушка для
NtReadVirtualMemory в системе без EDR, что означает, что системный вызов NtReadVirtualMemory не перехватывается:
Мы видим, что заглушка системного вызова
NtReadVirtualMemory начинается с инструкций:
Код:
00007ffc`d6dcc780 4c8bd1 mov r10,rcx
00007ffc`d6dcc783 b83f000000 mov eax,3Fh
...
Вышеупомянутое относится к большинству функций, начинающихся с
Zw, то есть ZwReadVirtualMemory тоже.Вот как эти инструкции выглядят в байткоде:
Код:
4c 8b d1 b8
4c 8b d1 b8 — важны для этой статьи, запомните их — мы скоро вернемся к этому в разделе «Проверка наличия хуков».
Функция после установки хука
Ниже показан пример того, как выглядит заглушка системного вызова
NtReadVirtualMemory, когда она перехвачена EDR:
Обратите внимание, что в этом случае первая инструкция — это инструкция jmp, перенаправляющая выполнение кода куда-то еще (другой модуль в памяти процесса):
Код:
jmp 0000000047980084
вот как она выглядит в байткоде:
Код:
e9 0f 64 f8 c7
e9 - опкод для ближнего прыжка
0f64f8c7- смещение относительно адреса текущей инструкции, куда перейдет код
Проверка наличия хуков
Зная, что интересные функции/системные вызовы (которые часто используются в вредоносных программах), названия которых начинаются с
Nt | Zw, до установки хука, начнинаются с опкодов: 4c 8b d1 b8, мы можем определить, перехвачена ли данная функция или нет, следуя этому алгоритму:- Перебрать все экспортированные функции ntdll.dll
- Прочитать первые 4 байта заглушки системного вызова и проверить, начинаются ли они с
4c 8b d1 b8- Если да - то хук не установлен
- Если нет, функция, скорее всего, перехвачена (за парой исключений, упомянутых под спойлером "Ложные срабатывания").
NtReadVirtualMemoryначинается с кода операцииe9 0f 64 f8, а не4c 8b d1 b8, что означает, что она скорее всего перехваченаNtWriteVirtualMemoryзапускается с опкодами4c 8b d1 b8, что означает, что она не была перехвачена.
Хотя этот метод очень эффективен при обнаружении функций, перехваченных с помощью встроенных исправлений, он возвращает несколько ложных срабатываний при поиске перехваченных функций внутри 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, которая, как упоминалось ранее, является ложным срабатыванием:
Дополнение
После того, как я разместил эту заметку в своем твиттере, я получил следующий ответ от Дерека Ринда:
Дерек предлагает проверить, не перехвачена ли сама инструкция системного вызова. Подпрограмму обработчика системных вызовов (отвечающую за поиск функций в соответствии с номером системного вызова) можно найти, прочитав специальный регистр модели (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
От ТС
Эта статья является переводом, оригинал доступен тут
Небольшая, но полезная заметка, особенно для новичков. Решил перевести для форума.
Если есть какие-то ошибки, пишите в лс - исправлю!
Перевод:
Azrv3l cпециально для xss.pro