Как правильно реализовать потоки в драйвере ядра Windows? - PullRequest
0 голосов
/ 05 сентября 2018

Я пытаюсь научиться кодировать драйверы ядра 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");
}

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

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

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

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

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

еще один хороший пример FreeLibraryAndExitThread - драйвер по сути является режимом ядра dll, который можно загружать и выгружать. и FreeLibraryAndExitThread точно реализуют функционал, который нам нужен, но только для пользовательских режимов. опять нет такого 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

in 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 и возврата в систему.

так что главный трюк в файлах asm. здесь 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

64

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
0 голосов
/ 05 сентября 2018

После создания потока вы получите 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. Кроме того, этот код использует семафор вместо глобального флага для остановки потока.

...