some refactoring

This commit is contained in:
lhodges1 2024-01-14 08:33:57 +11:00
parent 19619119df
commit c5e8beaf99
17 changed files with 1161 additions and 1184 deletions

220
driver/apc.c Normal file
View file

@ -0,0 +1,220 @@
#include "apc.h"
#include "driver.h"
#include "imports.h"
VOID
GetApcContextByIndex(_Out_ PVOID* Context, _In_ INT Index)
{
AcquireDriverConfigLock();
*Context = GetApcContextArray()[Index];
ReleaseDriverConfigLock();
}
VOID
GetApcContext(_Out_ PVOID* Context, _In_ LONG ContextIdentifier)
{
AcquireDriverConfigLock();
for (INT index = 0; index < MAXIMUM_APC_CONTEXTS; index++)
{
PAPC_CONTEXT_HEADER header = GetApcContextArray()[index];
if (!header)
continue;
if (header->context_id != ContextIdentifier)
continue;
*Context = header;
goto unlock;
}
unlock:
ReleaseDriverConfigLock();
}
/*
* No need to hold the lock here as the thread freeing the APCs will
* already hold the configuration lock. We also dont want to release and
* reclaim the lock before calling this function since we need to ensure
* we hold the lock during the entire decrement and free process.
*/
BOOLEAN
FreeApcContextStructure(_Out_ PAPC_CONTEXT_HEADER Context)
{
DEBUG_VERBOSE("All APCs executed, freeing context structure");
for (INT index = 0; index < MAXIMUM_APC_CONTEXTS; index++)
{
PUINT64 entry = GetApcContextArray();
if (entry[index] != Context)
continue;
if (Context->count > 0)
return FALSE;
ImpExFreePoolWithTag(Context, POOL_TAG_APC);
entry[index] = NULL;
return TRUE;
}
return FALSE;
}
VOID
IncrementApcCount(_In_ LONG ContextId)
{
PAPC_CONTEXT_HEADER header = NULL;
GetApcContext(&header, ContextId);
if (!header)
return;
/* i actually dont think we need this lock here */
AcquireDriverConfigLock();
header->count += 1;
ReleaseDriverConfigLock();
}
VOID
FreeApcAndDecrementApcCount(_Inout_ PRKAPC Apc, _In_ LONG ContextId)
{
PAPC_CONTEXT_HEADER context = NULL;
ImpExFreePoolWithTag(Apc, POOL_TAG_APC);
GetApcContext(&context, ContextId);
if (!context)
return;
AcquireDriverConfigLock();
context->count -= 1;
ReleaseDriverConfigLock();
}
/*
* The reason we use a query model rather then checking the count of queued APCs
* after each APC free and decrement is that the lock will be recursively acquired by
* freeing threads (i.e executing APCs) rather then APC allocation threads. The reason for this
* being that freeing threads are executing at a higher IRQL then the APC allocation
* thread, hence they are granted higher priority by the scheduler when determining
* which thread will accquire the lock next:
*
* [+] Freeing thread -> ApcKernelRoutine IRQL: 1 (APC_LEVEL)
* [+] Allocation thread -> ValidateThreadViaKernelApcCallback IRQL: 0 (PASSIVE_LEVEL)
*
* As a result, once an APC is executed and reaches the freeing stage, it will acquire the
* lock and decrement it. Then, if atleast 1 APC execution thread is waiting on the lock,
* it will be prioritised due to its higher IRQL and the cycle will continue. Eventually,
* the count will reach 0 due to recursive acquisition by the executing APC threads and then
* the function will free the APC context structure. This will then cause a bug check the next
* time a thread accesses the context structure and hence not good :c.
*
* So to combat this, we add in a flag specifying whether or not an allocation of APCs is
* in progress, and even if the count is 0 we will not free the context structure until
* the count is 0 and allocation_in_progress is 0. We can then call this function alongside
* other query callbacks via IOCTL to constantly monitor the status of open APC contexts.
*/
NTSTATUS
QueryActiveApcContextsForCompletion()
{
for (INT index = 0; index < MAXIMUM_APC_CONTEXTS; index++)
{
PAPC_CONTEXT_HEADER entry = NULL;
GetApcContextByIndex(&entry, index);
AcquireDriverConfigLock();
if (!entry)
goto increment;
if (entry->count > 0 || entry->allocation_in_progress == TRUE)
goto increment;
switch (entry->context_id)
{
case APC_CONTEXT_ID_STACKWALK:
FreeApcStackwalkApcContextInformation(entry);
FreeApcContextStructure(entry);
break;
}
increment:
ReleaseDriverConfigLock();
}
return STATUS_SUCCESS;
}
VOID
InsertApcContext(_In_ PVOID Context)
{
if (IsDriverUnloading())
return STATUS_UNSUCCESSFUL;
AcquireDriverConfigLock();
PAPC_CONTEXT_HEADER header = Context;
for (INT index = 0; index < MAXIMUM_APC_CONTEXTS; index++)
{
PUINT64 entry = GetApcContextArray();
if (entry[index] == NULL)
{
entry[index] = Context;
goto end;
}
}
end:
ReleaseDriverConfigLock();
}
/*
* The driver config structure holds an array of pointers to APC context structures. These
* APC context structures are unique to each APC operation that this driver will perform. For
* example, a single context will manage all APCs that are used to stackwalk, whilst another
* context will be used to manage all APCs used to query a threads memory for example.
*
* Due to the nature of APCs, its important to keep a total or count of the number of APCs we
* have allocated and queued to threads. This information is stored in the APC_CONTEXT_HEADER which
* all APC context structures will contain as the first entry in their structure. It holds the
* ContextId which is a unique identifier for the type of APC operation it is managing aswell as the
* number of currently queued APCs.
*
* When an APC is allocated a queued, we increment this count. When an APC is completed and freed,
* we decrement this counter and free the APC itself. If all APCs have been freed and the counter is
* 0,the following objects will be freed:
*
* 1. Any additional allocations used by the APC stored in the context structure
* 2. The APC context structure for the given APC operation
* 3. The APC context entry in g_DriverConfig->>apc_contexts will be zero'd.
*
* It's important to remember that the driver can unload when pending APC's have not been freed due
* to the limitations windows places on APCs, however I am in the process of finding a solution for
* this.
*/
BOOLEAN
DrvUnloadFreeAllApcContextStructures()
{
AcquireDriverConfigLock();
for (INT index = 0; index < MAXIMUM_APC_CONTEXTS; index++)
{
PUINT64 entry = GetApcContextArray();
if (entry[index] == NULL)
continue;
PAPC_CONTEXT_HEADER context = entry[index];
if (context->count > 0)
{
ReleaseDriverConfigLock();
return FALSE;
}
ImpExFreePoolWithTag(entry, POOL_TAG_APC);
}
unlock:
ReleaseDriverConfigLock();
return TRUE;
}

35
driver/apc.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef APC_H
#define APC_H
#include "common.h"
#include "apc.h"
#include "driver.h"
#include "imports.h"
VOID
GetApcContextByIndex(_Out_ PVOID* Context, _In_ INT Index);
VOID
GetApcContext(_Out_ PVOID* Context, _In_ LONG ContextIdentifier);
BOOLEAN
FreeApcContextStructure(_Out_ PAPC_CONTEXT_HEADER Context);
VOID
IncrementApcCount(_In_ LONG ContextId);
VOID
FreeApcAndDecrementApcCount(_Inout_ PRKAPC Apc, _In_ LONG ContextId);
NTSTATUS
QueryActiveApcContextsForCompletion();
VOID
InsertApcContext(_In_ PVOID Context);
BOOLEAN
DrvUnloadFreeAllApcContextStructures();
#endif

View file

@ -8,44 +8,6 @@
#include "modules.h"
#include "imports.h"
/*
* Interlocked intrinsics are only atomic with respect to other InterlockedXxx functions,
* so all reads and writes to the THREAD_LIST->active flag must be with Interlocked instrinsics
* to ensure atomicity.
*/
typedef struct _THREAD_LIST
{
SINGLE_LIST_ENTRY start;
volatile BOOLEAN active;
KGUARDED_MUTEX lock;
} THREAD_LIST, *PTHREAD_LIST;
/* todo: maybe put this in the global config? hmm.. I kinda like how its encapsulated here tho hm..
*/
PTHREAD_LIST thread_list = NULL;
typedef struct _PROCESS_LIST
{
SINGLE_LIST_ENTRY start;
volatile BOOLEAN active;
KGUARDED_MUTEX lock;
} PROCESS_LIST, *PPROCESS_LIST;
PPROCESS_LIST process_list = NULL;
typedef struct _DRIVER_LIST
{
SINGLE_LIST_ENTRY start;
volatile ULONG count;
volatile BOOLEAN active;
KGUARDED_MUTEX lock;
} DRIVER_LIST, *PDRIVER_LIST;
PDRIVER_LIST driver_list = NULL;
STATIC
BOOLEAN
EnumHandleCallback(_In_ PHANDLE_TABLE HandleTable,
@ -83,62 +45,55 @@ CleanupThreadListFreeCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry)
VOID
CleanupProcessListOnDriverUnload()
{
InterlockedExchange(&process_list->active, FALSE);
PPROCESS_LIST_HEAD list = GetProcessList();
InterlockedExchange(&list->active, FALSE);
ImpPsSetCreateProcessNotifyRoutine(ProcessCreateNotifyRoutine, TRUE);
for (;;)
{
if (!ListFreeFirstEntry(
&process_list->start, &process_list->lock, CleanupProcessListFreeCallback))
{
ImpExFreePoolWithTag(process_list, POOL_TAG_THREAD_LIST);
if (!ListFreeFirstEntry(&list->start, &list->lock, CleanupProcessListFreeCallback))
return;
}
}
}
VOID
CleanupThreadListOnDriverUnload()
{
InterlockedExchange(&thread_list->active, FALSE);
PTHREAD_LIST_HEAD list = GetThreadList();
InterlockedExchange(&list->active, FALSE);
ImpPsRemoveCreateThreadNotifyRoutine(ThreadCreateNotifyRoutine);
for (;;)
{
if (!ListFreeFirstEntry(
&thread_list->start, &thread_list->lock, CleanupThreadListFreeCallback))
{
ImpExFreePoolWithTag(thread_list, POOL_TAG_THREAD_LIST);
if (!ListFreeFirstEntry(&list->start, &list->lock, CleanupThreadListFreeCallback))
return;
}
}
}
VOID
CleanupDriverListOnDriverUnload()
{
InterlockedExchange(&driver_list->active, FALSE);
PDRIVER_LIST_HEAD list = GetDriverList();
InterlockedExchange(&list->active, FALSE);
PsRemoveLoadImageNotifyRoutine(ImageLoadNotifyRoutineCallback);
for (;;)
{
if (!ListFreeFirstEntry(&driver_list->start, &driver_list->lock, NULL))
{
ImpExFreePoolWithTag(driver_list, POOL_TAG_DRIVER_LIST);
if (!ListFreeFirstEntry(&list->start, &list->lock, NULL))
return;
}
}
}
VOID
EnumerateThreadListWithCallbackRoutine(_In_ PVOID CallbackRoutine, _In_opt_ PVOID Context)
{
ImpKeAcquireGuardedMutex(&thread_list->lock);
PTHREAD_LIST_HEAD list = GetThreadList();
ImpKeAcquireGuardedMutex(&list->lock);
if (!CallbackRoutine)
goto unlock;
PTHREAD_LIST_ENTRY entry = thread_list->start.Next;
PTHREAD_LIST_ENTRY entry = list->start.Next;
while (entry)
{
@ -148,18 +103,19 @@ EnumerateThreadListWithCallbackRoutine(_In_ PVOID CallbackRoutine, _In_opt_ PVOI
}
unlock:
ImpKeReleaseGuardedMutex(&thread_list->lock);
ImpKeReleaseGuardedMutex(&list->lock);
}
VOID
EnumerateProcessListWithCallbackRoutine(_In_ PVOID CallbackRoutine, _In_opt_ PVOID Context)
{
ImpKeAcquireGuardedMutex(&process_list->lock);
PPROCESS_LIST_HEAD list = GetProcessList();
ImpKeAcquireGuardedMutex(&list->lock);
if (!CallbackRoutine)
goto unlock;
PPROCESS_LIST_ENTRY entry = process_list->start.Next;
PPROCESS_LIST_ENTRY entry = list->start.Next;
while (entry)
{
@ -169,7 +125,7 @@ EnumerateProcessListWithCallbackRoutine(_In_ PVOID CallbackRoutine, _In_opt_ PVO
}
unlock:
ImpKeReleaseGuardedMutex(&process_list->lock);
ImpKeReleaseGuardedMutex(&list->lock);
}
NTSTATUS
@ -181,15 +137,10 @@ InitialiseDriverList()
SYSTEM_MODULES modules = {0};
PDRIVER_LIST_ENTRY entry = NULL;
PRTL_MODULE_EXTENDED_INFO module_entry = NULL;
PDRIVER_LIST_HEAD list = GetDriverList();
driver_list =
ImpExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(DRIVER_LIST), POOL_TAG_DRIVER_LIST);
if (!driver_list)
return STATUS_MEMORY_NOT_ALLOCATED;
InterlockedExchange(&driver_list->active, TRUE);
ListInit(&driver_list->start, &driver_list->lock);
InterlockedExchange(&list->active, TRUE);
ListInit(&list->start, &list->lock);
status = GetSystemModuleInformation(&modules);
@ -230,7 +181,7 @@ InitialiseDriverList()
entry->hashed = FALSE;
}
ListInsert(&driver_list->start, entry, &driver_list->lock);
ListInsert(&list->start, entry, &list->lock);
}
end:
@ -247,10 +198,11 @@ end:
VOID
FindDriverEntryByBaseAddress(_In_ PVOID ImageBase, _Out_ PDRIVER_LIST_ENTRY* Entry)
{
PDRIVER_LIST_HEAD list = GetDriverList();
ImpKeAcquireGuardedMutex(&list->lock);
*Entry = NULL;
ImpKeAcquireGuardedMutex(&driver_list->lock);
PDRIVER_LIST_ENTRY entry = (PDRIVER_LIST_ENTRY)driver_list->start.Next;
PDRIVER_LIST_ENTRY entry = (PDRIVER_LIST_ENTRY)list->start.Next;
while (entry)
{
@ -263,7 +215,7 @@ FindDriverEntryByBaseAddress(_In_ PVOID ImageBase, _Out_ PDRIVER_LIST_ENTRY* Ent
entry = entry->list.Next;
}
unlock:
ImpKeReleaseGuardedMutex(&driver_list->lock);
ImpKeReleaseGuardedMutex(&list->lock);
}
VOID
@ -274,8 +226,9 @@ ImageLoadNotifyRoutineCallback(_In_opt_ PUNICODE_STRING FullImageName,
NTSTATUS status = STATUS_UNSUCCESSFUL;
PDRIVER_LIST_ENTRY entry = NULL;
RTL_MODULE_EXTENDED_INFO module = {0};
PDRIVER_LIST_HEAD list = GetDriverList();
if (InterlockedExchange(&driver_list->active, driver_list->active) == FALSE)
if (InterlockedExchange(&list->active, list->active) == FALSE)
return;
if (ImageInfo->SystemModeImage == FALSE)
@ -315,50 +268,33 @@ ImageLoadNotifyRoutineCallback(_In_opt_ PUNICODE_STRING FullImageName,
entry->hashed = FALSE;
}
ListInsert(&driver_list->start, entry, &driver_list->lock);
}
NTSTATUS
InitialiseProcessList()
{
PAGED_CODE();
process_list =
ImpExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(PROCESS_LIST), POOL_TAG_THREAD_LIST);
if (!process_list)
return STATUS_MEMORY_NOT_ALLOCATED;
InterlockedExchange(&process_list->active, TRUE);
ListInit(&process_list->start, &process_list->lock);
return STATUS_SUCCESS;
}
NTSTATUS
InitialiseThreadList()
{
PAGED_CODE();
thread_list =
ImpExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(THREAD_LIST), POOL_TAG_THREAD_LIST);
if (!thread_list)
return STATUS_MEMORY_NOT_ALLOCATED;
InterlockedExchange(&thread_list->active, TRUE);
ListInit(&thread_list->start, &thread_list->lock);
return STATUS_SUCCESS;
ListInsert(&list->start, entry, &list->lock);
}
VOID
FindProcessListEntryByProcess(_In_ PKPROCESS Process, _Inout_ PPROCESS_LIST_ENTRY* Entry)
InitialiseProcessList()
{
*Entry = NULL;
ImpKeAcquireGuardedMutex(&process_list->lock);
PPROCESS_LIST_HEAD list = GetProcessList();
InterlockedExchange(&list->active, TRUE);
ListInit(&list->start, &list->lock);
}
PPROCESS_LIST_ENTRY entry = (PPROCESS_LIST_ENTRY)process_list->start.Next;
VOID
InitialiseThreadList()
{
PTHREAD_LIST_HEAD list = GetThreadList();
InterlockedExchange(&list->active, TRUE);
ListInit(&list->start, &list->lock);
}
VOID
FindProcessListEntryByProcess(_In_ PKPROCESS Process, _Out_ PPROCESS_LIST_ENTRY* Entry)
{
PPROCESS_LIST_HEAD list = GetProcessList();
ImpKeAcquireGuardedMutex(&list->lock);
*Entry = NULL;
PPROCESS_LIST_ENTRY entry = (PPROCESS_LIST_ENTRY)list->start.Next;
while (entry)
{
@ -371,16 +307,17 @@ FindProcessListEntryByProcess(_In_ PKPROCESS Process, _Inout_ PPROCESS_LIST_ENTR
entry = entry->list.Next;
}
unlock:
ImpKeReleaseGuardedMutex(&process_list->lock);
ImpKeReleaseGuardedMutex(&list->lock);
}
VOID
FindThreadListEntryByThreadAddress(_In_ PKTHREAD Thread, _Inout_ PTHREAD_LIST_ENTRY* Entry)
FindThreadListEntryByThreadAddress(_In_ PKTHREAD Thread, _Out_ PTHREAD_LIST_ENTRY* Entry)
{
PTHREAD_LIST_HEAD list = GetThreadList();
ImpKeAcquireGuardedMutex(&list->lock);
*Entry = NULL;
ImpKeAcquireGuardedMutex(&thread_list->lock);
PTHREAD_LIST_ENTRY entry = (PTHREAD_LIST_ENTRY)thread_list->start.Next;
PTHREAD_LIST_ENTRY entry = (PTHREAD_LIST_ENTRY)list->start.Next;
while (entry)
{
@ -393,7 +330,7 @@ FindThreadListEntryByThreadAddress(_In_ PKTHREAD Thread, _Inout_ PTHREAD_LIST_EN
entry = entry->list.Next;
}
unlock:
ImpKeReleaseGuardedMutex(&thread_list->lock);
ImpKeReleaseGuardedMutex(&list->lock);
}
VOID
@ -402,8 +339,9 @@ ProcessCreateNotifyRoutine(_In_ HANDLE ParentId, _In_ HANDLE ProcessId, _In_ BOO
PPROCESS_LIST_ENTRY entry = NULL;
PKPROCESS parent = NULL;
PKPROCESS process = NULL;
PPROCESS_LIST_HEAD list = GetProcessList();
if (InterlockedExchange(&process_list->active, process_list->active) == FALSE)
if (InterlockedExchange(&list->active, list->active) == FALSE)
return;
ImpPsLookupProcessByProcessId(ParentId, &parent);
@ -426,7 +364,7 @@ ProcessCreateNotifyRoutine(_In_ HANDLE ParentId, _In_ HANDLE ProcessId, _In_ BOO
entry->parent = parent;
entry->process = process;
ListInsert(&process_list->start, entry, &process_list->lock);
ListInsert(&list->start, entry, &list->lock);
}
else
{
@ -438,7 +376,7 @@ ProcessCreateNotifyRoutine(_In_ HANDLE ParentId, _In_ HANDLE ProcessId, _In_ BOO
ImpObDereferenceObject(entry->parent);
ImpObDereferenceObject(entry->process);
ListRemoveEntry(&process_list->start, entry, &process_list->lock);
ListRemoveEntry(&list->start, entry, &list->lock);
}
}
@ -448,9 +386,10 @@ ThreadCreateNotifyRoutine(_In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _In_ BOOL
PTHREAD_LIST_ENTRY entry = NULL;
PKTHREAD thread = NULL;
PKPROCESS process = NULL;
PTHREAD_LIST_HEAD list = GetThreadList();
/* ensure we don't insert new entries if we are unloading */
if (InterlockedExchange(&thread_list->active, thread_list->active) == FALSE)
if (InterlockedExchange(&list->active, list->active) == FALSE)
return;
ImpPsLookupThreadByThreadId(ThreadId, &thread);
@ -475,7 +414,7 @@ ThreadCreateNotifyRoutine(_In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _In_ BOOL
entry->apc = NULL;
entry->apc_queued = FALSE;
ListInsert(&thread_list->start, &entry->list, &thread_list->lock);
ListInsert(&list->start, &entry->list, &list->lock);
}
else
{
@ -487,7 +426,7 @@ ThreadCreateNotifyRoutine(_In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _In_ BOOL
ImpObDereferenceObject(entry->thread);
ImpObDereferenceObject(entry->owning_process);
ListRemoveEntry(&thread_list->start, entry, &thread_list->lock);
ListRemoveEntry(&list->start, entry, &list->lock);
}
}
@ -856,7 +795,7 @@ TimerObjectCallbackRoutine(_In_ PKDPC Dpc,
_In_opt_ PVOID SystemArgument2)
{
PTIMER_OBJECT timer = (PTIMER_OBJECT)DeferredContext;
/* we dont want to queue our work item if it hasnt executed */
if (timer->state)
return;

View file

@ -1,6 +1,5 @@
#ifndef CALLBACKS_H
#define CALLBACKS_H
#include <ntifs.h>
#include <wdftypes.h>
#include <wdf.h>
@ -25,7 +24,7 @@ typedef struct _OPEN_HANDLE_FAILURE_REPORT
#define PROCESS_CREATE_PROCESS 0x0080
#define PROCESS_TERMINATE 0x0001
#define PROCESS_CREATE_THREAD 0x0002
#define PROCESS_DUP_HANDLE 0x0040
//#define PROCESS_DUP_HANDLE 0x0040
#define PROCESS_QUERY_INFORMATION 0x0400
#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
#define PROCESS_SET_INFORMATION 0x0200
@ -45,24 +44,6 @@ static const uintptr_t EPROCESS_PLIST_ENTRY_OFFSET = 0x448;
static UNICODE_STRING OBJECT_TYPE_PROCESS = RTL_CONSTANT_STRING(L"Process");
static UNICODE_STRING OBJECT_TYPE_THREAD = RTL_CONSTANT_STRING(L"Thread");
typedef struct _THREAD_LIST_ENTRY
{
SINGLE_LIST_ENTRY list;
PKTHREAD thread;
PKPROCESS owning_process;
BOOLEAN apc_queued;
PKAPC apc;
} THREAD_LIST_ENTRY, *PTHREAD_LIST_ENTRY;
typedef struct _PROCESS_LIST_ENTRY
{
SINGLE_LIST_ENTRY list;
PKPROCESS process;
PKPROCESS parent;
} PROCESS_LIST_ENTRY, *PPROCESS_LIST_ENTRY;
#define DRIVER_PATH_LENGTH 0x100
#define SHA_256_HASH_LENGTH 32
@ -94,10 +75,10 @@ ObPreOpCallbackRoutine(_In_ PVOID RegistrationContext,
NTSTATUS
EnumerateProcessHandles(_In_ PPROCESS_LIST_ENTRY ProcessListEntry, _In_opt_ PVOID Context);
NTSTATUS
VOID
InitialiseThreadList();
NTSTATUS
VOID
InitialiseProcessList();
VOID

View file

@ -39,6 +39,149 @@
#define STATIC static
/*
* Interlocked intrinsics are only atomic with respect to other InterlockedXxx functions,
* so all reads and writes to the THREAD_LIST->active flag must be with Interlocked instrinsics
* to ensure atomicity.
*/
typedef struct _THREAD_LIST_HEAD
{
SINGLE_LIST_ENTRY start;
volatile BOOLEAN active;
KGUARDED_MUTEX lock;
} THREAD_LIST_HEAD, *PTHREAD_LIST_HEAD;
typedef struct _PROCESS_LIST_HEAD
{
SINGLE_LIST_ENTRY start;
volatile BOOLEAN active;
KGUARDED_MUTEX lock;
} PROCESS_LIST_HEAD, *PPROCESS_LIST_HEAD;
typedef struct _DRIVER_LIST_HEAD
{
SINGLE_LIST_ENTRY start;
volatile ULONG count;
volatile BOOLEAN active;
KGUARDED_MUTEX lock;
} DRIVER_LIST_HEAD, *PDRIVER_LIST_HEAD;
typedef struct _THREAD_LIST_ENTRY
{
SINGLE_LIST_ENTRY list;
PKTHREAD thread;
PKPROCESS owning_process;
BOOLEAN apc_queued;
PKAPC apc;
} THREAD_LIST_ENTRY, *PTHREAD_LIST_ENTRY;
typedef struct _PROCESS_LIST_ENTRY
{
SINGLE_LIST_ENTRY list;
PKPROCESS process;
PKPROCESS parent;
} PROCESS_LIST_ENTRY, *PPROCESS_LIST_ENTRY;
#define DRIVER_PATH_MAX_LENGTH 512
#define MOTHERBOARD_SERIAL_CODE_LENGTH 64
#define DEVICE_DRIVE_0_SERIAL_CODE_LENGTH 64
#define MAX_REPORTS_PER_IRP 20
#define POOL_TAG_STRINGS 'strs'
#define IOCTL_STORAGE_QUERY_PROPERTY 0x002D1400
#define MAXIMUM_APC_CONTEXTS 10
/*
* ioctl_flag consists of the first 16 bits of the Function part of the CTL code
* cookie_value consists of a static 16 bit value generated by the user mode app on startup
* which is then passed to the driver and stored.
*/
typedef union _SECURITY_COOKIE
{
struct
{
UINT32 ioctl_flag : 16;
UINT32 cookie_value : 16;
} bits;
UINT32 flags;
} SECURITY_COOKIE, *PSECURITY_COOKIE;
typedef struct _TIMER_OBJECT
{
/*
* state = 1: callback in progress
* state = 0: no callback in progress (i.e safe to free and unregister)
*/
volatile LONG state;
PKTIMER timer;
PKDPC dpc;
PIO_WORKITEM work_item;
} TIMER_OBJECT, *PTIMER_OBJECT;
typedef enum _ENVIRONMENT_TYPE
{
NativeWindows = 0,
Vmware,
VirtualBox
} ENVIRONMENT_TYPE;
typedef enum _PROCESSOR_TYPE
{
Unknown = 0,
GenuineIntel,
AuthenticAmd
} PROCESSOR_TYPE;
#define VENDOR_STRING_MAX_LENGTH 256
typedef struct _SYSTEM_INFORMATION
{
CHAR motherboard_serial[MOTHERBOARD_SERIAL_CODE_LENGTH];
CHAR drive_0_serial[DEVICE_DRIVE_0_SERIAL_CODE_LENGTH];
CHAR vendor[VENDOR_STRING_MAX_LENGTH];
BOOLEAN virtualised_environment;
ENVIRONMENT_TYPE environment;
PROCESSOR_TYPE processor;
RTL_OSVERSIONINFOW os_information;
} SYSTEM_INFORMATION, *PSYSTEM_INFORMATION;
typedef struct _OB_CALLBACKS_CONFIG
{
PVOID registration_handle;
KGUARDED_MUTEX lock;
} OB_CALLBACKS_CONFIG, *POB_CALLBACKS_CONFIG;
typedef struct _IRP_QUEUE_HEAD
{
SINGLE_LIST_ENTRY start;
volatile INT count;
KGUARDED_MUTEX lock;
} IRP_QUEUE_HEAD, *PIRP_QUEUE_HEAD;
typedef struct _IRP_QUEUE_ENTRY
{
SINGLE_LIST_ENTRY entry;
PIRP irp;
} IRP_QUEUE_ENTRY, *PIRP_QUEUE_ENTRY;
#define NMI_CONTEXT_POOL '7331'
#define STACK_FRAMES_POOL 'loop'
#define INVALID_DRIVER_LIST_HEAD_POOL 'rwar'
@ -69,7 +212,7 @@
#define POOL_TAG_THREAD_LIST 'list'
#define POOL_TAG_DRIVER_LIST 'drvl'
#define POOL_TAG_IRP_QUEUE 'irpp'
#define POOL_TAG_TIMER 'time'
#define POOL_TAG_TIMER 'time'
#define IA32_APERF_MSR 0x000000E8

File diff suppressed because it is too large Load diff

View file

@ -9,84 +9,7 @@
#include "queue.h"
#include "modules.h"
#include "integrity.h"
#define DRIVER_PATH_MAX_LENGTH 512
#define MOTHERBOARD_SERIAL_CODE_LENGTH 64
#define DEVICE_DRIVE_0_SERIAL_CODE_LENGTH 64
#define MAX_REPORTS_PER_IRP 20
#define POOL_TAG_STRINGS 'strs'
#define IOCTL_STORAGE_QUERY_PROPERTY 0x002D1400
#define MAXIMUM_APC_CONTEXTS 10
typedef struct _TIMER_OBJECT
{
/*
* state = 1: callback in progress
* state = 0: no callback in progress (i.e safe to free and unregister)
*/
volatile LONG state;
PKTIMER timer;
PKDPC dpc;
PIO_WORKITEM work_item;
} TIMER_OBJECT, *PTIMER_OBJECT;
typedef enum _ENVIRONMENT_TYPE
{
NativeWindows = 0,
Vmware,
VirtualBox
} ENVIRONMENT_TYPE;
typedef enum _PROCESSOR_TYPE
{
Unknown = 0,
GenuineIntel,
AuthenticAmd
} PROCESSOR_TYPE;
#define VENDOR_STRING_MAX_LENGTH 256
typedef struct _SYSTEM_INFORMATION
{
CHAR motherboard_serial[MOTHERBOARD_SERIAL_CODE_LENGTH];
CHAR drive_0_serial[DEVICE_DRIVE_0_SERIAL_CODE_LENGTH];
CHAR vendor[VENDOR_STRING_MAX_LENGTH];
BOOLEAN virtualised_environment;
ENVIRONMENT_TYPE environment;
PROCESSOR_TYPE processor;
RTL_OSVERSIONINFOW os_information;
} SYSTEM_INFORMATION, *PSYSTEM_INFORMATION;
typedef struct _OB_CALLBACKS_CONFIG
{
PVOID registration_handle;
KGUARDED_MUTEX lock;
} OB_CALLBACKS_CONFIG, *POB_CALLBACKS_CONFIG;
typedef struct _IRP_QUEUE_HEAD
{
SINGLE_LIST_ENTRY start;
volatile INT count;
KGUARDED_MUTEX lock;
} IRP_QUEUE_HEAD, *PIRP_QUEUE_HEAD;
typedef struct _IRP_QUEUE_ENTRY
{
SINGLE_LIST_ENTRY entry;
PIRP irp;
} IRP_QUEUE_ENTRY, *PIRP_QUEUE_ENTRY;
#include "callbacks.h"
NTSTATUS
ProcLoadInitialiseProcessConfig(_In_ PIRP Irp);
@ -100,27 +23,6 @@ GetProtectedProcessId(_Out_ PLONG ProcessId);
VOID
ReadProcessInitialisedConfigFlag(_Out_ PBOOLEAN Flag);
VOID
GetDriverPath(_Out_ PUNICODE_STRING DriverPath);
VOID
GetDriverConfigSystemInformation(_Out_ PSYSTEM_INFORMATION* SystemInformation);
VOID
GetApcContext(_Inout_ PVOID* Context, _In_ LONG ContextIdentifier);
VOID
InsertApcContext(_In_ PVOID Context);
VOID
GetApcContextByIndex(_Inout_ PVOID* Context, _In_ INT Index);
VOID
IncrementApcCount(_In_ LONG ContextId);
VOID
FreeApcAndDecrementApcCount(_Inout_ PRKAPC Apc, _In_ LONG ContextId);
NTSTATUS
QueryActiveApcContextsForCompletion();
@ -139,30 +41,58 @@ ProcCloseClearProcessConfiguration();
VOID
GetCallbackConfigStructure(_Out_ POB_CALLBACKS_CONFIG* CallbackConfiguration);
VOID
ImageLoadSetProcessId(_In_ HANDLE ProcessId);
VOID
GetDriverDeviceName(_Out_ PUNICODE_STRING DeviceName);
VOID
GetDriverRegistryPath(_Out_ PUNICODE_STRING RegistryPath);
VOID
GetDriverName(_Out_ LPCSTR* DriverName);
VOID
GetDriverSymbolicLink(_Out_ PUNICODE_STRING DeviceSymbolicLink);
LPCSTR
GetDriverName();
PDEVICE_OBJECT
GetDriverDeviceObject();
GetSystemModuleValidationContext(_Out_ PSYS_MODULE_VAL_CONTEXT* Context);
PDRIVER_OBJECT
GetDriverObject();
PIRP_QUEUE_HEAD
GetIrpQueueHead();
PSYS_MODULE_VAL_CONTEXT
GetSystemModuleValidationContext();
PUNICODE_STRING
GetDriverPath();
PUNICODE_STRING
GetDriverRegistryPath();
PUNICODE_STRING
GetDriverDeviceName();
PUNICODE_STRING
GetDriverSymbolicLink();
PSYSTEM_INFORMATION
GetDriverConfigSystemInformation();
PREPORT_QUEUE_HEAD
GetDriverReportQueue();
PTHREAD_LIST_HEAD
GetThreadList();
PDRIVER_LIST_HEAD
GetDriverList();
PPROCESS_LIST_HEAD
GetProcessList();
PUINT64
GetApcContextArray();
VOID
AcquireDriverConfigLock();
VOID
ReleaseDriverConfigLock();
BOOLEAN
IsDriverUnloading();
#endif

View file

@ -188,6 +188,7 @@
<FilesToPackage Include="$(TargetPath)" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="apc.c" />
<ClCompile Include="callbacks.c" />
<ClCompile Include="driver.c" />
<ClCompile Include="hv.c" />
@ -200,6 +201,7 @@
<ClCompile Include="thread.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="apc.h" />
<ClInclude Include="callbacks.h" />
<ClInclude Include="common.h" />
<ClInclude Include="driver.h" />

View file

@ -54,6 +54,9 @@
<ClCompile Include="imports.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="apc.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="driver.h">
@ -92,6 +95,9 @@
<ClInclude Include="imports.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="apc.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<MASM Include="arch.asm">

View file

@ -101,7 +101,7 @@ FindNtExport(PCZPSTR ExportName)
}
NTSTATUS
ResolveNtImports()
ResolveDynamicImports(_In_ PDRIVER_OBJECT DriverObject)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;

View file

@ -10,7 +10,7 @@ VOID
FreeDriverImportsStructure();
NTSTATUS
ResolveNtImports();
ResolveDynamicImports(_In_ PDRIVER_OBJECT DriverObject);
#define IMPORT_FUNCTION_MAX_LENGTH 128
#define IMPORT_FUNCTION_COUNT 256

View file

@ -117,18 +117,10 @@ GetDriverImageSize(_Inout_ PIRP Irp)
PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL;
LPCSTR driver_name = NULL;
LPCSTR driver_name = GetDriverName();
SYSTEM_MODULES modules = {0};
PRTL_MODULE_EXTENDED_INFO driver_info = NULL;
GetDriverName(&driver_name);
if (!driver_name)
{
DEBUG_ERROR("GetDriverName failed with no status.");
return status;
}
status = GetSystemModuleInformation(&modules);
if (!NT_SUCCESS(status))
@ -172,18 +164,10 @@ GetModuleInformationByName(_Out_ PRTL_MODULE_EXTENDED_INFO ModuleInfo, _In_ LPCS
PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL;
LPCSTR driver_name = NULL;
LPCSTR driver_name = GetDriverName();
SYSTEM_MODULES modules = {0};
PRTL_MODULE_EXTENDED_INFO driver_info = NULL;
GetDriverName(&driver_name);
if (!driver_name)
{
DEBUG_ERROR("GetDriverName failed with no status.");
return status;
}
status = GetSystemModuleInformation(&modules);
if (!NT_SUCCESS(status))
@ -562,18 +546,10 @@ RetrieveInMemoryModuleExecutableSections(_Inout_ PIRP Irp)
PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL;
LPCSTR driver_name = NULL;
SIZE_T bytes_written = NULL;
PVOID buffer = NULL;
RTL_MODULE_EXTENDED_INFO module_info = {0};
GetDriverName(&driver_name);
if (!driver_name)
{
DEBUG_ERROR("GetDriverName failed with no status");
return status;
}
LPCSTR driver_name = GetDriverName();
status = GetModuleInformationByName(&module_info, driver_name);
@ -1570,22 +1546,13 @@ NTSTATUS
ValidateOurDriverImage()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
LPCSTR driver_name = NULL;
UNICODE_STRING path = {0};
SYSTEM_MODULES modules = {0};
PRTL_MODULE_EXTENDED_INFO module_info = NULL;
PVOID memory_hash = NULL;
ULONG memory_hash_size = 0;
PDRIVER_LIST_ENTRY entry = NULL;
GetDriverPath(&path);
GetDriverName(&driver_name);
if (!driver_name)
{
DEBUG_ERROR("GetDriverName failed with no status");
return status;
}
LPCSTR driver_name = GetDriverName();
PUNICODE_STRING path = GetDriverPath();
status = GetSystemModuleInformation(&modules);
@ -1772,10 +1739,8 @@ NTSTATUS
SystemModuleVerificationDispatcher()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PSYS_MODULE_VAL_CONTEXT context = NULL;
PIO_WORKITEM work_item = NULL;
GetSystemModuleValidationContext(&context);
PSYS_MODULE_VAL_CONTEXT context = GetSystemModuleValidationContext();
if (context->complete)
{

View file

@ -194,9 +194,8 @@ ValidateIrpInputBuffer(_In_ PIRP Irp, _In_ ULONG RequiredSize)
//_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)
NTSTATUS
DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
DeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp)
{
UNREFERENCED_PARAMETER(DriverObject);
PAGED_CODE();
NTSTATUS status = STATUS_SUCCESS;

View file

@ -14,7 +14,7 @@ typedef struct _DRIVER_INITIATION_INFORMATION
//_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)
NTSTATUS
DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp);
DeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp);
_Dispatch_type_(IRP_MJ_CLOSE) NTSTATUS
DeviceClose(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp);

View file

@ -5,7 +5,7 @@
#include "ioctl.h"
#include "ia32.h"
#include "imports.h"
#include "apc.h"
#include "thread.h"
#define WHITELISTED_MODULE_TAG 'whte'
@ -139,7 +139,7 @@ AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules,
STATIC
NTSTATUS
LaunchNonMaskableInterrupt(_Inout_ PNMI_CONTEXT NmiContext);
LaunchNonMaskableInterrupt();
STATIC
VOID

View file

@ -11,55 +11,20 @@
#include "common.h"
#include "imports.h"
/*
* This mutex is to prevent a new item being pushed to the queue
* while the HandlePeriodicCallbackReportQueue is iterating through
* the objects. This can be an issue because the spinlock is released
* after each report is placed in the IRP buffer which means a new report
* can be pushed into the queue before the next iteration can take ownership
* of the spinlock.
*/
typedef struct _REPORT_QUEUE_CONFIGURATION
{
QUEUE_HEAD head;
volatile BOOLEAN is_driver_unloading;
KGUARDED_MUTEX lock;
} REPORT_QUEUE_CONFIGURATION, *PREPORT_QUEUE_CONFIGURATION;
REPORT_QUEUE_CONFIGURATION report_queue_config = {0};
VOID
InitialiseGlobalReportQueue(_Out_ PBOOLEAN Status)
InitialiseGlobalReportQueue()
{
report_queue_config.head.start = NULL;
report_queue_config.head.end = NULL;
report_queue_config.head.entries = 0;
report_queue_config.is_driver_unloading = FALSE;
PREPORT_QUEUE_HEAD queue = GetDriverReportQueue();
ImpKeInitializeGuardedMutex(&report_queue_config.head.lock);
ImpKeInitializeGuardedMutex(&report_queue_config.lock);
queue->head.start = NULL;
queue->head.end = NULL;
queue->head.entries = 0;
queue->is_driver_unloading = FALSE;
*Status = TRUE;
ImpKeInitializeGuardedMutex(&queue->head.lock);
ImpKeInitializeGuardedMutex(&queue->lock);
}
// PQUEUE_HEAD QueueCreate()
//{
// PQUEUE_HEAD head = ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( QUEUE_HEAD ),
// QUEUE_POOL_TAG );
//
// if ( !head )
// return NULL;
//
// head->end = NULL;
// head->start = NULL;
// head->entries = 0;
//
// KeInitializeSpinLock( &head->lock );
//
// return head;
// }
VOID
QueuePush(_Inout_ PQUEUE_HEAD Head, _In_ PVOID Data)
{
@ -115,33 +80,34 @@ end:
VOID
InsertReportToQueue(_In_ PVOID Report)
{
if (InterlockedExchange(&report_queue_config.is_driver_unloading,
report_queue_config.is_driver_unloading))
PREPORT_QUEUE_HEAD queue = GetDriverReportQueue();
if (InterlockedExchange(&queue->is_driver_unloading, queue->is_driver_unloading))
return;
ImpKeAcquireGuardedMutex(&report_queue_config.lock);
QueuePush(&report_queue_config.head, Report);
ImpKeReleaseGuardedMutex(&report_queue_config.lock);
ImpKeAcquireGuardedMutex(&queue->lock);
QueuePush(&queue->head, Report);
ImpKeReleaseGuardedMutex(&queue->lock);
}
VOID
FreeGlobalReportQueueObjects()
{
InterlockedExchange(&report_queue_config.is_driver_unloading, TRUE);
ImpKeAcquireGuardedMutex(&report_queue_config.lock);
PREPORT_QUEUE_HEAD queue = GetDriverReportQueue();
PVOID report = QueuePop(&report_queue_config.head);
InterlockedExchange(&queue->is_driver_unloading, TRUE);
ImpKeAcquireGuardedMutex(&queue->lock);
while (report != NULL)
PVOID report = QueuePop(&queue->head);
while (report)
{
ImpExFreePoolWithTag(report, REPORT_POOL_TAG);
report = QueuePop(&report_queue_config.head);
DEBUG_VERBOSE("Unloading report queue. Entries remaining: %i",
report_queue_config.head.entries);
report = QueuePop(&queue->head);
}
end:
ImpKeReleaseGuardedMutex(&report_queue_config.lock);
ImpKeReleaseGuardedMutex(&queue->lock);
}
/*
@ -153,7 +119,7 @@ end:
* reports generated from ObRegisterCallbacks for example much easier.
*/
NTSTATUS
HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
HandlePeriodicGlobalReportQueueQuery(_Out_ PIRP Irp)
{
INT count = 0;
NTSTATUS status = STATUS_UNSUCCESSFUL;
@ -163,8 +129,9 @@ HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
ULONG report_buffer_size = 0;
PREPORT_HEADER report_header = NULL;
GLOBAL_REPORT_QUEUE_HEADER header = {0};
PREPORT_QUEUE_HEAD queue = GetDriverReportQueue();
ImpKeAcquireGuardedMutex(&report_queue_config.lock);
ImpKeAcquireGuardedMutex(&queue->lock);
report_buffer_size = sizeof(INVALID_PROCESS_ALLOCATION_REPORT) * MAX_REPORTS_PER_IRP +
sizeof(GLOBAL_REPORT_QUEUE_HEADER);
@ -174,7 +141,7 @@ HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x", status);
ImpKeReleaseGuardedMutex(&report_queue_config.lock);
ImpKeReleaseGuardedMutex(&queue->lock);
return status;
}
@ -183,11 +150,11 @@ HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
if (!report_buffer)
{
ImpKeReleaseGuardedMutex(&report_queue_config.lock);
ImpKeReleaseGuardedMutex(&queue->lock);
return STATUS_MEMORY_NOT_ALLOCATED;
}
report = QueuePop(&report_queue_config.head);
report = QueuePop(&queue->head);
if (report == NULL)
{
@ -278,13 +245,13 @@ HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
/* QueuePop frees the node, but we still need to free the returned data */
ImpExFreePoolWithTag(report, REPORT_POOL_TAG);
report = QueuePop(&report_queue_config.head);
report = QueuePop(&queue->head);
count += 1;
}
end:
ImpKeReleaseGuardedMutex(&report_queue_config.lock);
ImpKeReleaseGuardedMutex(&queue->lock);
Irp->IoStatus.Information = sizeof(GLOBAL_REPORT_QUEUE_HEADER) + total_size;
header.count = count;

View file

@ -6,13 +6,6 @@
#define MAX_REPORTS_PER_IRP 20
typedef struct _QUEUE_NODE
{
struct _QUEUE_NODE* next;
PVOID data;
} QUEUE_NODE, *PQUEUE_NODE;
typedef struct QUEUE_HEAD
{
struct _QUEUE_NODE* start;
@ -22,6 +15,29 @@ typedef struct QUEUE_HEAD
} QUEUE_HEAD, *PQUEUE_HEAD;
/*
* This mutex is to prevent a new item being pushed to the queue
* while the HandlePeriodicCallbackReportQueue is iterating through
* the objects. This can be an issue because the spinlock is released
* after each report is placed in the IRP buffer which means a new report
* can be pushed into the queue before the next iteration can take ownership
* of the spinlock.
*/
typedef struct _REPORT_QUEUE_HEAD
{
QUEUE_HEAD head;
volatile BOOLEAN is_driver_unloading;
KGUARDED_MUTEX lock;
} REPORT_QUEUE_HEAD, *PREPORT_QUEUE_HEAD;
typedef struct _QUEUE_NODE
{
struct _QUEUE_NODE* next;
PVOID data;
} QUEUE_NODE, *PQUEUE_NODE;
typedef struct _GLOBAL_REPORT_QUEUE_HEADER
{
INT count;
@ -43,16 +59,16 @@ PVOID
QueuePop(_Inout_ PQUEUE_HEAD Head);
VOID
InitialiseGlobalReportQueue(_Out_ PBOOLEAN Status);
InitialiseGlobalReportQueue();
VOID
InsertReportToQueue(_In_ PVOID Report);
NTSTATUS
HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp);
HandlePeriodicGlobalReportQueueQuery(_Out_ PIRP Irp);
VOID
FreeGlobalReportQueueObjects();
NTSTATUS
HandlePeriodicGlobalReportQueueQuery(_Out_ PIRP Irp);
VOID
ListInit(_Inout_ PSINGLE_LIST_ENTRY Head, _Inout_ PKGUARDED_MUTEX Lock);
@ -72,4 +88,7 @@ ListRemoveEntry(_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PSINGLE_LIST_ENTRY Entry,
_In_ PKGUARDED_MUTEX Lock);
VOID
FreeGlobalReportQueueObjects();
#endif