mirror-ac/driver/modules.c

1113 lines
28 KiB
C
Raw Normal View History

2023-08-19 04:52:57 +02:00
#include "modules.h"
2023-09-24 13:13:20 +02:00
#include "callbacks.h"
2023-09-25 17:41:38 +02:00
#include "driver.h"
2023-09-24 13:13:20 +02:00
2023-08-21 11:13:00 +02:00
#define WHITELISTED_MODULE_TAG 'whte'
2023-08-30 18:29:44 +02:00
#define NMI_DELAY 200 * 10000
2023-08-28 17:00:52 +02:00
2023-08-21 11:13:00 +02:00
#define WHITELISTED_MODULE_COUNT 3
#define MODULE_MAX_STRING_SIZE 256
2023-08-27 16:34:21 +02:00
#define NTOSKRNL 0
#define CLASSPNP 1
#define WDF01000 2
2023-08-21 11:13:00 +02:00
CHAR WHITELISTED_MODULES[ WHITELISTED_MODULE_COUNT ][ MODULE_MAX_STRING_SIZE ] =
{
"ntoskrnl.exe",
"CLASSPNP.SYS",
"Wdf01000.sys",
};
2023-09-13 11:46:28 +02:00
typedef struct _NMI_CORE_CONTEXT
{
INT nmi_callbacks_run;
}NMI_CORE_CONTEXT, * PNMI_CORE_CONTEXT;
typedef struct _NMI_CONTEXT
{
PVOID thread_data_pool;
PVOID stack_frames;
PVOID nmi_core_context;
INT core_count;
}NMI_CONTEXT, * PNMI_CONTEXT;
/*
* TODO: this needs to be refactored to just return the entry not the whole fukin thing
*/
2023-09-26 12:00:45 +02:00
PRTL_MODULE_EXTENDED_INFO
FindSystemModuleByName(
2023-08-21 11:13:00 +02:00
_In_ LPCSTR ModuleName,
2023-08-22 19:32:25 +02:00
_In_ PSYSTEM_MODULES SystemModules
2023-08-21 11:13:00 +02:00
)
{
2023-08-22 19:32:25 +02:00
if ( !ModuleName || !SystemModules )
2023-08-21 11:13:00 +02:00
return STATUS_INVALID_PARAMETER;
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;
}
}
}
2023-09-26 12:00:45 +02:00
STATIC
NTSTATUS
PopulateWhitelistedModuleBuffer(
2023-08-21 11:13:00 +02:00
_In_ PVOID Buffer,
_In_ PSYSTEM_MODULES SystemModules
)
{
if ( !Buffer || !SystemModules)
return STATUS_INVALID_PARAMETER;
for ( INT index = 0; index < WHITELISTED_MODULE_COUNT; index++ )
{
LPCSTR name = WHITELISTED_MODULES[ index ];
2023-08-22 19:32:25 +02:00
PRTL_MODULE_EXTENDED_INFO module = FindSystemModuleByName( name, SystemModules );
2023-08-21 11:13:00 +02:00
WHITELISTED_REGIONS region;
region.base = (UINT64)module->ImageBase;
region.end = region.base + module->ImageSize;
RtlCopyMemory(
( UINT64 )Buffer + index * sizeof( WHITELISTED_REGIONS ),
&region,
sizeof( WHITELISTED_REGIONS )
);
}
2023-08-21 14:44:31 +02:00
return STATUS_SUCCESS;
2023-08-21 11:13:00 +02:00
}
2023-09-26 12:00:45 +02:00
STATIC
NTSTATUS
ValidateDriverIOCTLDispatchRegion(
2023-08-19 11:44:42 +02:00
_In_ PDRIVER_OBJECT Driver,
_In_ PSYSTEM_MODULES Modules,
2023-08-21 11:13:00 +02:00
_In_ PWHITELISTED_REGIONS WhitelistedRegions,
2023-08-19 11:44:42 +02:00
_In_ PBOOLEAN Flag
)
{
2023-08-21 11:13:00 +02:00
if ( !Modules || !Driver || !Flag || !WhitelistedRegions )
return STATUS_INVALID_PARAMETER;
2023-08-19 12:58:07 +02:00
UINT64 dispatch_function;
2023-08-21 11:13:00 +02:00
UINT64 module_base;
UINT64 module_end;
2023-08-19 11:44:42 +02:00
*Flag = TRUE;
2023-08-19 12:58:07 +02:00
dispatch_function = Driver->MajorFunction[ IRP_MJ_DEVICE_CONTROL ];
2023-08-19 11:44:42 +02:00
2023-08-19 12:58:07 +02:00
if ( dispatch_function == NULL )
2023-08-19 13:00:45 +02:00
return STATUS_SUCCESS;
2023-08-19 11:44:42 +02:00
for ( INT index = 0; index < Modules->module_count; index++ )
2023-08-19 12:58:07 +02:00
{
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;
2023-08-21 11:13:00 +02:00
/* make sure our driver has a device object which is required for IOCTL */
if ( Driver->DeviceObject == NULL )
2023-08-21 11:13:00 +02:00
return STATUS_SUCCESS;
2023-08-21 11:13:00 +02:00
module_base = ( UINT64 )system_module->ImageBase;
module_end = module_base + system_module->ImageSize;
2023-08-21 11:13:00 +02:00
/* firstly, check if its inside its own module */
if ( dispatch_function >= module_base && dispatch_function <= module_end )
return STATUS_SUCCESS;
2023-08-21 11:13:00 +02:00
/*
* 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_LOG( "name: %s, base: %p, size: %lx, dispatch: %llx, type: %lx",
system_module->FullPathName,
system_module->ImageBase,
system_module->ImageSize,
dispatch_function,
Driver->DeviceObject->DeviceType);
*Flag = FALSE;
2023-08-19 13:00:45 +02:00
return STATUS_SUCCESS;
2023-08-19 11:44:42 +02:00
}
2023-08-19 12:58:07 +02:00
2023-08-21 11:13:00 +02:00
return STATUS_SUCCESS;
2023-08-19 11:44:42 +02:00
}
2023-09-26 12:00:45 +02:00
STATIC
VOID
InitDriverList(
2023-08-19 04:52:57 +02:00
_In_ PINVALID_DRIVERS_HEAD ListHead
)
{
ListHead->count = 0;
ListHead->first_entry = NULL;
}
2023-09-26 12:00:45 +02:00
STATIC
VOID
AddDriverToList(
2023-08-19 04:52:57 +02:00
_In_ PINVALID_DRIVERS_HEAD InvalidDriversHead,
2023-08-19 11:44:42 +02:00
_In_ PDRIVER_OBJECT Driver,
_In_ INT Reason
2023-08-19 04:52:57 +02:00
)
{
PINVALID_DRIVER new_entry = ExAllocatePool2(
POOL_FLAG_NON_PAGED,
sizeof( INVALID_DRIVER ),
INVALID_DRIVER_LIST_ENTRY_POOL
);
if ( !new_entry )
return;
new_entry->driver = Driver;
2023-08-19 11:44:42 +02:00
new_entry->reason = Reason;
2023-08-19 04:52:57 +02:00
new_entry->next = InvalidDriversHead->first_entry;
InvalidDriversHead->first_entry = new_entry;
}
2023-09-26 12:00:45 +02:00
STATIC
VOID
RemoveInvalidDriverFromList(
2023-08-19 04:52:57 +02:00
_In_ PINVALID_DRIVERS_HEAD InvalidDriversHead
)
{
if ( InvalidDriversHead->first_entry )
{
PINVALID_DRIVER entry = InvalidDriversHead->first_entry;
InvalidDriversHead->first_entry = InvalidDriversHead->first_entry->next;
ExFreePoolWithTag( entry, INVALID_DRIVER_LIST_ENTRY_POOL );
}
}
2023-09-26 12:00:45 +02:00
STATIC
VOID
EnumerateInvalidDrivers(
2023-08-19 04:52:57 +02:00
_In_ PINVALID_DRIVERS_HEAD InvalidDriversHead
)
{
PINVALID_DRIVER entry = InvalidDriversHead->first_entry;
while ( entry != NULL )
{
DEBUG_LOG( "Invalid Driver: %wZ", entry->driver->DriverName );
entry = entry->next;
}
}
2023-09-26 12:00:45 +02:00
STATIC
NTSTATUS
ValidateDriverObjectHasBackingModule(
2023-08-19 04:52:57 +02:00
_In_ PSYSTEM_MODULES ModuleInformation,
_In_ PDRIVER_OBJECT DriverObject,
_Out_ PBOOLEAN Result
)
{
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->ImageBase == DriverObject->DriverStart )
{
*Result = TRUE;
return STATUS_SUCCESS;
}
}
DEBUG_LOG( "invalid driver found" );
*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
2023-09-26 12:00:45 +02:00
NTSTATUS
GetSystemModuleInformation(
2023-09-26 15:48:21 +02:00
_Inout_ PSYSTEM_MODULES ModuleInformation
2023-08-19 04:52:57 +02:00
)
{
if ( !ModuleInformation )
return STATUS_INVALID_PARAMETER;
ULONG size = 0;
/*
* query system module information without an output buffer to get
* number of bytes required to store all module info structures
*/
if ( !NT_SUCCESS( RtlQueryModuleInformation(
&size,
sizeof( RTL_MODULE_EXTENDED_INFO ),
NULL
) ) )
{
DEBUG_ERROR( "Failed to query module information" );
return STATUS_ABANDONED;
}
/* 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_ABANDONED;
}
/* Query the modules again this time passing a pointer to the allocated buffer */
if ( !NT_SUCCESS( RtlQueryModuleInformation(
&size,
sizeof( RTL_MODULE_EXTENDED_INFO ),
driver_information
) ) )
{
DEBUG_ERROR( "Failed lolz" );
ExFreePoolWithTag( driver_information, SYSTEM_MODULES_POOL );
return STATUS_ABANDONED;
}
ModuleInformation->address = driver_information;
ModuleInformation->module_count = size / sizeof( RTL_MODULE_EXTENDED_INFO );
return STATUS_SUCCESS;
}
2023-09-26 12:00:45 +02:00
STATIC
NTSTATUS
ValidateDriverObjects(
2023-08-19 04:52:57 +02:00
_In_ PSYSTEM_MODULES SystemModules,
_In_ PINVALID_DRIVERS_HEAD InvalidDriverListHead
)
{
if ( !SystemModules || !InvalidDriverListHead )
return STATUS_INVALID_PARAMETER;
HANDLE handle;
OBJECT_ATTRIBUTES attributes = { 0 };
PVOID directory = { 0 };
UNICODE_STRING directory_name;
2023-08-21 11:13:00 +02:00
NTSTATUS status;
2023-08-19 04:52:57 +02:00
RtlInitUnicodeString( &directory_name, L"\\Driver" );
InitializeObjectAttributes(
&attributes,
&directory_name,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
if ( !NT_SUCCESS( ZwOpenDirectoryObject(
&handle,
DIRECTORY_ALL_ACCESS,
&attributes
) ) )
{
DEBUG_ERROR( "Failed to query directory object" );
return STATUS_ABANDONED;
}
if ( !NT_SUCCESS( ObReferenceObjectByHandle(
handle,
DIRECTORY_ALL_ACCESS,
NULL,
KernelMode,
&directory,
NULL
) ) )
{
DEBUG_ERROR( "Failed to reference directory by handle" );
ZwClose( handle );
return STATUS_ABANDONED;
}
/*
* 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
*/
POBJECT_DIRECTORY directory_object = ( POBJECT_DIRECTORY )directory;
ExAcquirePushLockExclusiveEx( &directory_object->Lock, NULL );
2023-08-21 11:13:00 +02:00
PVOID whitelisted_regions_buffer = ExAllocatePool2(
POOL_FLAG_NON_PAGED,
WHITELISTED_MODULE_COUNT * MODULE_MAX_STRING_SIZE,
2023-08-22 19:32:25 +02:00
WHITELISTED_MODULE_TAG
);
2023-08-21 11:13:00 +02:00
if ( !whitelisted_regions_buffer )
goto end;
status = PopulateWhitelistedModuleBuffer(
whitelisted_regions_buffer,
SystemModules
);
if ( !NT_SUCCESS( status ) )
{
DEBUG_ERROR( "PopulateWhiteListedBuffer failed with status %x", status );
goto end;
}
2023-08-19 04:52:57 +02:00
for ( INT i = 0; i < NUMBER_HASH_BUCKETS; i++ )
{
POBJECT_DIRECTORY_ENTRY entry = directory_object->HashBuckets[ i ];
if ( !entry )
continue;
POBJECT_DIRECTORY_ENTRY sub_entry = entry;
while ( sub_entry )
{
PDRIVER_OBJECT current_driver = sub_entry->Object;
BOOLEAN flag;
2023-08-19 11:44:42 +02:00
/* validate driver has backing module */
2023-08-19 04:52:57 +02:00
if ( !NT_SUCCESS( ValidateDriverObjectHasBackingModule(
SystemModules,
current_driver,
&flag
) ) )
{
DEBUG_LOG( "Error validating driver object" );
ExReleasePushLockExclusiveEx( &directory_object->Lock, 0 );
ObDereferenceObject( directory );
ZwClose( handle );
return STATUS_ABANDONED;
}
if ( !flag )
{
InvalidDriverListHead->count += 1;
2023-08-19 11:44:42 +02:00
AddDriverToList( InvalidDriverListHead, current_driver, REASON_NO_BACKING_MODULE );
}
/* validate drivers IOCTL dispatch routines */
if ( !NT_SUCCESS( ValidateDriverIOCTLDispatchRegion(
current_driver,
SystemModules,
2023-08-21 11:13:00 +02:00
(PWHITELISTED_REGIONS)whitelisted_regions_buffer,
2023-08-19 11:44:42 +02:00
&flag
) ) )
{
DEBUG_LOG( "Error validating drivers IOCTL routines" );
ExReleasePushLockExclusiveEx( &directory_object->Lock, 0 );
ObDereferenceObject( directory );
ZwClose( handle );
return STATUS_ABANDONED;
}
if ( !flag )
{
InvalidDriverListHead->count += 1;
AddDriverToList( InvalidDriverListHead, current_driver, REASON_INVALID_IOCTL_DISPATCH );
}
2023-08-19 04:52:57 +02:00
sub_entry = sub_entry->ChainLink;
}
}
2023-08-21 11:13:00 +02:00
end:
if ( whitelisted_regions_buffer)
ExFreePoolWithTag( whitelisted_regions_buffer, WHITELISTED_MODULE_TAG );
2023-08-19 04:52:57 +02:00
ExReleasePushLockExclusiveEx( &directory_object->Lock, 0 );
ObDereferenceObject( directory );
ZwClose( handle );
return STATUS_SUCCESS;
}
2023-09-26 12:00:45 +02:00
NTSTATUS
HandleValidateDriversIOCTL(
2023-08-19 04:52:57 +02:00
_In_ PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
SYSTEM_MODULES system_modules = { 0 };
/* Fix annoying visual studio linting error */
RtlZeroMemory( &system_modules, sizeof( SYSTEM_MODULES ) );
status = GetSystemModuleInformation( &system_modules );
if ( !NT_SUCCESS( status ) )
{
DEBUG_ERROR( "Error retriving system module information" );
return status;
}
PINVALID_DRIVERS_HEAD 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_ABANDONED;
}
/*
* 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 );
if ( !NT_SUCCESS( ValidateDriverObjects( &system_modules, head ) ) )
{
DEBUG_ERROR( "Failed to validate driver objects" );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
return STATUS_ABANDONED;
}
2023-08-19 06:13:33 +02:00
MODULE_VALIDATION_FAILURE_HEADER header;
2023-08-20 09:32:46 +02:00
header.module_count = head->count >= MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT
? MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT
: head->count;
2023-08-19 06:13:33 +02:00
2023-08-19 04:52:57 +02:00
if ( head->count > 0 )
{
DEBUG_LOG( "found INVALID drivers with count: %i", head->count );
2023-09-23 09:22:43 +02:00
PVOID buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof( MODULE_VALIDATION_FAILURE_HEADER ) +
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT * sizeof( MODULE_VALIDATION_FAILURE ), 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;
}
2023-08-19 04:52:57 +02:00
Irp->IoStatus.Information = sizeof( MODULE_VALIDATION_FAILURE_HEADER ) +
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT * sizeof( MODULE_VALIDATION_FAILURE );
2023-08-20 07:51:53 +02:00
RtlCopyMemory(
2023-09-23 09:22:43 +02:00
buffer,
2023-08-20 07:51:53 +02:00
&header,
sizeof( MODULE_VALIDATION_FAILURE_HEADER )
);
2023-08-19 04:52:57 +02:00
for ( INT i = 0; i < head->count; i++ )
{
/* make sure we free any non reported modules */
if ( i >= MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT )
2023-08-20 05:23:40 +02:00
{
2023-08-19 04:52:57 +02:00
RemoveInvalidDriverFromList( head );
2023-08-20 05:23:40 +02:00
continue;
}
2023-08-19 04:52:57 +02:00
MODULE_VALIDATION_FAILURE report;
report.report_code = REPORT_MODULE_VALIDATION_FAILURE;
2023-08-19 11:44:42 +02:00
report.report_type = head->first_entry->reason;
2023-08-19 04:52:57 +02:00
report.driver_base_address = head->first_entry->driver->DriverStart;
2023-08-20 11:17:03 +02:00
report.driver_size = head->first_entry->driver->DriverSize;
2023-08-19 08:06:51 +02:00
2023-08-20 07:46:02 +02:00
ANSI_STRING string;
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 statsu %x", status );
2023-08-19 04:52:57 +02:00
RtlCopyMemory(
2023-09-23 09:22:43 +02:00
( UINT64 )buffer + sizeof( MODULE_VALIDATION_FAILURE_HEADER ) + i * sizeof( MODULE_VALIDATION_FAILURE ),
2023-08-19 04:52:57 +02:00
&report,
2023-08-19 08:06:51 +02:00
sizeof( MODULE_VALIDATION_FAILURE ) );
2023-08-19 04:52:57 +02:00
RemoveInvalidDriverFromList( head );
}
2023-09-23 09:22:43 +02:00
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 );
2023-08-19 04:52:57 +02:00
}
else
{
DEBUG_LOG( "No INVALID drivers found :)" );
}
ExFreePoolWithTag( head, INVALID_DRIVER_LIST_HEAD_POOL );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
2023-08-19 06:03:48 +02:00
2023-08-28 17:00:52 +02:00
return status;
}
2023-09-26 12:00:45 +02:00
NTSTATUS
IsInstructionPointerInInvalidRegion(
2023-08-28 17:00:52 +02:00
_In_ UINT64 RIP,
_In_ PSYSTEM_MODULES SystemModules,
_Out_ PBOOLEAN Result
)
{
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;
}
2023-09-26 12:00:45 +02:00
STATIC
NTSTATUS
AnalyseNmiData(
2023-09-13 11:46:28 +02:00
_In_ PNMI_CONTEXT NmiContext,
2023-08-28 17:00:52 +02:00
_In_ PSYSTEM_MODULES SystemModules,
_In_ PIRP Irp
)
{
2023-09-13 11:46:28 +02:00
if ( !NmiContext || !SystemModules )
2023-08-28 17:00:52 +02:00
return STATUS_INVALID_PARAMETER;
2023-09-13 11:46:28 +02:00
for ( INT core = 0; core < NmiContext->core_count; core++ )
2023-08-28 17:00:52 +02:00
{
2023-09-13 11:46:28 +02:00
PNMI_CORE_CONTEXT context = ( PNMI_CORE_CONTEXT )( ( uintptr_t )NmiContext->nmi_core_context + core * sizeof( NMI_CORE_CONTEXT ) );
2023-08-28 17:00:52 +02:00
/* Make sure our NMIs were run */
if ( !context->nmi_callbacks_run )
{
NMI_CALLBACK_FAILURE report;
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;
}
PNMI_CALLBACK_DATA thread_data = ( PNMI_CALLBACK_DATA )(
2023-09-13 11:46:28 +02:00
( uintptr_t )NmiContext->thread_data_pool + core * sizeof( NMI_CALLBACK_DATA ) );
2023-08-28 17:00:52 +02:00
DEBUG_LOG( "cpu number: %i callback count: %i", core, context->nmi_callbacks_run );
/* Walk the stack */
for ( INT frame = 0; frame < thread_data->num_frames_captured; frame++ )
{
BOOLEAN flag;
DWORD64 stack_frame = *( DWORD64* )(
2023-09-13 11:46:28 +02:00
( ( uintptr_t )NmiContext->stack_frames + thread_data->stack_frames_offset + frame * sizeof( PVOID ) ) );
2023-08-28 17:00:52 +02:00
if ( !NT_SUCCESS( IsInstructionPointerInInvalidRegion( stack_frame, SystemModules, &flag ) ) )
{
DEBUG_ERROR( "errro checking RIP for current stack address" );
continue;
}
if ( flag == FALSE )
{
/*
* 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;
report.report_code = REPORT_NMI_CALLBACK_FAILURE;
report.kthread_address = thread_data->kthread_address;
report.invalid_rip = stack_frame;
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;
}
2023-09-26 12:00:45 +02:00
STATIC
BOOLEAN
NmiCallback(
2023-08-28 17:00:52 +02:00
_In_ PVOID Context,
_In_ BOOLEAN Handled
)
{
UNREFERENCED_PARAMETER( Handled );
PVOID current_thread = KeGetCurrentThread();
NMI_CALLBACK_DATA thread_data = { 0 };
2023-09-13 11:46:28 +02:00
PNMI_CONTEXT nmi_context = ( PNMI_CONTEXT )Context;
ULONG proc_num = KeGetCurrentProcessorNumber();
2023-08-28 17:00:52 +02:00
/*
* Cannot allocate pool in this function as it runs at IRQL >= dispatch level
* so ive just allocated a global pool with size equal to 0x200 * num_procs
*/
INT num_frames_captured = RtlCaptureStackBackTrace(
NULL,
2023-09-14 06:09:52 +02:00
STACK_FRAME_POOL_SIZE / sizeof(UINT64),
2023-09-13 11:46:28 +02:00
( uintptr_t )nmi_context->stack_frames + proc_num * STACK_FRAME_POOL_SIZE,
2023-08-28 17:00:52 +02:00
NULL
);
/*
* This function is run in the context of the interrupted thread hence we can
* gather any and all information regarding the thread that may be useful for analysis
*/
thread_data.kthread_address = ( UINT64 )current_thread;
thread_data.kprocess_address = ( UINT64 )PsGetCurrentProcess();
thread_data.stack_base = *( ( UINT64* )( ( uintptr_t )current_thread + KTHREAD_STACK_BASE_OFFSET ) );
thread_data.stack_limit = *( ( UINT64* )( ( uintptr_t )current_thread + KTHREAD_STACK_LIMIT_OFFSET ) );
thread_data.start_address = *( ( UINT64* )( ( uintptr_t )current_thread + KTHREAD_START_ADDRESS_OFFSET ) );
thread_data.cr3 = __readcr3();
thread_data.stack_frames_offset = proc_num * STACK_FRAME_POOL_SIZE;
thread_data.num_frames_captured = num_frames_captured;
RtlCopyMemory(
2023-09-13 11:46:28 +02:00
( ( uintptr_t )nmi_context->thread_data_pool ) + proc_num * sizeof( thread_data ),
2023-08-28 17:00:52 +02:00
&thread_data,
sizeof( thread_data )
);
2023-09-13 11:46:28 +02:00
PNMI_CORE_CONTEXT core_context =
( PNMI_CORE_CONTEXT )( ( uintptr_t )nmi_context->nmi_core_context + proc_num * sizeof( NMI_CORE_CONTEXT ) );
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
core_context->nmi_callbacks_run += 1;
2023-08-30 18:29:44 +02:00
2023-08-28 17:00:52 +02:00
return TRUE;
}
2023-09-26 12:00:45 +02:00
STATIC
NTSTATUS
LaunchNonMaskableInterrupt(
2023-09-13 11:46:28 +02:00
_In_ PNMI_CONTEXT NmiContext
2023-08-28 17:00:52 +02:00
)
{
2023-09-13 11:46:28 +02:00
if ( !NmiContext )
2023-08-28 17:00:52 +02:00
return STATUS_INVALID_PARAMETER;
2023-09-13 11:46:28 +02:00
PKAFFINITY_EX ProcAffinityPool =
ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( KAFFINITY_EX ), PROC_AFFINITY_POOL );
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
if ( !ProcAffinityPool )
return STATUS_MEMORY_NOT_ALLOCATED;
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
NmiContext->stack_frames =
ExAllocatePool2( POOL_FLAG_NON_PAGED, NmiContext->core_count * STACK_FRAME_POOL_SIZE, STACK_FRAMES_POOL );
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
if ( !NmiContext->stack_frames )
2023-08-28 17:00:52 +02:00
{
2023-09-13 11:46:28 +02:00
ExFreePoolWithTag( ProcAffinityPool, PROC_AFFINITY_POOL );
return STATUS_MEMORY_NOT_ALLOCATED;
2023-08-28 17:00:52 +02:00
}
2023-09-13 11:46:28 +02:00
NmiContext->thread_data_pool =
ExAllocatePool2( POOL_FLAG_NON_PAGED, NmiContext->core_count * sizeof( NMI_CALLBACK_DATA ), THREAD_DATA_POOL );
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
if ( !NmiContext->thread_data_pool )
2023-08-28 17:00:52 +02:00
{
2023-09-13 11:46:28 +02:00
ExFreePoolWithTag( NmiContext->stack_frames, STACK_FRAMES_POOL );
ExFreePoolWithTag( ProcAffinityPool, PROC_AFFINITY_POOL );
return STATUS_MEMORY_NOT_ALLOCATED;
2023-08-28 17:00:52 +02:00
}
LARGE_INTEGER delay = { 0 };
2023-09-13 11:46:28 +02:00
delay.QuadPart -= 100 * 10000;
2023-08-30 18:29:44 +02:00
2023-09-13 11:46:28 +02:00
for ( ULONG core = 0; core < NmiContext->core_count; core++ )
2023-08-28 17:00:52 +02:00
{
2023-09-13 11:46:28 +02:00
KeInitializeAffinityEx( ProcAffinityPool );
KeAddProcessorAffinityEx( ProcAffinityPool, core );
2023-08-30 18:29:44 +02:00
2023-09-13 11:46:28 +02:00
HalSendNMI( ProcAffinityPool );
2023-08-28 17:00:52 +02:00
/*
* 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 );
}
2023-09-13 11:46:28 +02:00
ExFreePoolWithTag( ProcAffinityPool, PROC_AFFINITY_POOL );
2023-08-28 17:00:52 +02:00
return STATUS_SUCCESS;
}
2023-09-26 15:48:21 +02:00
NTSTATUS
HandleNmiIOCTL(
2023-08-28 17:00:52 +02:00
_In_ PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
SYSTEM_MODULES system_modules = { 0 };
2023-09-13 11:46:28 +02:00
NMI_CONTEXT nmi_context = { 0 };
PVOID callback_handle;
2023-08-30 18:29:44 +02:00
2023-09-13 11:46:28 +02:00
nmi_context.core_count = KeQueryActiveProcessorCountEx( 0 );
nmi_context.nmi_core_context =
ExAllocatePool2( POOL_FLAG_NON_PAGED, nmi_context.core_count * sizeof( NMI_CORE_CONTEXT ), NMI_CONTEXT_POOL );
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
if ( !nmi_context.nmi_core_context )
return STATUS_MEMORY_NOT_ALLOCATED;
2023-08-28 17:00:52 +02:00
/*
* 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
*/
2023-09-13 15:19:07 +02:00
callback_handle = KeRegisterNmiCallback( NmiCallback, &nmi_context );
2023-08-28 17:00:52 +02:00
2023-09-13 11:46:28 +02:00
if ( !callback_handle )
2023-08-28 17:00:52 +02:00
{
DEBUG_ERROR( "KeRegisterNmiCallback failed" );
2023-09-13 11:46:28 +02:00
ExFreePoolWithTag( nmi_context.nmi_core_context, NMI_CONTEXT_POOL );
2023-08-28 17:00:52 +02:00
return STATUS_ABANDONED;
}
/*
* We query the system modules each time since they can potentially
* change at any time
*/
status = GetSystemModuleInformation( &system_modules );
if ( !NT_SUCCESS( status ) )
{
DEBUG_ERROR( "Error retriving system module information" );
return status;
}
2023-09-13 11:46:28 +02:00
status = LaunchNonMaskableInterrupt( &nmi_context );
2023-08-28 17:00:52 +02:00
if ( !NT_SUCCESS( status ) )
{
DEBUG_ERROR( "Error running NMI callbacks" );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
return status;
}
2023-09-13 11:46:28 +02:00
status = AnalyseNmiData( &nmi_context, &system_modules, Irp );
2023-08-28 17:00:52 +02:00
if ( !NT_SUCCESS( status ) )
DEBUG_ERROR( "Error analysing nmi data" );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
2023-09-13 11:46:28 +02:00
ExFreePoolWithTag( nmi_context.nmi_core_context, NMI_CONTEXT_POOL );
2023-09-13 12:25:32 +02:00
if ( nmi_context.stack_frames )
ExFreePoolWithTag( nmi_context.stack_frames, STACK_FRAMES_POOL );
if (nmi_context.thread_data_pool )
ExFreePoolWithTag( nmi_context.thread_data_pool, THREAD_DATA_POOL );
2023-09-13 11:46:28 +02:00
KeDeregisterNmiCallback( callback_handle );
2023-08-28 17:00:52 +02:00
2023-08-19 04:52:57 +02:00
return status;
2023-09-24 13:13:20 +02:00
}
/*
* The RundownRoutine is executed if the thread terminates before the APC was delivered to
* user mode.
*/
2023-09-26 12:00:45 +02:00
STATIC
VOID
ApcRundownRoutine(
2023-09-24 13:13:20 +02:00
_In_ PRKAPC Apc
)
{
2023-09-26 15:32:06 +02:00
FreeApcAndDecrementApcCount( Apc, APC_CONTEXT_ID_STACKWALK );
2023-09-24 13:13:20 +02:00
}
/*
2023-09-25 17:41:38 +02:00
* 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.
2023-09-24 13:13:20 +02:00
*/
2023-09-26 12:00:45 +02:00
STATIC
VOID
ApcKernelRoutine(
2023-09-24 13:13:20 +02:00
_In_ PRKAPC Apc,
2023-09-25 17:41:38 +02:00
_Inout_ _Deref_pre_maybenull_ PKNORMAL_ROUTINE* NormalRoutine,
2023-09-24 13:13:20 +02:00
_Inout_ _Deref_pre_maybenull_ PVOID* NormalContext,
_Inout_ _Deref_pre_maybenull_ PVOID* SystemArgument1,
_Inout_ _Deref_pre_maybenull_ PVOID* SystemArgument2
)
{
2023-09-25 17:41:38 +02:00
PVOID buffer = NULL;
INT frames_captured = 0;
UINT64 stack_frame = 0;
NTSTATUS status;
BOOLEAN flag = FALSE;
PAPC_STACKWALK_CONTEXT context;
context = ( PAPC_STACKWALK_CONTEXT )Apc->NormalContext;
buffer = ExAllocatePool2( POOL_FLAG_NON_PAGED, 0x200, POOL_TAG_APC );
if ( !buffer )
2023-09-26 15:32:06 +02:00
goto free;
2023-09-25 17:41:38 +02:00
frames_captured = RtlCaptureStackBackTrace(
NULL,
STACK_FRAME_POOL_SIZE / sizeof(UINT64),
buffer,
NULL
);
if ( frames_captured == NULL )
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;
}
}
free:
2023-09-26 15:32:06 +02:00
if (buffer )
ExFreePoolWithTag( buffer, POOL_TAG_APC );
2023-09-26 12:00:45 +02:00
FreeApcAndDecrementApcCount( Apc, APC_CONTEXT_ID_STACKWALK );
2023-09-24 13:13:20 +02:00
}
/*
* The NormalRoutine is executed in user mode when the APC is delivered.
*/
2023-09-26 12:00:45 +02:00
STATIC
VOID
ApcNormalRoutine(
2023-09-24 13:13:20 +02:00
_In_opt_ PVOID NormalContext,
_In_opt_ PVOID SystemArgument1,
_In_opt_ PVOID SystemArgument2
)
{
}
2023-09-26 12:00:45 +02:00
STATIC
VOID
ValidateThreadViaKernelApcCallback(
2023-09-24 13:13:20 +02:00
_In_ PEPROCESS Process,
_In_ PVOID Context
)
{
NTSTATUS status;
PLIST_ENTRY thread_list_head;
PLIST_ENTRY thread_list_entry;
PETHREAD current_thread;
PKAPC apc = NULL;
BOOLEAN apc_status;
2023-09-26 15:32:06 +02:00
HANDLE id = PsGetProcessId( Process );
2023-09-26 12:00:45 +02:00
/* we dont want to schedule an apc to threads owned by the kernel */
if ( Process == PsInitialSystemProcess )
return;
2023-09-24 13:13:20 +02:00
thread_list_head = ( PLIST_ENTRY )( ( UINT64 )Process + KPROCESS_THREADLIST_OFFSET );
thread_list_entry = thread_list_head->Flink;
while ( thread_list_entry != thread_list_head )
{
current_thread = ( PETHREAD )( ( UINT64 )thread_list_entry - KTHREAD_THREADLIST_OFFSET );
2023-09-25 17:41:38 +02:00
/* ensure thread has a valid cid entry */
2023-09-24 13:13:20 +02:00
if ( PsGetThreadId( current_thread ) == NULL )
goto increment;
2023-09-25 17:41:38 +02:00
if (current_thread == KeGetCurrentThread())
goto increment;
2023-09-24 13:13:20 +02:00
apc = ( PKAPC )ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( KAPC ), POOL_TAG_APC );
if ( !apc )
goto increment;
KeInitializeApc(
apc,
current_thread,
OriginalApcEnvironment,
ApcKernelRoutine,
ApcRundownRoutine,
ApcNormalRoutine,
KernelMode,
2023-09-25 17:41:38 +02:00
Context
2023-09-24 13:13:20 +02:00
);
apc_status = KeInsertQueueApc(
apc,
NULL,
NULL,
IO_NO_INCREMENT
);
if ( !apc_status )
2023-09-25 17:41:38 +02:00
{
2023-09-24 13:13:20 +02:00
DEBUG_ERROR( "KeInsertQueueApc failed" );
2023-09-25 17:41:38 +02:00
goto increment;
}
2023-09-26 12:00:45 +02:00
IncrementApcCount( APC_CONTEXT_ID_STACKWALK );
2023-09-24 13:13:20 +02:00
increment:
thread_list_entry = thread_list_entry->Flink;
}
}
/*
* 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.
*/
2023-09-26 12:00:45 +02:00
NTSTATUS
ValidateThreadsViaKernelApc()
2023-09-24 13:13:20 +02:00
{
2023-09-25 17:41:38 +02:00
NTSTATUS status;
PAPC_STACKWALK_CONTEXT context = NULL;
context = ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( APC_STACKWALK_CONTEXT ), POOL_TAG_APC );
if ( !context )
return STATUS_MEMORY_NOT_ALLOCATED;
2023-09-26 12:00:45 +02:00
context->header.context_id = APC_CONTEXT_ID_STACKWALK;
2023-09-25 17:41:38 +02:00
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 ) )
{
ExFreePoolWithTag( context->modules, POOL_TAG_APC );
ExFreePoolWithTag( context, POOL_TAG_APC );
return STATUS_MEMORY_NOT_ALLOCATED;
}
InsertApcContext( context );
2023-09-24 13:13:20 +02:00
EnumerateProcessListWithCallbackFunction(
ValidateThreadViaKernelApcCallback,
2023-09-25 17:41:38 +02:00
context
2023-09-24 13:13:20 +02:00
);
2023-09-25 17:41:38 +02:00
}
2023-09-26 12:00:45 +02:00
VOID
FreeApcStackwalkApcContextInformation(
_In_ PAPC_STACKWALK_CONTEXT Context
)
2023-09-25 17:41:38 +02:00
{
2023-09-26 15:32:06 +02:00
if (Context->modules->address )
ExFreePoolWithTag( Context->modules->address, SYSTEM_MODULES_POOL );
2023-09-26 15:48:21 +02:00
2023-09-26 12:00:45 +02:00
if ( Context->modules )
2023-09-26 15:32:06 +02:00
ExFreePoolWithTag( Context->modules, POOL_TAG_APC );
2023-08-19 04:52:57 +02:00
}