improve system module val performance

This commit is contained in:
lhodges1 2024-01-01 01:06:24 +11:00
parent 7b15c10ca1
commit d6bc82f6e9
8 changed files with 675 additions and 258 deletions

View file

@ -24,6 +24,7 @@ open source anti cheat (lol) which I made for fun.
# planned features
- Heartbeat
- optimise stuff
- ntoskrnl integrity checks, or atleast a small subset of the kernel encompasing critical functions
- spoofed stack identifier
- process module inline hook detection (this would include checking whether the hook is valid, as many legimate programs hook user mode modules such as discord, nvidia overlay etc.)

View file

@ -105,18 +105,19 @@ DrvLoadInitialiseDriverConfig(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_ST
typedef struct _DRIVER_CONFIG
{
UNICODE_STRING unicode_driver_name;
ANSI_STRING ansi_driver_name;
UNICODE_STRING device_name;
UNICODE_STRING device_symbolic_link;
UNICODE_STRING driver_path;
UNICODE_STRING registry_path;
SYSTEM_INFORMATION system_information;
PVOID apc_contexts[MAXIMUM_APC_CONTEXTS];
PDRIVER_OBJECT driver_object;
PDEVICE_OBJECT device_object;
volatile BOOLEAN unload_in_progress;
KGUARDED_MUTEX lock;
UNICODE_STRING unicode_driver_name;
ANSI_STRING ansi_driver_name;
UNICODE_STRING device_name;
UNICODE_STRING device_symbolic_link;
UNICODE_STRING driver_path;
UNICODE_STRING registry_path;
SYSTEM_INFORMATION system_information;
PVOID apc_contexts[MAXIMUM_APC_CONTEXTS];
PDRIVER_OBJECT driver_object;
PDEVICE_OBJECT device_object;
volatile BOOLEAN unload_in_progress;
KGUARDED_MUTEX lock;
SYS_MODULE_VAL_CONTEXT sys_val_context;
} DRIVER_CONFIG, *PDRIVER_CONFIG;
@ -431,6 +432,11 @@ end:
return status;
}
GetSystemModuleValidationContext(_Out_ PSYS_MODULE_VAL_CONTEXT* Context)
{
*Context = &driver_config.sys_val_context;
}
_IRQL_requires_max_(APC_LEVEL)
_Acquires_lock_(_Lock_kind_mutex_)
_Releases_lock_(_Lock_kind_mutex_)
@ -509,6 +515,12 @@ GetDriverName(_Out_ LPCSTR* DriverName)
KeReleaseGuardedMutex(&driver_config.lock);
}
PDEVICE_OBJECT
GetDriverDeviceObject()
{
return driver_config.device_object;
}
_IRQL_requires_max_(APC_LEVEL)
_Acquires_lock_(_Lock_kind_mutex_)
_Releases_lock_(_Lock_kind_mutex_)
@ -1303,6 +1315,7 @@ DrvLoadInitialiseDriverConfig(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_ST
driver_config.unload_in_progress = FALSE;
driver_config.system_information.virtualised_environment = FALSE;
driver_config.sys_val_context.active = FALSE;
RtlInitUnicodeString(&driver_config.device_name, L"\\Device\\DonnaAC");
RtlInitUnicodeString(&driver_config.device_symbolic_link, L"\\??\\DonnaAC");

View file

@ -8,6 +8,7 @@
#include "common.h"
#include "queue.h"
#include "modules.h"
#include "integrity.h"
#define DRIVER_PATH_MAX_LENGTH 512
#define MOTHERBOARD_SERIAL_CODE_LENGTH 64
@ -185,4 +186,9 @@ _Releases_lock_(_Lock_kind_mutex_)
VOID
GetDriverSymbolicLink(_Out_ PUNICODE_STRING DeviceSymbolicLink);
PDEVICE_OBJECT
GetDriverDeviceObject();
GetSystemModuleValidationContext(_Out_ PSYS_MODULE_VAL_CONTEXT* Context);
#endif

View file

@ -1562,246 +1562,578 @@ FindWinLogonProcess(_In_ PPROCESS_LIST_ENTRY Entry, _In_opt_ PVOID Context)
}
}
/*
* todo: for some reason this works flawlessy on my vm, but fails on my real pc...
*/
NTSTATUS
ValidateSystemModules()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
BOOLEAN bstatus = FALSE;
ANSI_STRING ansi_string = {0};
UNICODE_STRING path = {0};
ULONG section_size = 0;
HANDLE section_handle = NULL;
PVOID section = NULL;
PVOID disk_buffer = NULL;
ULONG disk_buffer_size = 0;
PVOID disk_hash = NULL;
ULONG disk_hash_size = 0;
UINT64 disk_text_base = 0;
UINT64 memory_text_base = 0;
ULONG memory_text_size = 0;
PVOID memory_hash = NULL;
ULONG memory_hash_size = 0;
PVOID memory_buffer = NULL;
ULONG memory_buffer_size = 0;
ULONG result = 0;
PEPROCESS process = NULL;
KAPC_STATE apc_state = {0};
SYSTEM_MODULES modules = {0};
PRTL_MODULE_EXTENDED_INFO module_info = NULL;
PIMAGE_SECTION_HEADER disk_text_header = NULL;
PIMAGE_SECTION_HEADER memory_text_header = NULL;
//NTSTATUS
//ValidateSystemModules()
//{
// NTSTATUS status = STATUS_UNSUCCESSFUL;
// BOOLEAN bstatus = FALSE;
// ANSI_STRING ansi_string = {0};
// UNICODE_STRING path = {0};
// ULONG section_size = 0;
// HANDLE section_handle = NULL;
// PVOID section = NULL;
// PVOID disk_buffer = NULL;
// ULONG disk_buffer_size = 0;
// PVOID disk_hash = NULL;
// ULONG disk_hash_size = 0;
// UINT64 disk_text_base = 0;
// UINT64 memory_text_base = 0;
// ULONG memory_text_size = 0;
// PVOID memory_hash = NULL;
// ULONG memory_hash_size = 0;
// PVOID memory_buffer = NULL;
// ULONG memory_buffer_size = 0;
// ULONG result = 0;
// PEPROCESS process = NULL;
// KAPC_STATE apc_state = {0};
// SYSTEM_MODULES modules = {0};
// PRTL_MODULE_EXTENDED_INFO module_info = NULL;
// PIMAGE_SECTION_HEADER disk_text_header = NULL;
// PIMAGE_SECTION_HEADER memory_text_header = NULL;
//
// DEBUG_VERBOSE("Beginning to validate system modules.");
//
// status = GetSystemModuleInformation(&modules);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("GetSystemModuleInformation failed with status %s", status);
// return status;
// }
//
// /*
// * Since ntoskrnl itself is a process, we skip it here - we will validate it elsewhere.
// */
// for (INT index = 1; index < modules.module_count; index++)
// {
// module_info = (PRTL_MODULE_EXTENDED_INFO)((UINT64)modules.address +
// index * sizeof(RTL_MODULE_EXTENDED_INFO));
//
// RtlInitAnsiString(&ansi_string, module_info->FullPathName);
//
// if (!ansi_string.Buffer)
// {
// DEBUG_ERROR("RtlInitAnsiString failed with status %x", status);
// ansi_string.Buffer = NULL;
// ansi_string.Length = 0;
// ansi_string.MaximumLength = 0;
// continue;
// }
//
// status = RtlAnsiStringToUnicodeString(&path, &ansi_string, TRUE);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("RtlAnsiStringToUnicodeString failed with status %x", status);
// path.Buffer = NULL;
// goto free_iteration;
// }
//
// status = MapDiskImageIntoVirtualAddressSpace(
// &section_handle, &section, &path, &section_size);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("MapDiskImageIntoVirtualAddressSpace failed with status %x",
// status);
// goto free_iteration;
// }
//
// status = StoreModuleExecutableRegionsInBuffer(
// &disk_buffer, section, section_size, &disk_buffer_size);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("StoreModuleExecutableRegionsInBuffer failed with status %x",
// status);
// goto free_iteration;
// }
//
// /*
// * For win32k and related modules, because they are 32bit for us to read the memory
// * we need to attach to a 32 bit process. A simple check is that the 32 bit image
// * base wont be a valid address, while this is hacky it works. Then we simply attach
// * to a 32 bit address space, in our case winlogon, which will allow us to perform
// * the copy.
// */
// if (!MmIsAddressValid(module_info->ImageBase))
// {
// DEBUG_VERBOSE(
// "Win32k related module found, acquiring 32 bit address space...");
//
// EnumerateProcessListWithCallbackRoutine(FindWinLogonProcess, &process);
//
// if (!process)
// goto free_iteration;
//
// KeStackAttachProcess(process, &apc_state);
//
// status = StoreModuleExecutableRegionsInBuffer(&memory_buffer,
// module_info->ImageBase,
// module_info->ImageSize,
// &memory_buffer_size);
//
// KeUnstackDetachProcess(&apc_state);
// }
// else
// {
// status = StoreModuleExecutableRegionsInBuffer(&memory_buffer,
// module_info->ImageBase,
// module_info->ImageSize,
// &memory_buffer_size);
// }
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("StoreModuleExecutableRegionsInbuffer 2 failed with status %x",
// status);
// goto free_iteration;
// }
//
// disk_text_base = (UINT64)disk_buffer + sizeof(INTEGRITY_CHECK_HEADER) +
// sizeof(IMAGE_SECTION_HEADER);
//
// memory_text_base = (UINT64)((UINT64)memory_buffer + sizeof(INTEGRITY_CHECK_HEADER) +
// sizeof(IMAGE_SECTION_HEADER));
//
// disk_text_header =
// (PIMAGE_SECTION_HEADER)((UINT64)disk_buffer + sizeof(INTEGRITY_CHECK_HEADER));
//
// memory_text_header =
// (PIMAGE_SECTION_HEADER)((UINT64)memory_buffer + sizeof(INTEGRITY_CHECK_HEADER));
//
// if (!disk_text_base || !memory_text_base || !disk_buffer || !memory_buffer ||
// !MmIsAddressValid(disk_buffer) || !MmIsAddressValid(memory_buffer))
// {
// DEBUG_ERROR("Buffer(s) are null. An error has occured.");
// goto free_iteration;
// }
//
// if (disk_text_header->SizeOfRawData != memory_text_header->SizeOfRawData)
// {
// DEBUG_WARNING("Executable section sizes differ between images.");
// goto free_iteration;
// }
//
// status = ComputeHashOfBuffer(
// disk_text_base, disk_text_header->SizeOfRawData, &disk_hash, &disk_hash_size);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("ComputeHashOfBuffer failed with status %s", status);
// goto free_iteration;
// }
//
// status = ComputeHashOfBuffer(memory_text_base,
// memory_text_header->SizeOfRawData,
// &memory_hash,
// &memory_hash_size);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("ComputeHashOfBuffer failed with status %x", status);
// goto free_iteration;
// }
//
// if (!MmIsAddressValid(memory_hash) || !MmIsAddressValid(disk_hash))
// goto free_iteration;
//
// result = RtlCompareMemory(memory_hash, disk_hash, memory_hash_size);
//
// if (result = memory_text_header->SizeOfRawData)
// DEBUG_VERBOSE("Module executable sections are valid for the module: %s",
// module_info->FullPathName);
// else
// DEBUG_WARNING("Module regions are not valid for module: %s",
// module_info->FullPathName);
//
// free_iteration:
//
// if (memory_buffer)
// ExFreePoolWithTag(memory_buffer, POOL_TAG_INTEGRITY);
//
// if (memory_hash)
// ExFreePoolWithTag(memory_hash, POOL_TAG_INTEGRITY);
//
// if (disk_buffer)
// ExFreePoolWithTag(disk_buffer, POOL_TAG_INTEGRITY);
//
// if (disk_hash)
// ExFreePoolWithTag(disk_hash, POOL_TAG_INTEGRITY);
//
// free_section:
//
// if (section_handle)
// ZwClose(section_handle);
//
// if (section)
// ZwUnmapViewOfSection(ZwCurrentProcess(), section);
//
// /*
// * Its times like this where you see why allocating all local variables at the
// * beginning of the function may not be so ideal...
// */
// if (path.Buffer)
// {
// RtlFreeUnicodeString(&path);
// path.Buffer = NULL;
// path.Length = 0;
// path.MaximumLength = 0;
// }
//
// ansi_string.Buffer = NULL;
// ansi_string.Length = 0;
// ansi_string.MaximumLength = 0;
//
// section_handle = NULL;
// section = NULL;
// memory_buffer = NULL;
// memory_hash = NULL;
// disk_buffer = NULL;
// disk_hash = NULL;
// }
//
// if (modules.address)
// ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
//
// return status;
//}
DEBUG_VERBOSE("Beginning to validate system modules.");
STATIC
VOID
ValidateSystemModule(_In_ PRTL_MODULE_EXTENDED_INFO Module)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
BOOLEAN bstatus = FALSE;
ANSI_STRING ansi_string = {0};
UNICODE_STRING path = {0};
ULONG section_size = 0;
HANDLE section_handle = NULL;
PVOID section = NULL;
PVOID disk_buffer = NULL;
ULONG disk_buffer_size = 0;
PVOID disk_hash = NULL;
ULONG disk_hash_size = 0;
UINT64 disk_text_base = 0;
UINT64 memory_text_base = 0;
ULONG memory_text_size = 0;
PVOID memory_hash = NULL;
ULONG memory_hash_size = 0;
PVOID memory_buffer = NULL;
ULONG memory_buffer_size = 0;
ULONG result = 0;
PEPROCESS process = NULL;
KAPC_STATE apc_state = {0};
PIMAGE_SECTION_HEADER disk_text_header = NULL;
PIMAGE_SECTION_HEADER memory_text_header = NULL;
RtlInitAnsiString(&ansi_string, Module->FullPathName);
if (!ansi_string.Buffer)
{
DEBUG_ERROR("RtlInitAnsiString failed with status %x", status);
return;
}
status = RtlAnsiStringToUnicodeString(&path, &ansi_string, TRUE);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("RtlAnsiStringToUnicodeString failed with status %x", status);
goto end;
}
status =
MapDiskImageIntoVirtualAddressSpace(&section_handle, &section, &path, &section_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("MapDiskImageIntoVirtualAddressSpace failed with status %x", status);
goto end;
}
status = StoreModuleExecutableRegionsInBuffer(
&disk_buffer, section, section_size, &disk_buffer_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("StoreModuleExecutableRegionsInBuffer failed with status %x", status);
goto end;
}
/*
* For win32k and related modules, because they are 32bit for us to read the
* memory we need to attach to a 32 bit process. A simple check is that the
* 32 bit image base wont be a valid address, while this is hacky it works.
* Then we simply attach to a 32 bit address space, in our case winlogon,
* which will allow us to perform the copy.
*/
if (!MmIsAddressValid(Module->ImageBase))
{
DEBUG_VERBOSE("Win32k related module found, acquiring 32 bit address space...");
EnumerateProcessListWithCallbackRoutine(FindWinLogonProcess, &process);
if (!process)
goto end;
KeStackAttachProcess(process, &apc_state);
status = StoreModuleExecutableRegionsInBuffer(
&memory_buffer, Module->ImageBase, Module->ImageSize, &memory_buffer_size);
KeUnstackDetachProcess(&apc_state);
}
else
{
status = StoreModuleExecutableRegionsInBuffer(
&memory_buffer, Module->ImageBase, Module->ImageSize, &memory_buffer_size);
}
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("StoreModuleExecutableRegionsInbuffer 2 failed with status %x", status);
goto end;
}
disk_text_base =
(UINT64)disk_buffer + sizeof(INTEGRITY_CHECK_HEADER) + sizeof(IMAGE_SECTION_HEADER);
memory_text_base = (UINT64)((UINT64)memory_buffer + sizeof(INTEGRITY_CHECK_HEADER) +
sizeof(IMAGE_SECTION_HEADER));
disk_text_header =
(PIMAGE_SECTION_HEADER)((UINT64)disk_buffer + sizeof(INTEGRITY_CHECK_HEADER));
memory_text_header =
(PIMAGE_SECTION_HEADER)((UINT64)memory_buffer + sizeof(INTEGRITY_CHECK_HEADER));
if (!disk_text_base || !memory_text_base || !disk_buffer || !memory_buffer ||
!MmIsAddressValid(disk_buffer) || !MmIsAddressValid(memory_buffer))
{
DEBUG_ERROR("Buffer(s) are null. An error has occured.");
goto end;
}
if (disk_text_header->SizeOfRawData != memory_text_header->SizeOfRawData)
{
DEBUG_WARNING("Executable section sizes differ between images.");
goto end;
}
status = ComputeHashOfBuffer(
disk_text_base, disk_text_header->SizeOfRawData, &disk_hash, &disk_hash_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ComputeHashOfBuffer failed with status %s", status);
goto end;
}
status = ComputeHashOfBuffer(
memory_text_base, memory_text_header->SizeOfRawData, &memory_hash, &memory_hash_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ComputeHashOfBuffer failed with status %x", status);
goto end;
}
if (!MmIsAddressValid(memory_hash) || !MmIsAddressValid(disk_hash))
goto end;
result = RtlCompareMemory(memory_hash, disk_hash, memory_hash_size);
if (result = memory_text_header->SizeOfRawData)
DEBUG_VERBOSE(
"Thread: %lx: Module executable sections are valid for the module: %s",
PsGetCurrentThreadId(),
Module->FullPathName);
else
DEBUG_WARNING("Thread: %lx: Module regions are not valid for module: %s",
PsGetCurrentThreadId(),
Module->FullPathName);
end:
if (memory_buffer)
ExFreePoolWithTag(memory_buffer, POOL_TAG_INTEGRITY);
if (memory_hash)
ExFreePoolWithTag(memory_hash, POOL_TAG_INTEGRITY);
if (disk_buffer)
ExFreePoolWithTag(disk_buffer, POOL_TAG_INTEGRITY);
if (disk_hash)
ExFreePoolWithTag(disk_hash, POOL_TAG_INTEGRITY);
if (section_handle)
ZwClose(section_handle);
if (section)
ZwUnmapViewOfSection(ZwCurrentProcess(), section);
if (path.Buffer)
RtlFreeUnicodeString(&path);
}
STATIC
VOID
SystemModuleVerificationDispatchFunction(_In_ PDEVICE_OBJECT DeviceObject,
_In_ PSYS_MODULE_VAL_CONTEXT Context)
{
InterlockedIncrement(&Context->active_thread_count);
LONG count = InterlockedExchange(&Context->current_count, Context->current_count);
LONG max = count + Context->block_size;
for (; count < max && count < Context->total_count; count++)
{
if (!InterlockedCompareExchange(
&Context->dispatcher_info[count].validated, TRUE, FALSE))
{
ValidateSystemModule(&Context->module_info[count]);
}
}
if (count == Context->total_count)
InterlockedExchange(&Context->complete, TRUE);
InterlockedExchange(&Context->current_count, count);
InterlockedDecrement(&Context->active_thread_count);
}
#define VALIDATION_BLOCK_SIZE 25
/*
* Multithreaded delayed priority work items improve 1% lows by 25% and reduces average PC latency
* by 10% compared to traditional multithreading. This is important as having high average fps but
* low 1% lows just leads to stuttery gameplay which in competitive multiplayer games is simply not
* alright. Overall still room for improvement but from a statistical and feel which the gameplay is
* much smoother (tested in cs2).
*
* A potential idea for further improvement is finding the cores with the least cpu usages and
* setting the worker threads affinity accordingly.
*/
STATIC
NTSTATUS
InitialiseSystemModuleVerificationContext(PSYS_MODULE_VAL_CONTEXT Context)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
SYSTEM_MODULES modules = {0};
PMODULE_DISPATCHER_HEADER dispatcher_array = NULL;
status = GetSystemModuleInformation(&modules);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("GetSystemModuleInformation failed with status %s", status);
DEBUG_ERROR("GetSystemModuleInformation failed with status %x", status);
return status;
}
/*
* Since ntoskrnl itself is a process, we skip it here - we will validate it elsewhere.
*/
for (INT index = 1; index < modules.module_count; index++)
dispatcher_array = ExAllocatePool2(POOL_FLAG_NON_PAGED,
modules.module_count * sizeof(MODULE_DISPATCHER_HEADER),
POOL_TAG_INTEGRITY);
if (!dispatcher_array)
{
module_info = (PRTL_MODULE_EXTENDED_INFO)((UINT64)modules.address +
index * sizeof(RTL_MODULE_EXTENDED_INFO));
RtlInitAnsiString(&ansi_string, module_info->FullPathName);
if (!ansi_string.Buffer)
{
DEBUG_ERROR("RtlInitAnsiString failed with status %x", status);
ansi_string.Buffer = NULL;
ansi_string.Length = 0;
ansi_string.MaximumLength = 0;
continue;
}
status = RtlAnsiStringToUnicodeString(&path, &ansi_string, TRUE);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("RtlAnsiStringToUnicodeString failed with status %x", status);
path.Buffer = NULL;
goto free_iteration;
}
status = MapDiskImageIntoVirtualAddressSpace(
&section_handle, &section, &path, &section_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("MapDiskImageIntoVirtualAddressSpace failed with status %x",
status);
goto free_iteration;
}
status = StoreModuleExecutableRegionsInBuffer(
&disk_buffer, section, section_size, &disk_buffer_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("StoreModuleExecutableRegionsInBuffer failed with status %x",
status);
goto free_iteration;
}
/*
* For win32k and related modules, because they are 32bit for us to read the memory
* we need to attach to a 32 bit process. A simple check is that the 32 bit image
* base wont be a valid address, while this is hacky it works. Then we simply attach
* to a 32 bit address space, in our case winlogon, which will allow us to perform
* the copy.
*/
if (!MmIsAddressValid(module_info->ImageBase))
{
DEBUG_VERBOSE(
"Win32k related module found, acquiring 32 bit address space...");
EnumerateProcessListWithCallbackRoutine(FindWinLogonProcess, &process);
if (!process)
goto free_iteration;
KeStackAttachProcess(process, &apc_state);
status = StoreModuleExecutableRegionsInBuffer(&memory_buffer,
module_info->ImageBase,
module_info->ImageSize,
&memory_buffer_size);
KeUnstackDetachProcess(&apc_state);
}
else
{
status = StoreModuleExecutableRegionsInBuffer(&memory_buffer,
module_info->ImageBase,
module_info->ImageSize,
&memory_buffer_size);
}
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("StoreModuleExecutableRegionsInbuffer 2 failed with status %x",
status);
goto free_iteration;
}
disk_text_base = (UINT64)disk_buffer + sizeof(INTEGRITY_CHECK_HEADER) +
sizeof(IMAGE_SECTION_HEADER);
memory_text_base = (UINT64)((UINT64)memory_buffer + sizeof(INTEGRITY_CHECK_HEADER) +
sizeof(IMAGE_SECTION_HEADER));
disk_text_header =
(PIMAGE_SECTION_HEADER)((UINT64)disk_buffer + sizeof(INTEGRITY_CHECK_HEADER));
memory_text_header =
(PIMAGE_SECTION_HEADER)((UINT64)memory_buffer + sizeof(INTEGRITY_CHECK_HEADER));
if (!disk_text_base || !memory_text_base || !disk_buffer || !memory_buffer ||
!MmIsAddressValid(disk_buffer) || !MmIsAddressValid(memory_buffer))
{
DEBUG_ERROR("Buffer(s) are null. An error has occured.");
goto free_section;
}
if (disk_text_header->SizeOfRawData != memory_text_header->SizeOfRawData)
{
DEBUG_WARNING("Executable section sizes differ between images.");
goto free_iteration;
}
status = ComputeHashOfBuffer(
disk_text_base, disk_text_header->SizeOfRawData, &disk_hash, &disk_hash_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ComputeHashOfBuffer failed with status %s", status);
goto free_iteration;
}
status = ComputeHashOfBuffer(memory_text_base,
memory_text_header->SizeOfRawData,
&memory_hash,
&memory_hash_size);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ComputeHashOfBuffer failed with status %x", status);
goto free_iteration;
}
if (!MmIsAddressValid(memory_hash) || !MmIsAddressValid(disk_hash))
goto free_iteration;
result = RtlCompareMemory(memory_hash, disk_hash, memory_hash_size);
if (result = memory_text_header->SizeOfRawData)
DEBUG_VERBOSE("Module executable sections are valid for the module: %s",
module_info->FullPathName);
else
DEBUG_WARNING("Module regions are not valid for module: %s",
module_info->FullPathName);
free_iteration:
if (memory_buffer)
ExFreePoolWithTag(memory_buffer, POOL_TAG_INTEGRITY);
if (memory_hash)
ExFreePoolWithTag(memory_hash, POOL_TAG_INTEGRITY);
if (disk_buffer)
ExFreePoolWithTag(disk_buffer, POOL_TAG_INTEGRITY);
if (disk_hash)
ExFreePoolWithTag(disk_hash, POOL_TAG_INTEGRITY);
free_section:
if (section_handle)
ZwClose(section_handle);
if (section)
ZwUnmapViewOfSection(ZwCurrentProcess(), section);
/*
* Its times like this where you see why allocating all local variables at the
* beginning of the function may not be so ideal...
*/
if (path.Buffer)
{
RtlFreeUnicodeString(&path);
path.Buffer = NULL;
path.Length = 0;
path.MaximumLength = 0;
}
ansi_string.Buffer = NULL;
ansi_string.Length = 0;
ansi_string.MaximumLength = 0;
section_handle = NULL;
section = NULL;
memory_buffer = NULL;
memory_hash = NULL;
disk_buffer = NULL;
disk_hash = NULL;
ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
return STATUS_MEMORY_NOT_ALLOCATED;
}
if (modules.address)
ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
Context->active_thread_count = 0;
Context->active = TRUE;
Context->complete = FALSE;
Context->dispatcher_info = dispatcher_array;
Context->module_info = modules.address;
Context->current_count = 0;
Context->total_count = modules.module_count;
Context->block_size = VALIDATION_BLOCK_SIZE;
return status;
}
VOID
FreeWorkItems(_In_ PSYS_MODULE_VAL_CONTEXT Context)
{
for (INT index = 0; index < VERIFICATION_THREAD_COUNT; index++)
{
if (Context->work_items[index])
{
IoFreeWorkItem(Context->work_items[index]);
Context->work_items[index] = 0ull;
}
}
}
STATIC
VOID
FreeModuleVerificationItems(_In_ PSYS_MODULE_VAL_CONTEXT Context)
{
if (!Context->active_thread_count)
{
if (Context->module_info)
ExFreePoolWithTag(Context->module_info, SYSTEM_MODULES_POOL);
if (Context->dispatcher_info)
ExFreePoolWithTag(Context->dispatcher_info, POOL_TAG_INTEGRITY);
}
}
NTSTATUS
SystemModuleVerificationDispatcher()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PSYS_MODULE_VAL_CONTEXT context = NULL;
PIO_WORKITEM work_item = NULL;
GetSystemModuleValidationContext(&context);
if (context->complete)
{
DEBUG_VERBOSE("System modules integrity check complete. Freeing items.");
context->active = FALSE;
FreeModuleVerificationItems(context);
FreeWorkItems(context);
return STATUS_SUCCESS;
}
if (!context->active)
{
DEBUG_VERBOSE("Context not active, generating new one");
status = InitialiseSystemModuleVerificationContext(context);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR(
"InitialiseSystemModuleVerificationContext failed with status %x",
status);
return status;
}
}
else
{
FreeWorkItems(context);
}
for (INT index = 0; index < VERIFICATION_THREAD_COUNT; index++)
{
work_item = IoAllocateWorkItem(GetDriverDeviceObject());
if (!work_item)
continue;
IoQueueWorkItem(
work_item, SystemModuleVerificationDispatchFunction, DelayedWorkQueue, context);
context->work_items[index] = work_item;
}
DEBUG_VERBOSE("Finished validating modules via dispatcher threads");
return STATUS_SUCCESS;
}
/*
* After some further research it appears that performing integrity checks the same way as
* validating other modules is not as straight forward as there are many (thousands) of hooks that

View file

@ -5,6 +5,62 @@
#include "common.h"
typedef struct _MODULE_DISPATCHER_HEADER
{
volatile UINT32 validated; // if this is > 0, a thread is already using it
UINT8 result;
} MODULE_DISPATCHER_HEADER, *PMODULE_DISPATCHER_HEADER;
typedef struct _SYSTEM_MODULE_INFORMATION
{
MODULE_DISPATCHER_HEADER dispatcher_header;
RTL_MODULE_EXTENDED_INFO module_information;
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
// #pragma pack(1)
// typedef struct VERIFICATION_CONTEXT
//{
// // PSYSTEM_MODULE_INFORMATION module_info;
//
//
// } VERIFICATION_CONTEXT, *PVERIFICATION_CONTEXT;
// #pragma pack()
#define VERIFICATION_THREAD_COUNT 4
typedef struct _SYS_MODULE_VAL_CONTEXT
{
/* Stores the number of actively executing worker threads */
volatile LONG active_thread_count;
/* determines whether a validation is in progress */
volatile LONG active;
/* determines whether a validation is complete */
volatile LONG complete;
/* current count of validated modules */
volatile LONG current_count;
/* total count of modules */
UINT32 total_count;
/* number of modules to validate in a single sweep */
UINT32 block_size;
/* pointer to the buffer containing the system module information */
PRTL_MODULE_EXTENDED_INFO module_info;
/* pointer to the array of dispatcher info used to synchonize threads */
PMODULE_DISPATCHER_HEADER dispatcher_info;
/* array of pointers to work items, used to free work items when complete */
PIO_WORKITEM work_items[VERIFICATION_THREAD_COUNT];
} SYS_MODULE_VAL_CONTEXT, *PSYS_MODULE_VAL_CONTEXT;
typedef enum _SMBIOS_TABLE_INDEX
{
SmbiosInformation = 0,
@ -15,8 +71,8 @@ typedef enum _SMBIOS_TABLE_INDEX
} SMBIOS_TABLE_INDEX;
#define SMBIOS_VMWARE_SERIAL_NUMBER_SUB_INDEX 3
#define SMBIOS_NATIVE_SERIAL_NUMBER_SUB_INDEX 4
#define SMBIOS_VENDOR_STRING_SUB_INDEX 1
#define SMBIOS_NATIVE_SERIAL_NUMBER_SUB_INDEX 4
#define SMBIOS_VENDOR_STRING_SUB_INDEX 1
NTSTATUS
GetDriverImageSize(_Inout_ PIRP Irp);
@ -64,4 +120,7 @@ ValidateNtoskrnl();
NTSTATUS
GetOsVersionInformation(_Out_ PRTL_OSVERSIONINFOW VersionInfo);
NTSTATUS
SystemModuleVerificationDispatcher();
#endif

View file

@ -444,7 +444,8 @@ DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
* Currently the validation is buggy, once the validation is better will
* probably bugcheck the system.
*/
status = ValidateSystemModules();
//status = ValidateSystemModules();
status = SystemModuleVerificationDispatcher();
if (!NT_SUCCESS(status))
DEBUG_ERROR("ValidateSystemModules failed with status %x", status);

View file

@ -33,8 +33,8 @@ ValidateThreadsPspCidTableEntry(_In_ PETHREAD Thread)
* is important is because passing in a handle value of 0 which, even though is a valid cid,
* returns a non success status meaning we mark it an invalid cid entry even though it is.
* To combat this we simply add a little check here. The problem is this can be easily
* bypassed by simply modifying the ETHREAD->Cid.UniqueThread identifier.. So while it isnt a
* perfect detection method for now it's good enough.
* bypassed by simply modifying the ETHREAD->Cid.UniqueThread identifier.. So while it isnt
* a perfect detection method for now it's good enough.
*/
if ((UINT64)thread_id < (UINT64)KeQueryActiveProcessorCount(NULL))
return TRUE;
@ -57,6 +57,19 @@ ValidateThreadsPspCidTableEntry(_In_ PETHREAD Thread)
return TRUE;
}
/*
* I did not reverse this myself and previously had no idea how you would go about
* detecting KiAttachProcess so credits to KANKOSHEV for the explanation:
*
* https://github.com/KANKOSHEV/Detect-KeAttachProcess/tree/main
* https://doxygen.reactos.org/d0/dc9/procobj_8c.html#adec6dc539d4a5c0ee7d0f48e24ef0933
*
* To expand on his writeup a little, the offset that he provides is equivalent to
* PKAPC_STATE->Process. This is where KiAttachProcess writes the process that thread is attaching
* to when it's called. The APC_STATE structure holds relevant information about the thread's APC
* state and is quite important during context switch scenarios as it's how the thread determines if
* it has any APC's queued.
*/
_IRQL_always_function_min_(DISPATCH_LEVEL) STATIC VOID
DetectAttachedThreadsProcessCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
_Inout_opt_ PVOID Context)
@ -68,12 +81,17 @@ _IRQL_always_function_min_(DISPATCH_LEVEL) STATIC VOID
GetProtectedProcessEProcess(&protected_process);
if (protected_process == NULL)
if (!protected_process)
return;
apc_state = (PKAPC_STATE)((UINT64)ThreadListEntry->thread + KTHREAD_APC_STATE_OFFSET);
if (apc_state->Process == protected_process)
/*
* Just a sanity check even though it doesnt really make sense for internal threads of our
* protected process to attach..
*/
if (apc_state->Process == protected_process &&
ThreadListEntry->owning_process != protected_process)
{
DEBUG_WARNING("Thread is attached to our protected process: %llx",
(UINT64)ThreadListEntry->thread);
@ -92,19 +110,6 @@ _IRQL_always_function_min_(DISPATCH_LEVEL) STATIC VOID
}
}
/*
* I did not reverse this myself and previously had no idea how you would go about
* detecting KiAttachProcess so credits to KANKOSHEV for the explanation:
*
* https://github.com/KANKOSHEV/Detect-KeAttachProcess/tree/main
* https://doxygen.reactos.org/d0/dc9/procobj_8c.html#adec6dc539d4a5c0ee7d0f48e24ef0933
*
* To expand on his writeup a little, the offset that he provides is equivalent to
* PKAPC_STATE->Process. This is where KiAttachProcess writes the process that thread is attaching
* to when it's called. The APC_STATE structure holds relevant information about the thread's APC
* state and is quite important during context switch scenarios as it's how the thread determines if
* it has any APC's queued.
*/
VOID
DetectThreadsAttachedToProtectedProcess()
{

View file

@ -76,9 +76,9 @@ Init(HINSTANCE hinstDLL)
srand(time(NULL));
while (!GetAsyncKeyState(VK_DELETE))
while (!GetAsyncKeyState(VK_DELETE))
{
int seed = (rand() % 11);
int seed = (rand() % 11);
std::cout << "Seed: " << seed << std::endl;