mirror of
https://github.com/donnaskiez/ac.git
synced 2024-11-21 22:24:08 +01:00
1892 lines
No EOL
67 KiB
C
1892 lines
No EOL
67 KiB
C
#include "modules.h"
|
|
|
|
#include "callbacks.h"
|
|
#include "driver.h"
|
|
#include "ioctl.h"
|
|
#include "ia32.h"
|
|
|
|
#include "thread.h"
|
|
|
|
#define WHITELISTED_MODULE_TAG 'whte'
|
|
|
|
#define NMI_DELAY 200 * 10000
|
|
|
|
#define WHITELISTED_MODULE_COUNT 11
|
|
#define MODULE_MAX_STRING_SIZE 256
|
|
|
|
#define NTOSKRNL 0
|
|
#define CLASSPNP 1
|
|
#define WDF01000 2
|
|
|
|
/*
|
|
* The modules seen in the array below have been seen to commonly hook other drivers'
|
|
* IOCTL dispatch routines. Its possible to see this by using WinObjEx64 and checking which
|
|
* module each individual dispatch routine lies in. These modules are then addded to the list
|
|
* (in addition to either the driver itself or ntoskrnl) which is seen as a valid region
|
|
* for a drivers dispatch routine to lie within.
|
|
*/
|
|
CHAR WHITELISTED_MODULES[WHITELISTED_MODULE_COUNT][MODULE_MAX_STRING_SIZE] = {"ntoskrnl.exe",
|
|
"CLASSPNP.SYS",
|
|
"Wdf01000.sys",
|
|
"HIDCLASS.SYS",
|
|
"storport.sys",
|
|
"dxgkrnl.sys",
|
|
"ndis.sys",
|
|
"ks.sys",
|
|
"portcls.sys",
|
|
"rdbss.sys",
|
|
"LXCORE.SYS"};
|
|
|
|
#define MODULE_REPORT_DRIVER_NAME_BUFFER_SIZE 128
|
|
|
|
#define REASON_NO_BACKING_MODULE 1
|
|
#define REASON_INVALID_IOCTL_DISPATCH 2
|
|
|
|
#define SYSTEM_IDLE_PROCESS_ID 0
|
|
#define SYSTEM_PROCESS_ID 4
|
|
#define SVCHOST_PROCESS_ID 8
|
|
|
|
typedef struct _WHITELISTED_REGIONS
|
|
{
|
|
UINT64 base;
|
|
UINT64 end;
|
|
|
|
} WHITELISTED_REGIONS, *PWHITELISTED_REGIONS;
|
|
|
|
typedef struct _NMI_POOLS
|
|
{
|
|
PVOID thread_data_pool;
|
|
PVOID stack_frames;
|
|
PVOID nmi_context;
|
|
|
|
} NMI_POOLS, *PNMI_POOLS;
|
|
|
|
typedef struct _MODULE_VALIDATION_FAILURE_HEADER
|
|
{
|
|
INT module_count;
|
|
|
|
} MODULE_VALIDATION_FAILURE_HEADER, *PMODULE_VALIDATION_FAILURE_HEADER;
|
|
|
|
typedef struct _NMI_CONTEXT
|
|
{
|
|
UINT64 interrupted_rip;
|
|
UINT64 interrupted_rsp;
|
|
UINT64 kthread;
|
|
UINT32 callback_count;
|
|
BOOLEAN user_thread;
|
|
|
|
} NMI_CONTEXT, *PNMI_CONTEXT;
|
|
|
|
typedef struct _INVALID_DRIVER
|
|
{
|
|
struct _INVALID_DRIVER* next;
|
|
INT reason;
|
|
PDRIVER_OBJECT driver;
|
|
|
|
} INVALID_DRIVER, *PINVALID_DRIVER;
|
|
|
|
typedef struct _INVALID_DRIVERS_HEAD
|
|
{
|
|
PINVALID_DRIVER first_entry;
|
|
INT count;
|
|
|
|
} INVALID_DRIVERS_HEAD, *PINVALID_DRIVERS_HEAD;
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
PopulateWhitelistedModuleBuffer(_Inout_ PVOID Buffer, _In_ PSYSTEM_MODULES SystemModules);
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDriverIOCTLDispatchRegion(_In_ PDRIVER_OBJECT Driver,
|
|
_In_ PSYSTEM_MODULES Modules,
|
|
_In_ PWHITELISTED_REGIONS WhitelistedRegions,
|
|
_Out_ PBOOLEAN Flag);
|
|
|
|
STATIC
|
|
VOID
|
|
InitDriverList(_Inout_ PINVALID_DRIVERS_HEAD ListHead);
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
AddDriverToList(_Inout_ PINVALID_DRIVERS_HEAD InvalidDriversHead,
|
|
_In_ PDRIVER_OBJECT Driver,
|
|
_In_ INT Reason);
|
|
|
|
STATIC
|
|
VOID
|
|
RemoveInvalidDriverFromList(_Inout_ PINVALID_DRIVERS_HEAD InvalidDriversHead);
|
|
|
|
STATIC
|
|
VOID
|
|
EnumerateInvalidDrivers(_In_ PINVALID_DRIVERS_HEAD InvalidDriversHead);
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDriverObjectHasBackingModule(_In_ PSYSTEM_MODULES ModuleInformation,
|
|
_In_ PDRIVER_OBJECT DriverObject,
|
|
_Out_ PBOOLEAN Result);
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
_Acquires_lock_(_Lock_kind_critical_section_)
|
|
_Releases_lock_(_Lock_kind_critical_section_)
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDriverObjects(_In_ PSYSTEM_MODULES SystemModules,
|
|
_Inout_ PINVALID_DRIVERS_HEAD InvalidDriverListHead);
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules, _Inout_ PIRP Irp);
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
LaunchNonMaskableInterrupt(_Inout_ PNMI_CONTEXT NmiContext);
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ApcRundownRoutine(_In_ PRKAPC Apc);
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ApcKernelRoutine(_In_ PRKAPC Apc,
|
|
_Inout_ _Deref_pre_maybenull_ PKNORMAL_ROUTINE* NormalRoutine,
|
|
_Inout_ _Deref_pre_maybenull_ PVOID* NormalContext,
|
|
_Inout_ _Deref_pre_maybenull_ PVOID* SystemArgument1,
|
|
_Inout_ _Deref_pre_maybenull_ PVOID* SystemArgument2);
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ApcNormalRoutine(_In_opt_ PVOID NormalContext,
|
|
_In_opt_ PVOID SystemArgument1,
|
|
_In_opt_ PVOID SystemArgument2);
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ValidateThreadViaKernelApcCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
|
|
_Inout_opt_ PVOID Context);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
# pragma alloc_text(PAGE, FindSystemModuleByName)
|
|
# pragma alloc_text(PAGE, PopulateWhitelistedModuleBuffer)
|
|
# pragma alloc_text(PAGE, ValidateDriverIOCTLDispatchRegion)
|
|
# pragma alloc_text(PAGE, InitDriverList)
|
|
# pragma alloc_text(PAGE, AddDriverToList)
|
|
# pragma alloc_text(PAGE, RemoveInvalidDriverFromList)
|
|
# pragma alloc_text(PAGE, EnumerateInvalidDrivers)
|
|
# pragma alloc_text(PAGE, ValidateDriverObjectHasBackingModule)
|
|
# pragma alloc_text(PAGE, GetSystemModuleInformation)
|
|
# pragma alloc_text(PAGE, ValidateDriverObjects)
|
|
# pragma alloc_text(PAGE, HandleValidateDriversIOCTL)
|
|
# pragma alloc_text(PAGE, IsInstructionPointerInInvalidRegion)
|
|
# pragma alloc_text(PAGE, AnalyseNmiData)
|
|
# pragma alloc_text(PAGE, LaunchNonMaskableInterrupt)
|
|
# pragma alloc_text(PAGE, HandleNmiIOCTL)
|
|
# pragma alloc_text(PAGE, ApcRundownRoutine)
|
|
# pragma alloc_text(PAGE, ApcKernelRoutine)
|
|
# pragma alloc_text(PAGE, ApcNormalRoutine)
|
|
# pragma alloc_text(PAGE, FlipKThreadMiscFlagsFlag)
|
|
# pragma alloc_text(PAGE, ValidateThreadsViaKernelApc)
|
|
# pragma alloc_text(PAGE, ValidateThreadViaKernelApcCallback)
|
|
#endif
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!ModuleName || !SystemModules)
|
|
return NULL;
|
|
|
|
for (INT index = 0; index < SystemModules->module_count; index++)
|
|
{
|
|
PRTL_MODULE_EXTENDED_INFO system_module =
|
|
(PRTL_MODULE_EXTENDED_INFO)((uintptr_t)SystemModules->address +
|
|
index * sizeof(RTL_MODULE_EXTENDED_INFO));
|
|
|
|
if (strstr(system_module->FullPathName, ModuleName))
|
|
{
|
|
return system_module;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
PopulateWhitelistedModuleBuffer(_Inout_ PVOID Buffer, _In_ PSYSTEM_MODULES SystemModules)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!Buffer || !SystemModules)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
for (INT index = 0; index < WHITELISTED_MODULE_COUNT; index++)
|
|
{
|
|
LPCSTR name = WHITELISTED_MODULES[index];
|
|
|
|
PRTL_MODULE_EXTENDED_INFO module = FindSystemModuleByName(name, SystemModules);
|
|
|
|
/* not everyone will contain all whitelisted modules */
|
|
if (!module)
|
|
continue;
|
|
|
|
WHITELISTED_REGIONS region = {0};
|
|
region.base = (UINT64)module->ImageBase;
|
|
region.end = region.base + module->ImageSize;
|
|
|
|
RtlCopyMemory((UINT64)Buffer + index * sizeof(WHITELISTED_REGIONS),
|
|
®ion,
|
|
sizeof(WHITELISTED_REGIONS));
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDriverIOCTLDispatchRegion(_In_ PDRIVER_OBJECT Driver,
|
|
_In_ PSYSTEM_MODULES Modules,
|
|
_In_ PWHITELISTED_REGIONS WhitelistedRegions,
|
|
_Out_ PBOOLEAN Flag)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!Modules || !Driver || !Flag || !WhitelistedRegions)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
UINT64 dispatch_function = 0;
|
|
UINT64 module_base = 0;
|
|
UINT64 module_end = 0;
|
|
|
|
*Flag = TRUE;
|
|
|
|
dispatch_function = Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL];
|
|
|
|
if (dispatch_function == NULL)
|
|
return STATUS_SUCCESS;
|
|
|
|
for (INT index = 0; index < Modules->module_count; index++)
|
|
{
|
|
PRTL_MODULE_EXTENDED_INFO system_module =
|
|
(PRTL_MODULE_EXTENDED_INFO)((uintptr_t)Modules->address +
|
|
index * sizeof(RTL_MODULE_EXTENDED_INFO));
|
|
|
|
if (system_module->ImageBase != Driver->DriverStart)
|
|
continue;
|
|
|
|
/* make sure our driver has a device object which is required for IOCTL */
|
|
if (Driver->DeviceObject == NULL)
|
|
return STATUS_SUCCESS;
|
|
|
|
module_base = (UINT64)system_module->ImageBase;
|
|
module_end = module_base + system_module->ImageSize;
|
|
|
|
/* firstly, check if its inside its own module */
|
|
if (dispatch_function >= module_base && dispatch_function <= module_end)
|
|
return STATUS_SUCCESS;
|
|
|
|
/*
|
|
* The WDF framework and other low level drivers often hook the dispatch routines
|
|
* when initiating the respective config of their framework or system. With a bit of
|
|
* digging you can view the drivers reponsible for the hooks. What this means is
|
|
* that there will be legit drivers with dispatch routines that point outside of
|
|
* ntoskrnl and their own memory region. So, I have formed a list which contains the
|
|
* drivers that perform these hooks and we iteratively check if the dispatch routine
|
|
* is contained within one of these whitelisted regions. A note on how to imrpove
|
|
* this is the fact that a code cave can be used inside a whitelisted region which
|
|
* then jumps to an invalid region such as a manually mapped driver. So in the
|
|
* future we should implement a function which checks for standard hook
|
|
* implementations like mov rax jmp rax etc.
|
|
*/
|
|
for (INT index = 0; index < WHITELISTED_MODULE_COUNT; index++)
|
|
{
|
|
if (dispatch_function >= WhitelistedRegions[index].base &&
|
|
dispatch_function <= WhitelistedRegions[index].end)
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
DEBUG_WARNING("Driver with invalid dispatch routine found: %s",
|
|
system_module->FullPathName);
|
|
|
|
*Flag = FALSE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InitDriverList(_Inout_ PINVALID_DRIVERS_HEAD ListHead)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ListHead->count = 0;
|
|
ListHead->first_entry = NULL;
|
|
}
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
AddDriverToList(_Inout_ PINVALID_DRIVERS_HEAD InvalidDriversHead,
|
|
_In_ PDRIVER_OBJECT Driver,
|
|
_In_ INT Reason)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PINVALID_DRIVER new_entry = ExAllocatePool2(
|
|
POOL_FLAG_NON_PAGED, sizeof(INVALID_DRIVER), INVALID_DRIVER_LIST_ENTRY_POOL);
|
|
|
|
if (!new_entry)
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
new_entry->driver = Driver;
|
|
new_entry->reason = Reason;
|
|
new_entry->next = InvalidDriversHead->first_entry;
|
|
InvalidDriversHead->first_entry = new_entry;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
RemoveInvalidDriverFromList(_Inout_ PINVALID_DRIVERS_HEAD InvalidDriversHead)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (InvalidDriversHead->first_entry)
|
|
{
|
|
PINVALID_DRIVER entry = InvalidDriversHead->first_entry;
|
|
InvalidDriversHead->first_entry = InvalidDriversHead->first_entry->next;
|
|
ExFreePoolWithTag(entry, INVALID_DRIVER_LIST_ENTRY_POOL);
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
EnumerateInvalidDrivers(_In_ PINVALID_DRIVERS_HEAD InvalidDriversHead)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PINVALID_DRIVER entry = InvalidDriversHead->first_entry;
|
|
|
|
while (entry != NULL)
|
|
{
|
|
DEBUG_VERBOSE("Invalid Driver: %wZ", entry->driver->DriverName);
|
|
entry = entry->next;
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDriverObjectHasBackingModule(_In_ PSYSTEM_MODULES ModuleInformation,
|
|
_In_ PDRIVER_OBJECT DriverObject,
|
|
_Out_ PBOOLEAN Result)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!ModuleInformation || !DriverObject || !Result)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
for (INT i = 0; i < ModuleInformation->module_count; i++)
|
|
{
|
|
PRTL_MODULE_EXTENDED_INFO system_module =
|
|
(PRTL_MODULE_EXTENDED_INFO)((uintptr_t)ModuleInformation->address +
|
|
i * sizeof(RTL_MODULE_EXTENDED_INFO));
|
|
|
|
if (system_module->ImageSize == 0 || system_module->ImageBase == 0)
|
|
return STATUS_INVALID_MEMBER;
|
|
|
|
if (system_module->ImageBase == DriverObject->DriverStart)
|
|
{
|
|
*Result = TRUE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
DEBUG_WARNING("Driver found with no backing system image at address: %llx",
|
|
(UINT64)DriverObject->DriverStart);
|
|
|
|
*Result = FALSE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// https://imphash.medium.com/windows-process-internals-a-few-concepts-to-know-before-jumping-on-memory-forensics-part-3-4a0e195d947b
|
|
NTSTATUS
|
|
GetSystemModuleInformation(_Out_ PSYSTEM_MODULES ModuleInformation)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!ModuleInformation)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
ULONG size = 0;
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
/*
|
|
* query system module information without an output buffer to get
|
|
* number of bytes required to store all module info structures
|
|
*/
|
|
status = RtlQueryModuleInformation(&size, sizeof(RTL_MODULE_EXTENDED_INFO), NULL);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("RtlQueryModuleInformation failed with status %x", status);
|
|
return status;
|
|
}
|
|
|
|
/* Allocate a pool equal to the output size of RtlQueryModuleInformation */
|
|
PRTL_MODULE_EXTENDED_INFO driver_information =
|
|
ExAllocatePool2(POOL_FLAG_NON_PAGED, size, SYSTEM_MODULES_POOL);
|
|
|
|
if (!driver_information)
|
|
{
|
|
DEBUG_ERROR("Failed to allocate pool LOL");
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
}
|
|
|
|
/* Query the modules again this time passing a pointer to the allocated buffer */
|
|
status =
|
|
RtlQueryModuleInformation(&size, sizeof(RTL_MODULE_EXTENDED_INFO), driver_information);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("RtlQueryModuleInformation 2 failed with status %x", status);
|
|
ExFreePoolWithTag(driver_information, SYSTEM_MODULES_POOL);
|
|
return STATUS_ABANDONED;
|
|
}
|
|
|
|
ModuleInformation->address = driver_information;
|
|
ModuleInformation->module_count = size / sizeof(RTL_MODULE_EXTENDED_INFO);
|
|
|
|
return status;
|
|
}
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
_Acquires_lock_(_Lock_kind_critical_section_)
|
|
_Releases_lock_(_Lock_kind_critical_section_)
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDriverObjects(_In_ PSYSTEM_MODULES SystemModules,
|
|
_Inout_ PINVALID_DRIVERS_HEAD InvalidDriverListHead)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!SystemModules || !InvalidDriverListHead)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
HANDLE handle = NULL;
|
|
OBJECT_ATTRIBUTES attributes = {0};
|
|
PVOID directory = {0};
|
|
UNICODE_STRING directory_name = {0};
|
|
PVOID whitelisted_regions_buffer = NULL;
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
POBJECT_DIRECTORY directory_object = NULL;
|
|
|
|
RtlInitUnicodeString(&directory_name, L"\\Driver");
|
|
|
|
InitializeObjectAttributes(&attributes, &directory_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
status = ZwOpenDirectoryObject(&handle, DIRECTORY_ALL_ACCESS, &attributes);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("ZwOpenDirectoryObject failed with status %x", status);
|
|
return status;
|
|
}
|
|
|
|
status = ObReferenceObjectByHandle(
|
|
handle, DIRECTORY_ALL_ACCESS, NULL, KernelMode, &directory, NULL);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("ObReferenceObjectByHandle failed with status %x", status);
|
|
ZwClose(handle);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Windows organises its drivers in object directories (not the same as
|
|
* files directories). For the driver directory, there are 37 entries,
|
|
* each driver is hashed and indexed. If there is a driver with a duplicate
|
|
* index, it is inserted into same index in a linked list using the
|
|
* _OBJECT_DIRECTORY_ENTRY struct. So to enumerate all drivers we visit
|
|
* each entry in the hashmap, enumerate all objects in the linked list
|
|
* at entry j then we increment the hashmap index i. The motivation behind
|
|
* this is that when a driver is accessed, it is brought to the first index
|
|
* in the linked list, so drivers that are accessed the most can be
|
|
* accessed quickly
|
|
*/
|
|
|
|
directory_object = (POBJECT_DIRECTORY)directory;
|
|
|
|
ExAcquirePushLockExclusiveEx(&directory_object->Lock, NULL);
|
|
|
|
whitelisted_regions_buffer =
|
|
ExAllocatePool2(POOL_FLAG_NON_PAGED,
|
|
WHITELISTED_MODULE_COUNT * MODULE_MAX_STRING_SIZE,
|
|
WHITELISTED_MODULE_TAG);
|
|
|
|
if (!whitelisted_regions_buffer)
|
|
goto end;
|
|
|
|
status = PopulateWhitelistedModuleBuffer(whitelisted_regions_buffer, SystemModules);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("PopulateWhitelistedModuleBuffer failed with status %x", status);
|
|
goto end;
|
|
}
|
|
|
|
for (INT index = 0; index < NUMBER_HASH_BUCKETS; index++)
|
|
{
|
|
POBJECT_DIRECTORY_ENTRY entry = directory_object->HashBuckets[index];
|
|
|
|
if (!entry)
|
|
continue;
|
|
|
|
POBJECT_DIRECTORY_ENTRY sub_entry = entry;
|
|
|
|
while (sub_entry)
|
|
{
|
|
BOOLEAN flag = FALSE;
|
|
PDRIVER_OBJECT current_driver = sub_entry->Object;
|
|
|
|
/* validate driver has backing module */
|
|
|
|
status = ValidateDriverObjectHasBackingModule(
|
|
SystemModules, current_driver, &flag);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR(
|
|
"ValidateDriverObjectHasBackingModule failed with status %x",
|
|
status);
|
|
goto end;
|
|
}
|
|
|
|
if (!flag)
|
|
{
|
|
status = AddDriverToList(InvalidDriverListHead,
|
|
current_driver,
|
|
REASON_NO_BACKING_MODULE);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
DEBUG_ERROR("AddDriverToList failed with status %x",
|
|
status);
|
|
else
|
|
InvalidDriverListHead->count += 1;
|
|
}
|
|
|
|
/* validate drivers IOCTL dispatch routines */
|
|
|
|
status = ValidateDriverIOCTLDispatchRegion(
|
|
current_driver, SystemModules, whitelisted_regions_buffer, &flag);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR(
|
|
"ValidateDriverIOCTLDispatchRegion failed with status %x",
|
|
status);
|
|
goto end;
|
|
}
|
|
|
|
if (!flag)
|
|
{
|
|
status = AddDriverToList(InvalidDriverListHead,
|
|
current_driver,
|
|
REASON_INVALID_IOCTL_DISPATCH);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
DEBUG_ERROR("AddDriverToList failed with status %x",
|
|
status);
|
|
else
|
|
InvalidDriverListHead->count += 1;
|
|
}
|
|
|
|
sub_entry = sub_entry->ChainLink;
|
|
}
|
|
}
|
|
|
|
end:
|
|
if (whitelisted_regions_buffer)
|
|
ExFreePoolWithTag(whitelisted_regions_buffer, WHITELISTED_MODULE_TAG);
|
|
|
|
ExReleasePushLockExclusiveEx(&directory_object->Lock, 0);
|
|
ObDereferenceObject(directory);
|
|
ZwClose(handle);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
HandleValidateDriversIOCTL(_Inout_ PIRP Irp)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PVOID buffer = NULL;
|
|
ULONG buffer_size = 0;
|
|
SYSTEM_MODULES system_modules = {0};
|
|
MODULE_VALIDATION_FAILURE_HEADER header = {0};
|
|
PINVALID_DRIVERS_HEAD head = NULL;
|
|
|
|
/* Fix annoying visual studio linting error */
|
|
RtlZeroMemory(&system_modules, sizeof(SYSTEM_MODULES));
|
|
|
|
status = GetSystemModuleInformation(&system_modules);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("GetSystemModuleInformation failed with status %x", status);
|
|
return status;
|
|
}
|
|
|
|
head = ExAllocatePool2(
|
|
POOL_FLAG_NON_PAGED, sizeof(INVALID_DRIVERS_HEAD), INVALID_DRIVER_LIST_HEAD_POOL);
|
|
|
|
if (!head)
|
|
{
|
|
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
}
|
|
|
|
/*
|
|
* Use a linked list here so that so we have easy access to the invalid drivers
|
|
* which we can then use to copy the drivers logic for further analysis in
|
|
* identifying drivers specifically used for the purpose of cheating
|
|
*/
|
|
|
|
InitDriverList(head);
|
|
|
|
status = ValidateDriverObjects(&system_modules, head);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("ValidateDriverObjects failed with status %x", status);
|
|
goto end;
|
|
}
|
|
|
|
header.module_count = head->count >= MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT
|
|
? MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT
|
|
: head->count;
|
|
|
|
if (head->count > 0)
|
|
{
|
|
DEBUG_VERBOSE("System has an invalid driver count of: %i", head->count);
|
|
|
|
buffer_size =
|
|
sizeof(MODULE_VALIDATION_FAILURE_HEADER) +
|
|
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT * sizeof(MODULE_VALIDATION_FAILURE);
|
|
|
|
status = ValidateIrpOutputBuffer(Irp, buffer_size);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("Failed to validate output buffer.");
|
|
goto end;
|
|
}
|
|
|
|
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, buffer_size, MODULES_REPORT_POOL_TAG);
|
|
|
|
if (!buffer)
|
|
{
|
|
ExFreePoolWithTag(head, INVALID_DRIVER_LIST_HEAD_POOL);
|
|
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
}
|
|
|
|
Irp->IoStatus.Information =
|
|
sizeof(MODULE_VALIDATION_FAILURE_HEADER) +
|
|
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT * sizeof(MODULE_VALIDATION_FAILURE);
|
|
|
|
RtlCopyMemory(buffer, &header, sizeof(MODULE_VALIDATION_FAILURE_HEADER));
|
|
|
|
for (INT index = 0; index < head->count; index++)
|
|
{
|
|
/* make sure we free any non reported modules */
|
|
if (index >= MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT)
|
|
{
|
|
RemoveInvalidDriverFromList(head);
|
|
continue;
|
|
}
|
|
|
|
MODULE_VALIDATION_FAILURE report = {0};
|
|
report.report_code = REPORT_MODULE_VALIDATION_FAILURE;
|
|
report.report_type = head->first_entry->reason;
|
|
report.driver_base_address = head->first_entry->driver->DriverStart;
|
|
report.driver_size = head->first_entry->driver->DriverSize;
|
|
|
|
ANSI_STRING string = {0};
|
|
string.Length = 0;
|
|
string.MaximumLength = MODULE_REPORT_DRIVER_NAME_BUFFER_SIZE;
|
|
string.Buffer = &report.driver_name;
|
|
|
|
status = RtlUnicodeStringToAnsiString(
|
|
&string, &head->first_entry->driver->DriverName, FALSE);
|
|
|
|
/* still continue if we fail to get the driver name */
|
|
if (!NT_SUCCESS(status))
|
|
DEBUG_ERROR("RtlUnicodeStringToAnsiString failed with status %x",
|
|
status);
|
|
|
|
RtlCopyMemory((UINT64)buffer + sizeof(MODULE_VALIDATION_FAILURE_HEADER) +
|
|
index * sizeof(MODULE_VALIDATION_FAILURE),
|
|
&report,
|
|
sizeof(MODULE_VALIDATION_FAILURE));
|
|
|
|
RemoveInvalidDriverFromList(head);
|
|
}
|
|
|
|
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer,
|
|
buffer,
|
|
sizeof(MODULE_VALIDATION_FAILURE_HEADER) +
|
|
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT *
|
|
sizeof(MODULE_VALIDATION_FAILURE));
|
|
|
|
ExFreePoolWithTag(buffer, MODULES_REPORT_POOL_TAG);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_INFO("Found no invalid drivers on the system.");
|
|
}
|
|
|
|
end:
|
|
ExFreePoolWithTag(head, INVALID_DRIVER_LIST_HEAD_POOL);
|
|
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* TODO: this probably doesnt need to return an NTSTATUS, we can just return a boolean and remove
|
|
* the out variable.
|
|
*/
|
|
NTSTATUS
|
|
IsInstructionPointerInInvalidRegion(_In_ UINT64 RIP,
|
|
_In_ PSYSTEM_MODULES SystemModules,
|
|
_Out_ PBOOLEAN Result)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (!RIP || !SystemModules || !Result)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
/* Note that this does not check for HAL or PatchGuard Execution */
|
|
for (INT i = 0; i < SystemModules->module_count; i++)
|
|
{
|
|
PRTL_MODULE_EXTENDED_INFO system_module =
|
|
(PRTL_MODULE_EXTENDED_INFO)((uintptr_t)SystemModules->address +
|
|
i * sizeof(RTL_MODULE_EXTENDED_INFO));
|
|
|
|
UINT64 base = (UINT64)system_module->ImageBase;
|
|
UINT64 end = base + system_module->ImageSize;
|
|
|
|
if (RIP >= base && RIP <= end)
|
|
{
|
|
*Result = TRUE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
*Result = FALSE;
|
|
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.
|
|
*/
|
|
STATIC
|
|
NTSTATUS
|
|
AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules, _Inout_ PIRP Irp)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
BOOLEAN flag = FALSE;
|
|
|
|
if (!NmiContext || !SystemModules)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
for (INT core = 0; core < KeQueryActiveProcessorCount(0); core++)
|
|
{
|
|
/* Make sure our NMIs were run */
|
|
if (!NmiContext[core].callback_count)
|
|
{
|
|
NTSTATUS status =
|
|
ValidateIrpOutputBuffer(Irp, sizeof(NMI_CALLBACK_FAILURE));
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x",
|
|
status);
|
|
continue;
|
|
}
|
|
|
|
NMI_CALLBACK_FAILURE report = {0};
|
|
report.report_code = REPORT_NMI_CALLBACK_FAILURE;
|
|
report.kthread_address = NULL;
|
|
report.invalid_rip = NULL;
|
|
report.were_nmis_disabled = TRUE;
|
|
|
|
Irp->IoStatus.Information = sizeof(NMI_CALLBACK_FAILURE);
|
|
|
|
RtlCopyMemory(
|
|
Irp->AssociatedIrp.SystemBuffer, &report, sizeof(NMI_CALLBACK_FAILURE));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
DEBUG_VERBOSE("Analysing Nmi Data for: cpu number: %i callback count: %lx",
|
|
core,
|
|
NmiContext[core].callback_count);
|
|
|
|
/*
|
|
* Our NMI callback allows us to interrupt every running thread on each core. Now it
|
|
* is common practice for malicious programs to either unlink their thread from the
|
|
* KTHREAD linked list or remove their threads entry from the PspCidTable or both.
|
|
* Now the reason an unlinked thread can still be scheduled is because the scheduler
|
|
* keeps a seperate list that it uses to schedule threads. It then places these
|
|
* threads in the KPRCB in either the CurrentThread, IdleThread or NextThread.
|
|
*
|
|
* Since you can't just set a threads affinity to enumerate over all cores and read
|
|
* the KPCRB->CurrentThread (since it will just show your thread) we have to
|
|
* interrupt the thread. So below we are validating that the thread is indeed in our
|
|
* own threads list using our callback routine and then using PsGetThreadId
|
|
*
|
|
* I also want to integrate a way to SAFELY determine whether a thread has been
|
|
* removed from the KTHREADs linked list, maybe PsGetNextProcess ?
|
|
*/
|
|
|
|
if (!ValidateThreadsPspCidTableEntry(NmiContext[core].kthread))
|
|
{
|
|
DEBUG_WARNING("Thread: %llx was not found in the pspcid table.",
|
|
NmiContext[core].kthread);
|
|
|
|
PHIDDEN_SYSTEM_THREAD_REPORT report =
|
|
ExAllocatePool2(POOL_FLAG_NON_PAGED,
|
|
sizeof(HIDDEN_SYSTEM_THREAD_REPORT),
|
|
REPORT_POOL_TAG);
|
|
|
|
if (!report)
|
|
continue;
|
|
|
|
report->report_code = REPORT_HIDDEN_SYSTEM_THREAD;
|
|
report->found_in_kthreadlist = FALSE; // wip
|
|
report->found_in_pspcidtable = FALSE;
|
|
report->thread_id = PsGetThreadId(NmiContext[core].kthread);
|
|
report->thread_address = NmiContext[core].kthread;
|
|
|
|
RtlCopyMemory(
|
|
report->thread, NmiContext[core].kthread, sizeof(report->thread));
|
|
|
|
InsertReportToQueue(report);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_VERBOSE("Thread: %llx was found in PspCidTable",
|
|
NmiContext[core].kthread);
|
|
}
|
|
|
|
if (NmiContext[core].user_thread)
|
|
continue;
|
|
|
|
status = IsInstructionPointerInInvalidRegion(
|
|
NmiContext[core].interrupted_rip, SystemModules, &flag);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("IsInstructionPointerInInvalidRegion failed with status %x",
|
|
status);
|
|
continue;
|
|
}
|
|
|
|
if (!flag)
|
|
{
|
|
status = ValidateIrpOutputBuffer(Irp, sizeof(NMI_CALLBACK_FAILURE));
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x",
|
|
status);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Note: for now, we only handle 1 report at a time so we stop the
|
|
* analysis once we receive a report since we only send a buffer
|
|
* large enough for 1 report. In the future this should be changed
|
|
* to a buffer that can hold atleast 4 reports (since the chance we
|
|
* get 4 reports with a single NMI would be impossible) so we can
|
|
* continue parsing the rest of the stack frames after receiving a
|
|
* single report.
|
|
*/
|
|
|
|
NMI_CALLBACK_FAILURE report = {0};
|
|
report.report_code = REPORT_NMI_CALLBACK_FAILURE;
|
|
report.kthread_address = NmiContext[core].kthread;
|
|
report.invalid_rip = NmiContext[core].interrupted_rip;
|
|
report.were_nmis_disabled = FALSE;
|
|
|
|
Irp->IoStatus.Information = sizeof(NMI_CALLBACK_FAILURE);
|
|
|
|
RtlCopyMemory(
|
|
Irp->AssociatedIrp.SystemBuffer, &report, sizeof(NMI_CALLBACK_FAILURE));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
_IRQL_requires_max_(HIGH_LEVEL)
|
|
STATIC
|
|
BOOLEAN
|
|
NmiCallback(_Inout_opt_ PVOID Context, _In_ BOOLEAN Handled)
|
|
{
|
|
UNREFERENCED_PARAMETER(Handled);
|
|
|
|
PNMI_CONTEXT nmi_context = (PNMI_CONTEXT)Context;
|
|
ULONG proc_num = KeGetCurrentProcessorNumber();
|
|
UINT64 kpcr = 0;
|
|
TASK_STATE_SEGMENT_64* tss = NULL;
|
|
PMACHINE_FRAME machine_frame = NULL;
|
|
|
|
/*
|
|
* To find the IRETQ frame (MACHINE_FRAME) we need to find the top of the NMI ISR stack.
|
|
* This is stored at TSS->Ist[3]. To find the TSS, we can read it from KPCR->TSS_BASE. Once
|
|
* we have our TSS, we can read the value at TSS->Ist[3] which points to the top of the ISR
|
|
* stack, and subtract the size of the MACHINE_FRAME struct. Allowing us read the
|
|
* interrupted RIP.
|
|
*
|
|
* The reason this is needed is because RtlCaptureStackBackTrace is not safe to run
|
|
* at IRQL = HIGH_LEVEL, hence we need to manually unwind the ISR stack to find the
|
|
* interrupted rip.
|
|
*/
|
|
kpcr = __readmsr(IA32_GS_BASE);
|
|
tss = *(TASK_STATE_SEGMENT_64**)(kpcr + KPCR_TSS_BASE_OFFSET);
|
|
machine_frame = tss->Ist3 - sizeof(MACHINE_FRAME);
|
|
|
|
if (machine_frame->rip <= WINDOWS_USERMODE_MAX_ADDRESS)
|
|
nmi_context[proc_num].user_thread = TRUE;
|
|
|
|
nmi_context[proc_num].interrupted_rip = machine_frame->rip;
|
|
nmi_context[proc_num].interrupted_rsp = machine_frame->rsp;
|
|
nmi_context[proc_num].kthread = PsGetCurrentThread();
|
|
nmi_context[proc_num].callback_count += 1;
|
|
|
|
DEBUG_VERBOSE(
|
|
"[NMI CALLBACK]: Core Number: %lx, Interrupted RIP: %llx, Interrupted RSP: %llx",
|
|
proc_num,
|
|
machine_frame->rip,
|
|
machine_frame->rsp);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define NMI_DELAY_TIME 200 * 10000
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
LaunchNonMaskableInterrupt()
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PKAFFINITY_EX ProcAffinityPool =
|
|
ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(KAFFINITY_EX), PROC_AFFINITY_POOL);
|
|
|
|
if (!ProcAffinityPool)
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
LARGE_INTEGER delay = {0};
|
|
delay.QuadPart -= NMI_DELAY_TIME;
|
|
|
|
for (ULONG core = 0; core < KeQueryActiveProcessorCount(0); core++)
|
|
{
|
|
KeInitializeAffinityEx(ProcAffinityPool);
|
|
KeAddProcessorAffinityEx(ProcAffinityPool, core);
|
|
|
|
DEBUG_VERBOSE("Sending NMI");
|
|
HalSendNMI(ProcAffinityPool);
|
|
|
|
/*
|
|
* Only a single NMI can be active at any given time, so arbitrarily
|
|
* delay execution to allow time for the NMI to be processed
|
|
*/
|
|
KeDelayExecutionThread(KernelMode, FALSE, &delay);
|
|
}
|
|
|
|
ExFreePoolWithTag(ProcAffinityPool, PROC_AFFINITY_POOL);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
HandleNmiIOCTL(_Inout_ PIRP Irp)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PVOID callback_handle = NULL;
|
|
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);
|
|
|
|
if (!nmi_context)
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
/*
|
|
* We want to register and unregister our callback each time so it becomes harder
|
|
* for people to hook our callback and get up to some funny business
|
|
*/
|
|
callback_handle = KeRegisterNmiCallback(NmiCallback, nmi_context);
|
|
|
|
if (!callback_handle)
|
|
{
|
|
DEBUG_ERROR("KeRegisterNmiCallback failed with no status.");
|
|
ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/*
|
|
* We query the system modules each time since they can potentially
|
|
* change at any time
|
|
*/
|
|
status = GetSystemModuleInformation(&system_modules);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
KeDeregisterNmiCallback(callback_handle);
|
|
ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
|
|
DEBUG_ERROR("Error retriving system module information");
|
|
return status;
|
|
}
|
|
|
|
status = LaunchNonMaskableInterrupt();
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("Error running NMI callbacks");
|
|
KeDeregisterNmiCallback(callback_handle);
|
|
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
|
|
ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
|
|
return status;
|
|
}
|
|
|
|
status = AnalyseNmiData(nmi_context, &system_modules, Irp);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
DEBUG_ERROR("Error analysing nmi data");
|
|
|
|
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
|
|
ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
|
|
KeDeregisterNmiCallback(callback_handle);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* The RundownRoutine is executed if the thread terminates before the APC was delivered to
|
|
* user mode.
|
|
*/
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ApcRundownRoutine(_In_ PRKAPC Apc)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
FreeApcAndDecrementApcCount(Apc, APC_CONTEXT_ID_STACKWALK);
|
|
}
|
|
|
|
/*
|
|
* The KernelRoutine is executed in kernel mode at APC_LEVEL before the APC is delivered.
|
|
* This is also where we want to free our APC object.
|
|
*/
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ApcKernelRoutine(_In_ PRKAPC Apc,
|
|
_Inout_ _Deref_pre_maybenull_ PKNORMAL_ROUTINE* NormalRoutine,
|
|
_Inout_ _Deref_pre_maybenull_ PVOID* NormalContext,
|
|
_Inout_ _Deref_pre_maybenull_ PVOID* SystemArgument1,
|
|
_Inout_ _Deref_pre_maybenull_ PVOID* SystemArgument2)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PVOID buffer = NULL;
|
|
INT frames_captured = 0;
|
|
UINT64 stack_frame = 0;
|
|
BOOLEAN flag = FALSE;
|
|
PAPC_STACKWALK_CONTEXT context = NULL;
|
|
PTHREAD_LIST_ENTRY thread_list_entry = NULL;
|
|
|
|
context = (PAPC_STACKWALK_CONTEXT)Apc->NormalContext;
|
|
|
|
FindThreadListEntryByThreadAddress(KeGetCurrentThread(), &thread_list_entry);
|
|
|
|
if (!thread_list_entry)
|
|
return;
|
|
|
|
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, STACK_FRAME_POOL_SIZE, POOL_TAG_APC);
|
|
|
|
if (!buffer)
|
|
goto free;
|
|
|
|
frames_captured =
|
|
RtlCaptureStackBackTrace(NULL, STACK_FRAME_POOL_SIZE / sizeof(UINT64), buffer, NULL);
|
|
|
|
if (!frames_captured)
|
|
goto free;
|
|
|
|
for (INT index = 0; index < frames_captured; index++)
|
|
{
|
|
stack_frame = *(UINT64*)((UINT64)buffer + index * sizeof(UINT64));
|
|
|
|
/*
|
|
* Apc->NormalContext holds the address of our context data structure that
|
|
* we passed into KeInitializeApc as the last argument.
|
|
*/
|
|
status = IsInstructionPointerInInvalidRegion(stack_frame, context->modules, &flag);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("IsInstructionPointerInInvalidRegion failed with status %x",
|
|
status);
|
|
goto free;
|
|
}
|
|
|
|
if (flag == FALSE)
|
|
{
|
|
PAPC_STACKWALK_REPORT report = ExAllocatePool2(
|
|
POOL_FLAG_NON_PAGED, sizeof(APC_STACKWALK_REPORT), POOL_TAG_APC);
|
|
|
|
if (!report)
|
|
goto free;
|
|
|
|
report->report_code = REPORT_APC_STACKWALK;
|
|
report->kthread_address = (UINT64)KeGetCurrentThread();
|
|
report->invalid_rip = stack_frame;
|
|
|
|
RtlCopyMemory(&report->driver,
|
|
(UINT64)stack_frame - 0x500,
|
|
APC_STACKWALK_BUFFER_SIZE);
|
|
|
|
InsertReportToQueue(report);
|
|
}
|
|
}
|
|
|
|
free:
|
|
|
|
if (buffer)
|
|
ExFreePoolWithTag(buffer, POOL_TAG_APC);
|
|
|
|
FreeApcAndDecrementApcCount(Apc, APC_CONTEXT_ID_STACKWALK);
|
|
|
|
thread_list_entry->apc = NULL;
|
|
thread_list_entry->apc_queued = FALSE;
|
|
}
|
|
|
|
/*
|
|
* The NormalRoutine is executed in user mode when the APC is delivered.
|
|
*/
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ApcNormalRoutine(_In_opt_ PVOID NormalContext,
|
|
_In_opt_ PVOID SystemArgument1,
|
|
_In_opt_ PVOID SystemArgument2)
|
|
{
|
|
PAGED_CODE();
|
|
}
|
|
|
|
VOID
|
|
FlipKThreadMiscFlagsFlag(_In_ PKTHREAD Thread, _In_ ULONG FlagIndex, _In_ BOOLEAN NewValue)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PLONG misc_flags = (PLONG)((UINT64)Thread + KTHREAD_MISC_FLAGS_OFFSET);
|
|
ULONG mask = 1ul << FlagIndex;
|
|
|
|
if (NewValue)
|
|
*misc_flags |= mask;
|
|
else
|
|
*misc_flags &= ~mask;
|
|
}
|
|
|
|
#define THREAD_STATE_TERMINATED 4
|
|
#define THREAD_STATE_WAIT 5
|
|
#define THREAD_STATE_INIT 0
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
STATIC
|
|
VOID
|
|
ValidateThreadViaKernelApcCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
|
|
_Inout_opt_ PVOID Context)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PKAPC apc = NULL;
|
|
BOOLEAN apc_status = FALSE;
|
|
PLONG misc_flags = NULL;
|
|
PCHAR previous_mode = NULL;
|
|
PUCHAR state = NULL;
|
|
BOOLEAN apc_queueable = FALSE;
|
|
PAPC_STACKWALK_CONTEXT context = (PAPC_STACKWALK_CONTEXT)Context;
|
|
LPCSTR process_name = PsGetProcessImageFileName(ThreadListEntry->owning_process);
|
|
|
|
/*
|
|
* we dont want to schedule an apc to threads owned by the kernel
|
|
*
|
|
* Actually we do... todo: fix this.
|
|
*/
|
|
if (ThreadListEntry->owning_process == PsInitialSystemProcess || !Context)
|
|
return;
|
|
|
|
/* We are not interested in these processess.. for now lol */
|
|
if (!strcmp(process_name, "svchost.exe") || !strcmp(process_name, "Registry") ||
|
|
!strcmp(process_name, "smss.exe") || !strcmp(process_name, "csrss.exe") ||
|
|
!strcmp(process_name, "explorer.exe") || !strcmp(process_name, "svchost.exe") ||
|
|
!strcmp(process_name, "lsass.exe") || !strcmp(process_name, "MemCompression") ||
|
|
!strcmp(process_name, "WerFault.exe"))
|
|
return;
|
|
|
|
DEBUG_VERBOSE("Validating thread: %llx, process name: %s via kernel APC stackwalk.",
|
|
ThreadListEntry->thread,
|
|
process_name);
|
|
|
|
if (ThreadListEntry->thread == KeGetCurrentThread() || !ThreadListEntry->thread)
|
|
return;
|
|
/*
|
|
* Its possible to set the KThread->ApcQueueable flag to false ensuring that no APCs
|
|
* can be queued to the thread, as KeInsertQueueApc will check this flag before
|
|
* queueing an APC so lets make sure we flip this before before queueing ours. Since
|
|
* we filter out any system threads this should be fine... c:
|
|
*/
|
|
misc_flags = (PLONG)((UINT64)ThreadListEntry->thread + KTHREAD_MISC_FLAGS_OFFSET);
|
|
previous_mode = (PCHAR)((UINT64)ThreadListEntry->thread + KTHREAD_PREVIOUS_MODE_OFFSET);
|
|
state = (PUCHAR)((UINT64)ThreadListEntry->thread + KTHREAD_STATE_OFFSET);
|
|
|
|
/* we dont care about user mode threads */
|
|
// if (*previous_mode == UserMode)
|
|
// return;
|
|
|
|
/* todo: We should also flag all threads that have the flag set to false */
|
|
if (*misc_flags >> KTHREAD_MISC_FLAGS_APC_QUEUEABLE == FALSE)
|
|
FlipKThreadMiscFlagsFlag(
|
|
ThreadListEntry->thread, KTHREAD_MISC_FLAGS_APC_QUEUEABLE, TRUE);
|
|
|
|
/*
|
|
* force thread into an alertable state, noting that this does not guarantee that
|
|
* our APC will be run.
|
|
*/
|
|
if (*misc_flags >> KTHREAD_MISC_FLAGS_ALERTABLE == FALSE)
|
|
FlipKThreadMiscFlagsFlag(
|
|
ThreadListEntry->thread, KTHREAD_MISC_FLAGS_ALERTABLE, TRUE);
|
|
|
|
apc = (PKAPC)ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(KAPC), POOL_TAG_APC);
|
|
|
|
if (!apc)
|
|
return;
|
|
|
|
KeInitializeApc(apc,
|
|
ThreadListEntry->thread,
|
|
OriginalApcEnvironment,
|
|
ApcKernelRoutine,
|
|
ApcRundownRoutine,
|
|
ApcNormalRoutine,
|
|
KernelMode,
|
|
Context);
|
|
|
|
apc_status = KeInsertQueueApc(apc, NULL, NULL, IO_NO_INCREMENT);
|
|
|
|
if (!apc_status)
|
|
{
|
|
DEBUG_ERROR("KeInsertQueueApc failed with no status.");
|
|
ExFreePoolWithTag(apc, POOL_TAG_APC);
|
|
return;
|
|
}
|
|
|
|
ThreadListEntry->apc = apc;
|
|
ThreadListEntry->apc_queued = TRUE;
|
|
|
|
IncrementApcCount(APC_CONTEXT_ID_STACKWALK);
|
|
}
|
|
|
|
/*
|
|
* Since NMIs are only executed on the thread that is running on each logical core, it makes
|
|
* sense to make use of APCs that, while can be masked off, provide us to easily issue a
|
|
* callback routine to threads we want a stack trace of. Hence by utilising both APCs and
|
|
* NMIs we get excellent coverage of the entire system.
|
|
*/
|
|
NTSTATUS
|
|
ValidateThreadsViaKernelApc()
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PAPC_STACKWALK_CONTEXT context = NULL;
|
|
|
|
/* First, ensure we dont already have an ongoing operation */
|
|
GetApcContext(&context, APC_CONTEXT_ID_STACKWALK);
|
|
|
|
if (context)
|
|
{
|
|
DEBUG_WARNING("Existing APC_STACKWALK operation already in progress.");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
context = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(APC_STACKWALK_CONTEXT), POOL_TAG_APC);
|
|
|
|
if (!context)
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
context->header.context_id = APC_CONTEXT_ID_STACKWALK;
|
|
context->modules =
|
|
ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(SYSTEM_MODULES), POOL_TAG_APC);
|
|
|
|
if (!context->modules)
|
|
{
|
|
ExFreePoolWithTag(context, POOL_TAG_APC);
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
}
|
|
|
|
status = GetSystemModuleInformation(context->modules);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("GetSystemModuleInformation failed with status %x", status);
|
|
ExFreePoolWithTag(context->modules, POOL_TAG_APC);
|
|
ExFreePoolWithTag(context, POOL_TAG_APC);
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
}
|
|
|
|
status = InsertApcContext(context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("InsertApcContext failed with status %x", status);
|
|
ExFreePoolWithTag(context->modules, POOL_TAG_APC);
|
|
ExFreePoolWithTag(context, POOL_TAG_APC);
|
|
return status;
|
|
}
|
|
|
|
context->header.allocation_in_progress = TRUE;
|
|
EnumerateThreadListWithCallbackRoutine(ValidateThreadViaKernelApcCallback, context);
|
|
context->header.allocation_in_progress = FALSE;
|
|
|
|
return status;
|
|
}
|
|
|
|
VOID
|
|
FreeApcStackwalkApcContextInformation(_Inout_ PAPC_STACKWALK_CONTEXT Context)
|
|
{
|
|
if (Context->modules->address)
|
|
ExFreePoolWithTag(Context->modules->address, SYSTEM_MODULES_POOL);
|
|
|
|
if (Context->modules)
|
|
ExFreePoolWithTag(Context->modules, POOL_TAG_APC);
|
|
}
|
|
|
|
#define DPC_STACKWALK_STACKFRAME_COUNT 10
|
|
#define DPC_STACKWALK_FRAMES_TO_SKIP 3
|
|
|
|
typedef struct _DPC_CONTEXT
|
|
{
|
|
UINT64 stack_frame[DPC_STACKWALK_STACKFRAME_COUNT];
|
|
UINT16 frames_captured;
|
|
volatile BOOLEAN executed;
|
|
|
|
} DPC_CONTEXT, *PDPC_CONTEXT;
|
|
|
|
_Function_class_(KDEFERRED_ROUTINE) _IRQL_requires_max_(DISPATCH_LEVEL)
|
|
_IRQL_requires_min_(DISPATCH_LEVEL)
|
|
_IRQL_requires_(DISPATCH_LEVEL)
|
|
_IRQL_requires_same_
|
|
VOID
|
|
DpcStackwalkCallbackRoutine(_In_ PKDPC Dpc,
|
|
_In_opt_ PVOID DeferredContext,
|
|
_In_opt_ PVOID SystemArgument1,
|
|
_In_opt_ PVOID SystemArgument2)
|
|
{
|
|
PDPC_CONTEXT context = &((PDPC_CONTEXT)DeferredContext)[KeGetCurrentProcessorNumber()];
|
|
|
|
context->frames_captured = RtlCaptureStackBackTrace(DPC_STACKWALK_FRAMES_TO_SKIP,
|
|
DPC_STACKWALK_STACKFRAME_COUNT,
|
|
&context->stack_frame,
|
|
NULL);
|
|
InterlockedExchange(&context->executed, TRUE);
|
|
KeSignalCallDpcDone(SystemArgument1);
|
|
|
|
DEBUG_VERBOSE("Executed DPC on core: %lx, with %lx frames captured.",
|
|
KeGetCurrentProcessorNumber(),
|
|
context->frames_captured);
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
CheckForDpcCompletion(_In_ PDPC_CONTEXT Context)
|
|
{
|
|
for (UINT32 index = 0; index < KeQueryActiveProcessorCount(0); index++)
|
|
{
|
|
if (!InterlockedExchange(&Context[index].executed, Context[index].executed))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
STATIC
|
|
NTSTATUS
|
|
ValidateDpcCapturedStack(_In_ PSYSTEM_MODULES Modules, _In_ PDPC_CONTEXT Context)
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
BOOLEAN flag = FALSE;
|
|
PDPC_STACKWALK_REPORT report = NULL;
|
|
|
|
for (UINT32 core = 0; core < KeQueryActiveProcessorCount(0); core++)
|
|
{
|
|
for (UINT32 frame = 0; frame < Context[core].frames_captured; frame++)
|
|
{
|
|
status = IsInstructionPointerInInvalidRegion(
|
|
Context[core].stack_frame[frame], Modules, &flag);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR(
|
|
"IsInstructionPointerInInvalidRegion failed with status %x",
|
|
status);
|
|
continue;
|
|
}
|
|
|
|
if (!flag)
|
|
{
|
|
report = ExAllocatePool2(POOL_FLAG_NON_PAGED,
|
|
sizeof(DPC_STACKWALK_REPORT),
|
|
POOL_TAG_DPC);
|
|
|
|
if (!report)
|
|
continue;
|
|
|
|
report->report_code = REPORT_DPC_STACKWALK;
|
|
report->kthread_address = PsGetCurrentThread();
|
|
report->invalid_rip = Context[core].stack_frame[frame];
|
|
|
|
RtlCopyMemory(report->driver,
|
|
(UINT64)Context[core].stack_frame[frame] - 0x500,
|
|
APC_STACKWALK_BUFFER_SIZE);
|
|
|
|
InsertReportToQueue(report);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Lets use DPCs as another form of stackwalking rather then inter-process interrupts
|
|
* because DPCs run at IRQL = DISPATCH_LEVEL, allowing us to use functions such as
|
|
* RtlCaptureStackBackTrace whereas IPIs run at IRQL = IPI_LEVEL. DPCs are also harder
|
|
* to mask compared to APCs which can be masked with the flip of a bit in the KTHREAD
|
|
* structure.
|
|
*/
|
|
NTSTATUS
|
|
DispatchStackwalkToEachCpuViaDpc()
|
|
{
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
PDPC_CONTEXT context = NULL;
|
|
SYSTEM_MODULES modules = {0};
|
|
|
|
context = ExAllocatePool2(POOL_FLAG_NON_PAGED,
|
|
KeQueryActiveProcessorCount(0) * sizeof(DPC_CONTEXT),
|
|
POOL_TAG_DPC);
|
|
|
|
if (!context)
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
status = GetSystemModuleInformation(&modules);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("GetSystemModuleInformation failed with status %x", status);
|
|
goto end;
|
|
}
|
|
|
|
/* KeGenericCallDpc will queue a DPC to each processor with importance =
|
|
* HighImportance. This means our DPC will be inserted into the front of the DPC
|
|
* queue and executed immediately.*/
|
|
KeGenericCallDpc(DpcStackwalkCallbackRoutine, context);
|
|
|
|
while (!CheckForDpcCompletion(context))
|
|
YieldProcessor();
|
|
|
|
status = ValidateDpcCapturedStack(&modules, context);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DEBUG_ERROR("ValidateDpcCapturedStack failed with status %x", status);
|
|
goto end;
|
|
}
|
|
|
|
DEBUG_VERBOSE("Finished validating cores via dpc");
|
|
end:
|
|
|
|
if (modules.address)
|
|
ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
|
|
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;
|
|
} |