Привет! Ты когда-нибудь задумывался, как заглянуть в святая святых Windows - перехватить системные вызовы? Это как вскрыть сейф с динамитом: одно неверное движение - и BSOD! Но если ты готов к вызову, я покажу, как сделать это чисто и профессионально. Погнали?
🔥 Зачем это нужно?
Перехват syscalls - это суперсила для:
- Анализа малвари (антивирусы обожают это),
- Мониторинга процессов (кто что вызывает?),
- Хотфиксов уязвимостей (патчим на лету!).
Но предупреждаю:
⚠️ Пишем для образовательных целей. Ошибки в драйверах = мгновенный крах системы. Тестируем в виртуалке (VMWare/VirtualBox) с отключенным цифровым подписыванием драйверов.
📚 Теория за 60 секунд
- Syscall - это переход из user-mode в kernel-mode через syscall/sysenter.
- SSDT (System Service Descriptor Table) - таблица в ядре, где хранятся указатели на обработчики системных вызовов.
- Перехват: подменяем адрес функции в SSDT на свой обработчик.
- DLL в ядре? Миф! Драйверы - это .sys, но мы можем инжектить код в kernel-space.
💻 Пишем драйвер: пошагово
Шаг 1: Скелет драйвера
#include <ntddk.h>
// Наш перехватчик для NtOpenProcess
NTSTATUS HookedNtOpenProcess(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
) {
KdPrint(("NtOpenProcess вызван! PID: %d\n", ClientId->UniqueProcess));
// Вызываем оригинальную функцию (позже покажем, как её получить)
return OriginalNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}
// Точка входа драйвера
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverUnload = DriverUnload;
KdPrint(("Драйвер загружен!\n"));
return STATUS_SUCCESS;
}
// Выгрузка (обязательно!)
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
KdPrint(("Драйвер выгружен\n"));
}
Шаг 2: Ищем SSDT
Для x64 Windows используем KiServiceTable (не экспортируется, но адрес можно вычислить).
// Получаем адрес SSDT
ULONG_PTR FindSsdtAddress() {
PUCHAR code = (PUCHAR)__readmsr(0xC0000082); // IA32_LSTAR (syscall handler)
// Ищем сигнатуру mov eax, [KiServiceTable]
for (int i = 0; i < 512; i++) {
if (code[i] == 0x8B && code[i+1] == 0x04 && code[i+2] == 0xC5) {
return *(ULONG_PTR*)(code + i + 3);
}
}
return 0;
}
Шаг 3: Меняем адрес в SSDT
// Отключаем защиту страниц (CR0 WP-бит)
void DisableWriteProtection() {
__writecr0(__readcr0() & ~0x10000);
}
// Включаем обратно
void EnableWriteProtection() {
__writecr0(__readcr0() | 0x10000);
}
// Подменяем функцию
void HookSsdtFunction(ULONG_PTR ssdt, UINT32 index, PVOID newHandler) {
ULONG_PTR* table = (ULONG_PTR*)ssdt;
DisableWriteProtection();
OriginalNtOpenProcess = (NtOpenProcessFunc)table[index]; // Сохраняем оригинал
table[index] = (ULONG_PTR)newHandler; // Вставляем перехватчик
EnableWriteProtection();
}
Шаг 4: Инициализация в DriverEntry
// Объявляем оригинальную функцию
typedef NTSTATUS (*NtOpenProcessFunc)(...);
NtOpenProcessFunc OriginalNtOpenProcess = NULL;
NTSTATUS DriverEntry(...) {
ULONG_PTR ssdt = FindSsdtAddress();
if (!ssdt) return STATUS_FAILED_DRIVER_ENTRY;
const UINT32 NtOpenProcessIndex = 0x26; // Индекс для NtOpenProcess (Windows 10)
HookSsdtFunction(ssdt, NtOpenProcessIndex, HookedNtOpenProcess);
...
}
⚡ Фишки для избежания BSOD
- Проверка индексов: Убедись, что индекс syscall актуален для твоей версии Windows (смотри в WinDBG).
- Семафоры: Отключи прерывания на время модификации SSDT (cli/sti).
- Подпись драйвера: Используй Test Mode или DSEFIX для временного отключения проверки подписи.
Итог
Мы создали драйвер, который:
- Перехватывает NtOpenProcess,
- Логирует вызовы без падения системы,
- Корректно выгружается.
Попробуй добавить фильтрацию по PID или скрытие процессов!
Финальное предупреждение:
🔥 Этот код - трамплин в мир ядра. Одно неверное смещение - и виртуалка взорвётся синим экраном. Бэкапься, тестируй, читай Windows Internals.
Удачи, и пусть отладчик будет с тобой!