Как правильно реализовать потоки в драйвере ядра Windows?

Я пытаюсь научиться кодировать драйверы ядра Windows. В моем драйвере у меня есть 2 потока, которые в какой-то момент созданы с помощью PsCreateSystemThread

У меня есть глобальная переменная с именем Kill, которая сигнализирует о завершении потоков таким образом.

VOID AThread(IN PVOID Context)
{
    for (;;)
    {
        if(Kill == True)
            break;

        KmWriteProcessMemory(rProcess, &NewValue, dwAAddr, sizeof(NewValue));
    }


    PsTerminateSystemThread(STATUS_SUCCESS);
}

В моей функции выгрузки я устанавливаю Kill = TRUE

VOID f_DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    Kill = TRUE;
    IoDeleteSymbolicLink(&SymLinkName);
    IoDeleteDevice(pDeviceObject);
    DbgPrint("Driver Unloaded successfully..\r\n");
}

В большинстве случаев проблем нет, но иногда машина зависает, когда я пытаюсь выгрузить драйвер. Это происходит чаще, когда в потоках используется какая-то функция сна, поэтому я предполагаю, что происходит сбой, потому что потоки еще не завершены до того, как драйвер попытается выгрузить.

Я не слишком уверен, как использовать синхронизацию и тому подобное, и там не так много четкой информации, которую я могу найти. Итак, как мне правильно реализовать потоки и обеспечить их завершение до выгрузки драйвера?


person Michael Strobel    schedule 05.09.2018    source источник


Ответы (2)


После создания потока у вас будет результат HANDLE threadHandle. Затем вам нужно преобразовать этот дескриптор в PETHREAD ThreadObject; :

ObReferenceObjectByHandle(threadHandle,
                          THREAD_ALL_ACCESS,
                          NULL,
                          KernelMode,
                          &ThreadObject,
                          NULL );

и закрыть threadHandle:

ZwClose(threadHandle);

Если вы хотите остановить поток, установите флаг и дождитесь завершения потока:

Kill = TRUE;

KeWaitForSingleObject(ThreadObject,
                    Executive,
                    KernelMode,
                    FALSE,
                    NULL );

ObDereferenceObject(ThreadObject);

Затем функция f_DriverUnload может выйти.

Вы можете увидеть все это здесь: https://github.com/Microsoft/Windows-driver-samples/tree/master/general/cancel/sys

См. файлы cancel.h и cancel.c. Кроме того, этот код использует семафор вместо глобального флага для остановки потока.

person Alex F    schedule 05.09.2018
comment
@MichaelStrobel - существует гораздо лучшее решение - person RbMm; 05.09.2018
comment
Хотите уточнить? - person Michael Strobel; 05.09.2018

когда вы создаете поток, который использует ваш драйвер, драйвер, конечно, не должен быть выгружен, пока поток не завершится. для этого необходимо вызвать ObfReferenceObject для вашего объекта драйвера перед созданием потока. если создать поток не удалось - вызовите ObfDereferenceObject. и при выходе из потока - нужно вызвать ObfDereferenceObject. но вот проблема - как/откуда это вызвать? вызывать ObfDereferenceObject из конца подпрограммы потока нет смысла - драйвер можно выгрузить внутри ObfDereferenceObject и мы возвращаемся из вызова в несуществующее место в памяти. в идеале будет, если внешний код (сама Windows) вызовет это сразу после возврата потока.

найдите IoAllocateWorkItem для хорошего примера. рабочий элемент - как нить, и драйвер не должен быть выгружен, пока WorkerRoutine не вернется. и здесь система заботится об этом - для этого мы передаем DeviceObject в IoAllocateWorkItem: Указатель на объект драйвера вызывающей стороны или на один из объектов устройства вызывающей стороны. - система ссылается на этот объект (устройство или драйвер), когда мы вызов IoQueueWorkItem и это гарантия того, что драйвер не будет выгружен во время выполнения WorkerRoutine. когда он возвращается - Windows вызывает ObfDereferenceObject для переданного устройства или объекта драйвера. и здесь все ок, потому что после этого мы возвращаемся к коду ядра системы (а не к драйверу). но, к сожалению, PsCreateSystemThread не берет указатель на объект драйвера и не реализует такой функционал.

еще один хороший пример FreeLibraryAndExitThread - Драйвер по сути является dll режима ядра, которую можно загружать и выгружать. и FreeLibraryAndExitThread точно реализуем нужный нам функционал, но только для dll пользовательского режима. опять нет такого API в режиме ядра.

но в любом случае решение возможно. можно самому перейти (не вызывать) к ObfDereferenceObject в конце выполнения потока, но для этого нужно использовать ассемблерный код. невозможно проделать этот трюк в c/c++.

прежде всего давайте объявим указатель на объект драйвера в глобальной переменной - мы инициализируем его допустимым значением в точке входа драйвера.

extern "C" PVOID g_DriverObject;

чем некоторые макросы для получения искаженных имен c++, это необходимо для использования в файле asm:

#if 0
#define __ASM_FUNCTION __pragma(message(__FUNCDNAME__" proc\r\n" __FUNCDNAME__ " endp"))
#define _ASM_FUNCTION {__ASM_FUNCTION;}
#define ASM_FUNCTION {__ASM_FUNCTION;return 0;}
#define CPP_FUNCTION __pragma(message("extern " __FUNCDNAME__ " : PROC ; "  __FUNCSIG__))
#else
#define _ASM_FUNCTION
#define ASM_FUNCTION
#define CPP_FUNCTION
#endif

в c++ мы объявляем 2 функции для потока:

VOID _AThread(IN PVOID Context)_ASM_FUNCTION;

VOID __fastcall AThread(IN PVOID Context)
{
    CPP_FUNCTION;
    // some code here
    // but not call PsTerminateSystemThread !!
}

(не забудьте __fastcall на AThread - для x86 это нужно)

теперь мы создаем поток со следующим кодом:

    ObfReferenceObject(g_DriverObject);
    HANDLE hThread;
    if (0 > PsCreateSystemThread(&hThread, 0, 0, 0, 0, _AThread, ctx)) 
    {
        ObfDereferenceObject(g_DriverObject);
    }
    else
    {
        NtClose(hThread);
    }

поэтому вы устанавливаете точку входа потока в _AThread, которая будет реализована в файле asm. в начале вы звоните ObfReferenceObject(g_DriverObject);. _AThread вызовет фактическую реализацию потока AThread в c++. наконец, он возвращается обратно к _AThread (потому что вы не должны вызывать PsTerminateSystemThread. В любом случае вызов этого API вообще необязателен - когда подпрограмма потока возвращает управление системе - это будет вызываться автоматически). и _AThread в конце разыменовать g_DriverObject и вернуться в систему.

так что главный трюк в ассемблерных файлах. здесь 2 asm для x86 и x64:

x86:

.686p

extern _g_DriverObject:DWORD
extern __imp_@ObfDereferenceObject@4:DWORD
extern ?AThread@@YIXPAX@Z : PROC ; void __fastcall AThread(void *)

_TEXT segment

?_AThread@@YGXPAX@Z proc
        pop ecx
        xchg ecx,[esp]
        call ?AThread@@YIXPAX@Z
        mov ecx,_g_DriverObject
        jmp __imp_@ObfDereferenceObject@4
?_AThread@@YGXPAX@Z endp

_TEXT ends

END

x64:

extern g_DriverObject:QWORD
extern __imp_ObfDereferenceObject:QWORD
extern ?AThread@@YAXPEAX@Z : PROC ; void __cdecl AThread(void *)

_TEXT segment 'CODE'

?_AThread@@YAXPEAX@Z proc
    sub rsp,28h
    call ?AThread@@YAXPEAX@Z
    add rsp,28h
    mov rcx,g_DriverObject
    jmp __imp_ObfDereferenceObject
?_AThread@@YAXPEAX@Z endp

_TEXT ENDS

END
person RbMm    schedule 05.09.2018