implement hal table validation

This commit is contained in:
lhodges1 2024-01-02 03:45:40 +11:00
parent d6bc82f6e9
commit 1e2e41f76e
11 changed files with 613 additions and 241 deletions

View file

@ -11,7 +11,7 @@ open source anti cheat (lol) which I made for fun.
- DPC stackwalking via RtlCaptureStackBackTrace
- Handle stripping via obj callbacks
- Process handle table enumeration
- System module verification
- System module device object verification
- System module .text integrity checks (see known issues)
- Unlinked process detection
- Removed thread PspCidTable entry detection
@ -20,6 +20,7 @@ open source anti cheat (lol) which I made for fun.
- EPT hook detection (currently detects hyperdbg and DdiMon)
- Driver integrity checks both locally and over server
- Hypervisor detection
- HalDispatch and HalPrivateDispatch routine validation
# planned features
@ -35,7 +36,7 @@ open source anti cheat (lol) which I made for fun.
- pcileech firmware detection
- testing program to test the features
- simple user mode logger + usermode logging overhaul
- some more which i cant think of
- data ptr detction (+ chained data ptr walking)
# example

View file

@ -121,6 +121,7 @@
#define REPORT_ILLEGAL_ATTACH_PROCESS 100
#define REPORT_APC_STACKWALK 110
#define REPORT_DPC_STACKWALK 120
#define REPORT_DATA_TABLE_ROUTINE 130
/*
* Generic macros that allow you to quickly determine whether

View file

@ -432,11 +432,6 @@ 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_)
@ -521,6 +516,11 @@ GetDriverDeviceObject()
return driver_config.device_object;
}
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_)

View file

@ -405,21 +405,11 @@ MapDiskImageIntoVirtualAddressSpace(_Inout_ PHANDLE Sec
if (!NT_SUCCESS(status))
{
/*
* It is of utmost importants to mark SectionHandle as null after closing the
* handle from inside this function since an error has occured. The reason this is
* so important is because we are not responsible for freeing the function if it
* succeeds and even if it fails, we still allocate a value to the handle via
* ZwCreateSection. Meaning when the caller goes to check if the handle is null, it
* will not be null and will cause a double free.
*/
/* caller is responsible for closing handle on success, therefore null it */
DEBUG_ERROR("ZwMapViewOfSection failed with status %x", status);
ZwClose(file_handle);
ZwClose(*SectionHandle);
*SectionHandle = NULL;
return status;
}
@ -436,22 +426,7 @@ ComputeHashOfBuffer(_In_ PVOID Buffer,
{
PAGED_CODE();
/*
* Since the windows documentation for the BCrypt functions contain the worst variable
* naming scheme in existence, I will try to explain what they do. (for my sake and any
* readers who also aren't smart enough to understand their otherworldy naming convention)
*
* algo_handle: handle to our BCrypt algorithm
* hash_handle: handle to our BCrypt hash
* bytes_copied: number of bytes that were copied to the output buffer when using
* BCryptGetProperty resulting_hash_size: this is the size of the final buffer hash, it
* should be equal to 32 (sizeof SHA256 hash) hash_object_size: the size of the buffer that
* will temporarily store our hash object hash_object: pointer to the buffer storing our
* hash object which is used to hash our buffer resulting_hash: pointer to the buffer that
* stores the resulting hash of our buffer, this is what we care about
*/
NTSTATUS status = STATUS_ABANDONED;
NTSTATUS status = STATUS_UNSUCCESSFUL;
BCRYPT_ALG_HANDLE algo_handle = NULL;
BCRYPT_HASH_HANDLE hash_handle = NULL;
ULONG bytes_copied = 0;
@ -1350,7 +1325,7 @@ UINT64
MeasureReads(_In_ PVOID Address, _In_ ULONG Count)
{
UINT64 read_average = 0;
UINT64 old_irql;
UINT64 old_irql = 0;
MeasureInstructionRead(Address);
@ -1562,242 +1537,250 @@ FindWinLogonProcess(_In_ PPROCESS_LIST_ENTRY Entry, _In_opt_ PVOID Context)
}
}
//NTSTATUS
//ValidateSystemModules()
// 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 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.");
// DEBUG_VERBOSE("Beginning to validate system modules.");
//
// status = GetSystemModuleInformation(&modules);
// status = GetSystemModuleInformation(&modules);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("GetSystemModuleInformation failed with status %s", status);
// return status;
// }
// 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));
// /*
// * 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);
// 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;
// }
// 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);
// status = RtlAnsiStringToUnicodeString(&path, &ansi_string, TRUE);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("RtlAnsiStringToUnicodeString failed with status %x", status);
// path.Buffer = NULL;
// goto free_iteration;
// }
// 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);
// status = MapDiskImageIntoVirtualAddressSpace(
// &section_handle, &section, &path, &section_size);
//
// if (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("MapDiskImageIntoVirtualAddressSpace failed with status %x",
// status);
// goto free_iteration;
// }
// 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);
// 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;
// }
// 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...");
// /*
// * 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);
// EnumerateProcessListWithCallbackRoutine(FindWinLogonProcess, &process);
//
// if (!process)
// goto free_iteration;
// if (!process)
// goto free_iteration;
//
// KeStackAttachProcess(process, &apc_state);
// KeStackAttachProcess(process, &apc_state);
//
// status = StoreModuleExecutableRegionsInBuffer(&memory_buffer,
// module_info->ImageBase,
// module_info->ImageSize,
// &memory_buffer_size);
// 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);
// }
// 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;
// }
// 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);
// 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));
// 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));
// 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));
// 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_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;
// }
// 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);
// 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;
// }
// 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);
// 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 (!NT_SUCCESS(status))
// {
// DEBUG_ERROR("ComputeHashOfBuffer failed with status %x", status);
// goto free_iteration;
// }
//
// if (!MmIsAddressValid(memory_hash) || !MmIsAddressValid(disk_hash))
// goto free_iteration;
// if (!MmIsAddressValid(memory_hash) || !MmIsAddressValid(disk_hash))
// goto free_iteration;
//
// result = RtlCompareMemory(memory_hash, disk_hash, memory_hash_size);
// 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);
// 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:
// free_iteration:
//
// if (memory_buffer)
// ExFreePoolWithTag(memory_buffer, POOL_TAG_INTEGRITY);
// if (memory_buffer)
// ExFreePoolWithTag(memory_buffer, POOL_TAG_INTEGRITY);
//
// if (memory_hash)
// ExFreePoolWithTag(memory_hash, POOL_TAG_INTEGRITY);
// if (memory_hash)
// ExFreePoolWithTag(memory_hash, POOL_TAG_INTEGRITY);
//
// if (disk_buffer)
// ExFreePoolWithTag(disk_buffer, POOL_TAG_INTEGRITY);
// if (disk_buffer)
// ExFreePoolWithTag(disk_buffer, POOL_TAG_INTEGRITY);
//
// if (disk_hash)
// ExFreePoolWithTag(disk_hash, POOL_TAG_INTEGRITY);
// if (disk_hash)
// ExFreePoolWithTag(disk_hash, POOL_TAG_INTEGRITY);
//
// free_section:
// free_section:
//
// if (section_handle)
// ZwClose(section_handle);
// if (section_handle)
// ZwClose(section_handle);
//
// if (section)
// ZwUnmapViewOfSection(ZwCurrentProcess(), section);
// 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;
// }
// /*
// * 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;
// 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;
// }
// 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);
// if (modules.address)
// ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
//
// return status;
//}
// return status;
// }
STATIC
VOID
@ -2010,8 +1993,8 @@ SystemModuleVerificationDispatchFunction(_In_ PDEVICE_OBJECT DeviceObje
* 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).
* alright. Overall still room for improvement but from a statistical and feel standpoint 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.
@ -2046,10 +2029,12 @@ InitialiseSystemModuleVerificationContext(PSYS_MODULE_VAL_CONTEXT Context)
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;
/* skip hal.dll and ntosrnl.exe */
Context->current_count = 2;
return status;
}
@ -2129,7 +2114,7 @@ SystemModuleVerificationDispatcher()
context->work_items[index] = work_item;
}
DEBUG_VERBOSE("Finished validating modules via dispatcher threads");
DEBUG_VERBOSE("All worker threads dispatched for system module validation.");
return STATUS_SUCCESS;
}

View file

@ -489,11 +489,6 @@ _Dispatch_type_(IRP_MJ_CLOSE) NTSTATUS
DEBUG_INFO("Handle to driver closed.");
/*
* For now its fine, but this will need to be moved to our process load callbacks
* since right now anyone can open a handle to our driver and then close it lol
*/
/* we also lose reports here, so sohuld pass em into the irp before freeing */
FreeGlobalReportQueueObjects();
ProcCloseClearProcessConfiguration();

View file

@ -195,8 +195,9 @@ ValidateThreadViaKernelApcCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
#endif
/*
* TODO: this needs to be refactored to just return the entry not the whole fukin thing
* TODO: return ntstatus and pass result in an out parameter
* This returns a reference to an entry in the system modules array retrieved via
* GetSystemModuleInformation. It's important to remember we don't free the modules once we retrieve
* this reference, and instead only free them when we are done using it.
*/
PRTL_MODULE_EXTENDED_INFO
FindSystemModuleByName(_In_ LPCSTR ModuleName, _In_ PSYSTEM_MODULES SystemModules)
@ -802,6 +803,29 @@ IsInstructionPointerInInvalidRegion(_In_ UINT64 RIP,
return STATUS_SUCCESS;
}
NTSTATUS
IsInstructionPointerInsideModule(_In_ UINT64 Rip,
_In_ PRTL_MODULE_EXTENDED_INFO Module,
_Out_ PBOOLEAN Result)
{
PAGED_CODE();
if (!Rip || !Module || !Result)
return STATUS_INVALID_PARAMETER;
UINT64 base = (UINT64)Module->ImageBase;
UINT64 end = base + Module->ImageSize;
if (Rip >= base && Rip <= end)
{
*Result = TRUE;
return STATUS_SUCCESS;
}
*Result = FALSE;
return STATUS_SUCCESS;
}
/*
* todo: i think we should split this function up into each analysis i.e one for the interrupted
* rip, one for the cid etc.
@ -894,7 +918,8 @@ AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules,
}
else
{
DEBUG_VERBOSE("Thread: %llx was found in PspCidTable", NmiContext[core].kthread);
DEBUG_VERBOSE("Thread: %llx was found in PspCidTable",
NmiContext[core].kthread);
}
if (NmiContext[core].user_thread)
@ -1041,6 +1066,12 @@ HandleNmiIOCTL(_Inout_ PIRP Irp)
SYSTEM_MODULES system_modules = {0};
PNMI_CONTEXT nmi_context = NULL;
status = ValidateHalDispatchTables();
/* do we continue ? probably. */
if (!NT_SUCCESS(status))
DEBUG_ERROR("ValidateHalDispatchTables failed with status %x", status);
nmi_context = ExAllocatePool2(POOL_FLAG_NON_PAGED,
KeQueryActiveProcessorCount(0) * sizeof(NMI_CONTEXT),
NMI_CONTEXT_POOL);
@ -1549,5 +1580,313 @@ end:
if (context)
ExFreePoolWithTag(context, POOL_TAG_DPC);
return status;
}
/* todo: walk the chain of pointers to prevent jmp chaining */
STATIC
NTSTATUS
ValidateTableDispatchRoutines(_In_ PVOID* Base,
_In_ UINT32 Entries,
_In_ PSYSTEM_MODULES Modules,
_Out_ PVOID* Routine)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
BOOLEAN flag = FALSE;
for (UINT32 index = 0; index < Entries; index++)
{
if (!Base[index])
continue;
status = IsInstructionPointerInInvalidRegion(Base[index], Modules, &flag);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("IsInstructionPointerInInvalidRegion failed with status %x",
status);
continue;
}
if (!flag)
*Routine = Base[index];
}
return status;
}
/*
* windows version info: https://www.techthoughts.info/windows-version-numbers/
*
* sizes:
* https://www.vergiliusproject.com/kernels/x64/Windows%2011/22H2%20(2022%20Update)/HAL_PRIVATE_DISPATCH
*/
#define HAL_PRIVATE_DISPATCH_W11_22H2_SIZE 0x4f0
#define HAL_PRIVATE_DISPATCH_W10_22H2_SIZE 0x4b0
#define WINDOWS_10_MAX_BUILD_NUMBER 19045
STATIC
UINT32
GetHalPrivateDispatchTableRoutineCount(_In_ PRTL_OSVERSIONINFOW VersionInfo)
{
if (VersionInfo->dwBuildNumber <= WINDOWS_10_MAX_BUILD_NUMBER)
return (HAL_PRIVATE_DISPATCH_W10_22H2_SIZE / sizeof(UINT64)) - 1;
else
return (HAL_PRIVATE_DISPATCH_W11_22H2_SIZE / sizeof(UINT64)) - 1;
}
STATIC
NTSTATUS
ValidateHalPrivateDispatchTable(_Out_ PVOID* Routine, _In_ PSYSTEM_MODULES Modules)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PVOID table = NULL;
UNICODE_STRING string = RTL_CONSTANT_STRING(L"HalPrivateDispatchTable");
PVOID* base = NULL;
RTL_OSVERSIONINFOW os_info = {0};
UINT32 count = 0;
DEBUG_VERBOSE("Validating HalPrivateDispatchTable.");
table = MmGetSystemRoutineAddress(&string);
if (!table)
return status;
status = GetOsVersionInformation(&os_info);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("GetOsVersionInformation failed with status %x", status);
return status;
}
base = (UINT64)table + sizeof(UINT64);
count = GetHalPrivateDispatchTableRoutineCount(&os_info);
status = ValidateTableDispatchRoutines(base, count, Modules, Routine);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ValidateTableDispatchRoutines failed with status %x", status);
return status;
}
return status;
}
STATIC
NTSTATUS
ValidateHalDispatchTable(_Out_ PVOID* Routine, _In_ PSYSTEM_MODULES Modules)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
BOOLEAN flag = FALSE;
*Routine = NULL;
DEBUG_VERBOSE("Validating HalDispatchTable.");
/*
* Since windows exports all the function pointers inside the HalDispatchTable, we may
* aswell make use of them and validate it this way. While it definitely is ugly, it is the
* safest way to do it.
*
* What if there are 2 invalid routines? hmm.. tink.
*/
status = IsInstructionPointerInInvalidRegion(HalQuerySystemInformation, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalQuerySystemInformation;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalSetSystemInformation, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalSetSystemInformation;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalQueryBusSlots, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalQueryBusSlots;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalReferenceHandlerForBus, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalReferenceHandlerForBus;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalReferenceBusHandler, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalReferenceBusHandler;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalDereferenceBusHandler, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalDereferenceBusHandler;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalInitPnpDriver, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalInitPnpDriver;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalInitPowerManagement, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalInitPowerManagement;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalGetDmaAdapter, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalGetDmaAdapter;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalGetInterruptTranslator, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalGetInterruptTranslator;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalStartMirroring, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalStartMirroring;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalEndMirroring, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalEndMirroring;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalMirrorPhysicalMemory, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalMirrorPhysicalMemory;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalEndOfBoot, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalEndOfBoot;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalMirrorVerify, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalMirrorVerify;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalGetCachedAcpiTable, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalGetCachedAcpiTable;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalSetPciErrorHandlerCallback, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalSetPciErrorHandlerCallback;
else
return status;
status = IsInstructionPointerInInvalidRegion(HalGetPrmCache, Modules, &flag);
if (!flag && NT_SUCCESS(status))
*Routine = HalGetPrmCache;
return status;
}
STATIC
VOID
ReportDataTableInvalidRoutine(_In_ TABLE_ID TableId, _In_ UINT64 Address)
{
PDATA_TABLE_ROUTINE_REPORT report = ExAllocatePool2(
POOL_FLAG_NON_PAGED, sizeof(DATA_TABLE_ROUTINE_REPORT), POOL_TAG_INTEGRITY);
if (!report)
return;
DEBUG_WARNING(
"Invalid data table routine found. Table: %lx, Address: %llx", TableId, Address);
report->address = Address;
report->id = TableId;
report->id = REPORT_DATA_TABLE_ROUTINE;
RtlCopyMemory(report->routine, Address, DATA_TABLE_ROUTINE_BUF_SIZE);
InsertReportToQueue(report);
}
NTSTATUS
ValidateHalDispatchTables()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
SYSTEM_MODULES modules = {0};
PVOID routine1 = NULL;
PVOID routine2 = NULL;
status = GetSystemModuleInformation(&modules);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("GetSystemModuleInformation failed with status %x", status);
return status;
}
status = ValidateHalDispatchTable(&routine1, &modules);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ValidateHalDispatchTable failed with status %x", status);
goto end;
}
if (routine1)
ReportDataTableInvalidRoutine(HalDispatch, routine1);
else
DEBUG_VERBOSE("HalDispatch dispatch routines are valid.");
status = ValidateHalPrivateDispatchTable(&routine2, &modules);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("ValidateHalPrivateDispatchTable failed with status %x", status);
goto end;
}
if (routine2)
ReportDataTableInvalidRoutine(HalPrivateDispatch, routine2);
else
DEBUG_VERBOSE("HalPrivateDispatch dispatch routines are valid.");
end:
if (modules.address)
ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
return status;
}

View file

@ -7,7 +7,24 @@
#include "common.h"
#include "queue.h"
typedef struct NMI_CALLBACK_FAILURE
typedef enum _TABLE_ID
{
HalDispatch = 0,
HalPrivateDispatch
} TABLE_ID;
#define DATA_TABLE_ROUTINE_BUF_SIZE 256
typedef struct _DATA_TABLE_ROUTINE_REPORT
{
UINT32 report_code;
TABLE_ID id;
UINT64 address;
CHAR routine[DATA_TABLE_ROUTINE_BUF_SIZE];
} DATA_TABLE_ROUTINE_REPORT, *PDATA_TABLE_ROUTINE_REPORT;
typedef struct _NMI_CALLBACK_FAILURE
{
INT report_code;
INT were_nmis_disabled;
@ -20,10 +37,10 @@ typedef struct NMI_CALLBACK_FAILURE
typedef struct _DPC_STACKWALK_REPORT
{
UINT32 report_code;
UINT32 report_code;
UINT64 kthread_address;
UINT64 invalid_rip;
CHAR driver[APC_STACKWALK_BUFFER_SIZE];
CHAR driver[APC_STACKWALK_BUFFER_SIZE];
} DPC_STACKWALK_REPORT, *PDPC_STACKWALK_REPORT;
@ -110,4 +127,7 @@ FlipKThreadMiscFlagsFlag(_In_ PKTHREAD Thread, _In_ ULONG FlagIndex, _In_ BOOLEA
NTSTATUS
DispatchStackwalkToEachCpuViaDpc();
NTSTATUS
ValidateHalDispatchTables();
#endif

View file

@ -317,7 +317,7 @@ ScanPageForKernelObjectAllocation(_In_ UINT64 PageBase,
}
}
if (process == NULL)
if (!process)
break;
DEBUG_VERBOSE("Found process via pt walk: %llx", (UINT64)process);

View file

@ -277,6 +277,16 @@ HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
total_size += sizeof(DPC_STACKWALK_REPORT);
break;
case REPORT_DATA_TABLE_ROUTINE:
RtlCopyMemory((UINT64)report_buffer + sizeof(GLOBAL_REPORT_QUEUE_HEADER) +
total_size,
report,
sizeof(DATA_TABLE_ROUTINE_REPORT));
total_size += sizeof(DATA_TABLE_ROUTINE_REPORT);
break;
}
/* QueuePop frees the node, but we still need to free the returned data */

View file

@ -27,6 +27,7 @@
#define REPORT_ILLEGAL_ATTACH_PROCESS 100
#define REPORT_APC_STACKWALK 110
#define REPORT_DPC_STACKWALK 120
#define REPORT_DATA_TABLE_ROUTINE 130
#define TEST_STEAM_64_ID 123456789;
@ -214,6 +215,22 @@ struct DPC_STACKWALK_REPORT
UINT64 invalid_rip;
CHAR driver[4096];
};
enum TABLE_ID
{
HalDispatch = 0,
HalPrivateDispatch
};
#define DATA_TABLE_ROUTINE_BUF_SIZE 256
struct DATA_TABLE_ROUTINE_REPORT
{
UINT32 report_code;
TABLE_ID id;
UINT64 address;
CHAR routine[DATA_TABLE_ROUTINE_BUF_SIZE];
};
}
}

View file

@ -244,6 +244,10 @@ kernelmode::Driver::QueryReportQueue()
ReportTypeFromReportQueue<DPC_STACKWALK_REPORT>(
buffer, &total_size, &hidden_report);
break;
case REPORT_DATA_TABLE_ROUTINE:
ReportTypeFromReportQueue<DATA_TABLE_ROUTINE_REPORT>(
buffer, &total_size, &hidden_report);
break;
default: break;
}
}