big money changes to driver

This commit is contained in:
lhodges1 2023-08-19 12:52:57 +10:00
parent 9356803b33
commit 222191ffea
16 changed files with 1135 additions and 15 deletions

View file

@ -8,4 +8,111 @@
#define DEBUG_LOG(fmt, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[+] " fmt "\n", ##__VA_ARGS__)
#define DEBUG_ERROR(fmt, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[-] " fmt "\n", ##__VA_ARGS__)
#define NMI_CONTEXT_POOL '7331'
#define STACK_FRAMES_POOL 'loop'
#define INVALID_DRIVER_LIST_HEAD_POOL 'rwar'
#define INVALID_DRIVER_LIST_ENTRY_POOL 'gaah'
#define SYSTEM_MODULES_POOL 'halb'
#define THREAD_DATA_POOL 'doof'
#define PROC_AFFINITY_POOL 'eeee'
#define ERROR -1
#define STACK_FRAME_POOL_SIZE 0x200
#define NUMBER_HASH_BUCKETS 37
#define KTHREAD_STACK_BASE_OFFSET 0x030
#define KTHREAD_STACK_LIMIT_OFFSET 0x038
#define KTHREAD_START_ADDRESS_OFFSET 0x450
typedef struct _KAFFINITY_EX
{
USHORT Count;
USHORT Size;
ULONG Reserved;
ULONGLONG Bitmap[ 20 ];
} KAFFINITY_EX, * PKAFFINITY_EX;
typedef struct _OBJECT_DIRECTORY_ENTRY
{
struct _OBJECT_DIRECTORY_ENTRY* ChainLink;
PVOID Object;
ULONG HashValue;
} OBJECT_DIRECTORY_ENTRY, * POBJECT_DIRECTORY_ENTRY;
typedef struct _OBJECT_DIRECTORY
{
POBJECT_DIRECTORY_ENTRY HashBuckets[ NUMBER_HASH_BUCKETS ];
EX_PUSH_LOCK Lock;
struct _DEVICE_MAP* DeviceMap;
ULONG SessionId;
PVOID NamespaceEntry;
ULONG Flags;
} OBJECT_DIRECTORY, * POBJECT_DIRECTORY;
typedef struct _DEVICE_MAP
{
struct _OBJECT_DIRECTORY* DosDevicesDirectory;
struct _OBJECT_DIRECTORY* GlobalDosDevicesDirectory;
ULONG ReferenceCount;
ULONG DriveMap;
UCHAR DriveType[ 32 ];
} DEVICE_MAP, * PDEVICE_MAP;
typedef struct _RTL_MODULE_EXTENDED_INFO
{
PVOID ImageBase;
ULONG ImageSize;
USHORT FileNameOffset;
CHAR FullPathName[ 0x100 ];
} RTL_MODULE_EXTENDED_INFO, * PRTL_MODULE_EXTENDED_INFO;
/* undocumented functions */
EXTERN_C VOID KeInitializeAffinityEx(
PKAFFINITY_EX affinity
);
EXTERN_C VOID KeAddProcessorAffinityEx(
PKAFFINITY_EX affinity,
INT num
);
EXTERN_C VOID HalSendNMI(
PKAFFINITY_EX affinity
);
NTSTATUS
RtlQueryModuleInformation(
ULONG* InformationLength,
ULONG SizePerModule,
PVOID InformationBuffer );
/*
Thread Information Block: (GS register)
SEH frame: 0x00
Stack Base: 0x08
Stack Limit: 0x10
SubSystemTib: 0x18
Fiber Data: 0x20
Arbitrary Data: 0x28
TEB: 0x30
Environment Pointer: 0x38
Process ID: 0x40
Current Thread ID: 0x48
Active RPC Handle: 0x50
Thread Local Storage Array: 0x58
PEB: 0x60
Last error number: 0x68
Count Owned Critical Sections: 0x6C
CSR Client Thread: 0x70
Win32 Thread Information: 0x78
...
*/
#endif

View file

@ -119,11 +119,15 @@
<ItemGroup>
<ClCompile Include="driver.c" />
<ClCompile Include="ioctl.c" />
<ClCompile Include="modules.c" />
<ClCompile Include="nmi.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="common.h" />
<ClInclude Include="driver.h" />
<ClInclude Include="ioctl.h" />
<ClInclude Include="modules.h" />
<ClInclude Include="nmi.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View file

@ -30,6 +30,12 @@
<ClCompile Include="ioctl.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="nmi.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="modules.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="driver.h">
@ -41,5 +47,11 @@
<ClInclude Include="ioctl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="nmi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="modules.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -2,14 +2,71 @@
#include "common.h"
#include "nmi.h"
#include "modules.h"
NTSTATUS DeviceControl(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PIRP Irp
)
{
DEBUG_LOG( "Handle opened to DonnaAC" );
UNREFERENCED_PARAMETER( DriverObject );
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack_location = IoGetCurrentIrpStackLocation( Irp );
HANDLE handle;
switch ( stack_location->Parameters.DeviceIoControl.IoControlCode )
{
case IOCCTL_RUN_NMI_CALLBACKS:
status = HandleNmiIOCTL( Irp );
if ( !NT_SUCCESS( status ) )
DEBUG_ERROR( "RunNmiCallbacks failed with status %lx", status );
break;
case IOCTL_VALIDATE_DRIVER_OBJECTS:
/*
* The reason this function is run in a new thread and not the thread
* issuing the IOCTL is because ZwOpenDirectoryObject issues a
* user mode handle if called on the user mode thread calling DeviceIoControl.
* This is a problem because when we pass said handle to ObReferenceObjectByHandle
* it will issue a bug check under windows driver verifier.
*/
status = PsCreateSystemThread(
&handle,
PROCESS_ALL_ACCESS,
NULL,
NULL,
NULL,
HandleValidateDriversIOCTL,
Irp
);
if ( !NT_SUCCESS( status ) )
DEBUG_ERROR( "Failed to start thread to validate system drivers" );
/*
* wait on our thread so we dont complete the IRP before we've filled the
* buffer with information and prevent any weird IRP multithreaded interactions
*/
KeWaitForSingleObject( handle, Executive, KernelMode, FALSE, NULL );
ZwClose( handle );
break;
default:
DEBUG_ERROR( "Invalid IOCTL passed to driver" );
break;
}
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return Irp->IoStatus.Status;
Irp->IoStatus.Status = status;
return status;
}
NTSTATUS DeviceClose(

View file

@ -5,6 +5,11 @@
#include <wdftypes.h>
#include <wdf.h>
#include "nmi.h"
#define IOCCTL_RUN_NMI_CALLBACKS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2001, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_VALIDATE_DRIVER_OBJECTS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2002, METHOD_BUFFERED, FILE_ANY_ACCESS)
NTSTATUS DeviceControl(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PIRP Irp

337
driver/modules.c Normal file
View file

@ -0,0 +1,337 @@
#include "modules.h"
#include "nmi.h"
#include "common.h"
VOID InitDriverList(
_In_ PINVALID_DRIVERS_HEAD ListHead
)
{
ListHead->count = 0;
ListHead->first_entry = NULL;
}
VOID AddDriverToList(
_In_ PINVALID_DRIVERS_HEAD InvalidDriversHead,
_In_ PDRIVER_OBJECT Driver
)
{
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;
new_entry->next = InvalidDriversHead->first_entry;
InvalidDriversHead->first_entry = new_entry;
}
VOID RemoveInvalidDriverFromList(
_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 );
}
}
VOID EnumerateInvalidDrivers(
_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;
}
}
NTSTATUS ValidateDriverObjectHasBackingModule(
_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
NTSTATUS GetSystemModuleInformation(
_Out_ PSYSTEM_MODULES ModuleInformation
)
{
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;
}
NTSTATUS ValidateDriverObjects(
_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;
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 );
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;
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;
AddDriverToList( InvalidDriverListHead, current_driver );
}
sub_entry = sub_entry->ChainLink;
}
}
ExReleasePushLockExclusiveEx( &directory_object->Lock, 0 );
ObDereferenceObject( directory );
ZwClose( handle );
return STATUS_SUCCESS;
}
NTSTATUS HandleValidateDriversIOCTL(
_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;
}
if ( head->count > 0 )
{
DEBUG_LOG( "found INVALID drivers with count: %i", head->count );
Irp->IoStatus.Information = sizeof( MODULE_VALIDATION_FAILURE_HEADER ) +
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT * sizeof( MODULE_VALIDATION_FAILURE );
MODULE_VALIDATION_FAILURE_HEADER header;
header.module_count = head->count;
RtlCopyMemory(
Irp->AssociatedIrp.SystemBuffer,
&header,
sizeof( MODULE_VALIDATION_FAILURE_HEADER ) );
for ( INT i = 0; i < head->count; i++ )
{
/* make sure we free any non reported modules */
if ( i >= MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT )
RemoveInvalidDriverFromList( head );
MODULE_VALIDATION_FAILURE report;
report.report_code = REPORT_MODULE_VALIDATION_FAILURE;
report.driver_base_address = head->first_entry->driver->DriverStart;
report.driver_size = head->first_entry->driver->Size;
RtlCopyMemory(
&report.driver_name,
head->first_entry->driver->DriverName.Buffer,
MODULE_REPORT_DRIVER_NAME_BUFFER_SIZE );
RtlCopyMemory(
(UINT64)Irp->AssociatedIrp.SystemBuffer + sizeof( MODULE_VALIDATION_FAILURE_HEADER ) + i * sizeof( MODULE_VALIDATION_FAILURE ),
&report,
sizeof( MODULE_VALIDATION_FAILURE ));
RemoveInvalidDriverFromList( head );
}
}
else
{
DEBUG_LOG( "No INVALID drivers found :)" );
}
ExFreePoolWithTag( head, INVALID_DRIVER_LIST_HEAD_POOL );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
return status;
}

58
driver/modules.h Normal file
View file

@ -0,0 +1,58 @@
#ifndef MODULES_H
#define MODULES_H
#include <ntifs.h>
#include <intrin.h>
#define REPORT_MODULE_VALIDATION_FAILURE 60
#define MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT 5
#define MODULE_REPORT_DRIVER_NAME_BUFFER_SIZE 128
typedef struct _MODULE_VALIDATION_FAILURE_HEADER
{
INT module_count;
}MODULE_VALIDATION_FAILURE_HEADER, *PMODULE_VALIDATION_FAILURE_HEADER;
typedef struct _MODULE_VALIDATION_FAILURE
{
INT report_code;
UINT64 driver_base_address;
UINT64 driver_size;
PCHAR driver_name[ 128 ];
}MODULE_VALIDATION_FAILURE, *PMODULE_VALIDATION_FAILURE;
typedef struct _INVALID_DRIVER
{
struct _INVALID_DRIVER* next;
PDRIVER_OBJECT driver;
}INVALID_DRIVER, * PINVALID_DRIVER;
typedef struct _INVALID_DRIVERS_HEAD
{
PINVALID_DRIVER first_entry;
INT count; //keeps track of the number of drivers in the list
}INVALID_DRIVERS_HEAD, * PINVALID_DRIVERS_HEAD;
/* system modules information */
typedef struct _SYSTEM_MODULES
{
PVOID address;
INT module_count;
}SYSTEM_MODULES, * PSYSTEM_MODULES;
NTSTATUS GetSystemModuleInformation(
_Out_ PSYSTEM_MODULES ModuleInformation
);
NTSTATUS HandleValidateDriversIOCTL(
_In_ PIRP Irp
);
#endif

297
driver/nmi.c Normal file
View file

@ -0,0 +1,297 @@
#include "nmi.h"
#include "modules.h"
#include "common.h"
typedef struct _NMI_POOLS
{
PVOID thread_data_pool;
PVOID stack_frames;
PVOID nmi_context;
}NMI_POOLS, * PNMI_POOLS;
PVOID nmi_callback_handle = NULL;
/* Global structure to hold pointers to required memory for the NMI's */
NMI_POOLS nmi_pools = { 0 };
NTSTATUS IsInstructionPointerInInvalidRegion(
_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;
}
NTSTATUS AnalyseNmiData(
_In_ INT NumCores,
_In_ PSYSTEM_MODULES SystemModules,
_In_ PIRP Irp
)
{
if ( !NumCores || !SystemModules )
return STATUS_INVALID_PARAMETER;
for ( INT core = 0; core < NumCores; core++ )
{
PNMI_CONTEXT context = ( PNMI_CONTEXT )( ( uintptr_t )nmi_pools.nmi_context + core * sizeof( NMI_CONTEXT ) );
/* 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 )(
( uintptr_t )nmi_pools.thread_data_pool + core * sizeof( NMI_CALLBACK_DATA ) );
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* )(
( ( uintptr_t )nmi_pools.stack_frames + thread_data->stack_frames_offset + frame * sizeof( PVOID ) ) );
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;
}
BOOLEAN NmiCallback(
_In_ PVOID Context,
_In_ BOOLEAN Handled
)
{
UNREFERENCED_PARAMETER( Handled );
ULONG proc_num = KeGetCurrentProcessorNumber();
PVOID current_thread = KeGetCurrentThread();
NMI_CALLBACK_DATA thread_data = { 0 };
/*
* 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,
STACK_FRAME_POOL_SIZE,
( uintptr_t )nmi_pools.stack_frames + proc_num * STACK_FRAME_POOL_SIZE,
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(
( ( uintptr_t )nmi_pools.thread_data_pool ) + proc_num * sizeof( thread_data ),
&thread_data,
sizeof( thread_data )
);
PNMI_CONTEXT context = ( PNMI_CONTEXT )( ( uintptr_t )Context + proc_num * sizeof( NMI_CONTEXT ) );
context->nmi_callbacks_run += 1;
DEBUG_LOG( "num nmis called: %i from addr: %llx", context->nmi_callbacks_run, ( uintptr_t )context );
return TRUE;
}
NTSTATUS LaunchNonMaskableInterrupt(
_In_ ULONG NumCores
)
{
if ( !NumCores )
return STATUS_INVALID_PARAMETER;
PKAFFINITY_EX ProcAffinityPool = ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( KAFFINITY_EX ), PROC_AFFINITY_POOL );
if ( !ProcAffinityPool )
return STATUS_ABANDONED;
nmi_pools.stack_frames = ExAllocatePool2( POOL_FLAG_NON_PAGED, NumCores * STACK_FRAME_POOL_SIZE, STACK_FRAMES_POOL );
if ( !nmi_pools.stack_frames )
{
ExFreePoolWithTag( ProcAffinityPool, PROC_AFFINITY_POOL );
return STATUS_ABANDONED;
}
nmi_pools.thread_data_pool = ExAllocatePool2( POOL_FLAG_NON_PAGED, NumCores * sizeof( NMI_CALLBACK_DATA ), THREAD_DATA_POOL );
if ( !nmi_pools.thread_data_pool )
{
ExFreePoolWithTag( nmi_pools.stack_frames, STACK_FRAMES_POOL );
ExFreePoolWithTag( ProcAffinityPool, PROC_AFFINITY_POOL );
return STATUS_ABANDONED;
}
LARGE_INTEGER delay = { 0 };
delay.QuadPart -= 100 * 10000;
for ( ULONG core = 0; core < NumCores; core++ )
{
KeInitializeAffinityEx( ProcAffinityPool );
KeAddProcessorAffinityEx( ProcAffinityPool, core );
DEBUG_LOG( "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(
_In_ PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
SYSTEM_MODULES system_modules = { 0 };
ULONG num_cores = KeQueryActiveProcessorCountEx( 0 );
/* Fix annoying visual studio linting error */
RtlZeroMemory( &system_modules, sizeof( SYSTEM_MODULES ) );
RtlZeroMemory( &nmi_pools, sizeof( NMI_POOLS ) );
nmi_pools.nmi_context = ExAllocatePool2( POOL_FLAG_NON_PAGED, num_cores * sizeof( NMI_CONTEXT ), NMI_CONTEXT_POOL );
if ( !nmi_pools.nmi_context )
{
DEBUG_ERROR( "nmi_context ExAllocatePool2 failed" );
return STATUS_ABANDONED;
}
/*
* 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
*/
nmi_callback_handle = KeRegisterNmiCallback( NmiCallback, nmi_pools.nmi_context );
if ( !nmi_callback_handle )
{
DEBUG_ERROR( "KeRegisterNmiCallback failed" );
ExFreePoolWithTag( nmi_pools.nmi_context, NMI_CONTEXT_POOL );
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;
}
status = LaunchNonMaskableInterrupt( num_cores );
if ( !NT_SUCCESS( status ) )
{
DEBUG_ERROR( "Error running NMI callbacks" );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
return status;
}
status = AnalyseNmiData( num_cores, &system_modules, Irp );
if ( !NT_SUCCESS( status ) )
DEBUG_ERROR( "Error analysing nmi data" );
ExFreePoolWithTag( system_modules.address, SYSTEM_MODULES_POOL );
ExFreePoolWithTag( nmi_pools.stack_frames, STACK_FRAMES_POOL );
ExFreePoolWithTag( nmi_pools.thread_data_pool, THREAD_DATA_POOL );
ExFreePoolWithTag( nmi_pools.nmi_context, NMI_CONTEXT_POOL );
KeDeregisterNmiCallback( nmi_callback_handle );
return status;
}

41
driver/nmi.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef NMI_H
#define NMI_H
#include <ntifs.h>
#include <intrin.h>
#define REPORT_NMI_CALLBACK_FAILURE 50
NTSTATUS HandleNmiIOCTL(
_In_ PIRP Irp
);
typedef struct NMI_CALLBACK_FAILURE
{
INT report_code;
INT were_nmis_disabled;
UINT64 kthread_address;
UINT64 invalid_rip;
}NMI_CALLBACK_FAILURE, *PNMI_CALLBACK_FAILURE;
typedef struct _NMI_CONTEXT
{
INT nmi_callbacks_run;
}NMI_CONTEXT, * PNMI_CONTEXT;
typedef struct _NMI_CALLBACK_DATA
{
UINT64 kthread_address;
UINT64 kprocess_address;
UINT64 start_address;
UINT64 stack_limit;
UINT64 stack_base;
uintptr_t stack_frames_offset;
INT num_frames_captured;
UINT64 cr3;
}NMI_CALLBACK_DATA, * PNMI_CALLBACK_DATA;
#endif

View file

@ -1,7 +1,155 @@
#include "driver.h"
#include "../common.h"
kernelmode::Driver::Driver(LPCWSTR DriverName, std::shared_ptr<global::Report> ReportInterface )
{
this->driver_name = DriverName;
this->report_interface = ReportInterface;
}
void kernelmode::Driver::RunNmiCallbacks()
{
BOOLEAN status;
DWORD bytes_returned;
global::report_structures::NMI_CALLBACK_FAILURE report;
status = DeviceIoControl(
this->driver_handle,
IOCCTL_RUN_NMI_CALLBACKS,
NULL,
NULL,
&report,
sizeof( global::report_structures::NMI_CALLBACK_FAILURE ),
&bytes_returned,
( LPOVERLAPPED )NULL
);
if ( status == NULL )
{
LOG_ERROR( "DeviceIoControl failed with status code 0x%x", GetLastError() );
return;
}
if ( bytes_returned == NULL )
{
LOG_INFO( "All threads valid, nmis fine." );
return;
}
/* else, report */
this->report_interface->ReportViolation( &report );
}
void kernelmode::Driver::VerifySystemModules()
{
BOOLEAN status;
DWORD bytes_returned;
PVOID buffer;
SIZE_T buffer_size;
SIZE_T header_size;
global::report_structures::MODULE_VALIDATION_FAILURE_HEADER header;
global::report_structures::MODULE_VALIDATION_FAILURE report;
/*
* allocate enough to report 5 invalid driver objects + header. The reason we use a raw
* pointer here is so we can pass the address to DeviceIoControl. You are not able (atleast
* as far as im concerned) to pass a shared ptr to DeviceIoControl.
*/
header_size = sizeof( global::report_structures::MODULE_VALIDATION_FAILURE_HEADER );
buffer_size = sizeof( global::report_structures::MODULE_VALIDATION_FAILURE ) *
MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT +
header_size;
buffer = malloc( buffer_size );
status = DeviceIoControl(
this->driver_handle,
IOCTL_VALIDATE_DRIVER_OBJECTS,
NULL,
NULL,
buffer,
buffer_size,
&bytes_returned,
( LPOVERLAPPED )NULL
);
if ( status == NULL )
{
LOG_ERROR( "DeviceIoControl failed with status code 0x%x", GetLastError() );
free( buffer );
return;
}
if ( bytes_returned == NULL )
{
LOG_INFO( "All threads valid, nmis fine." );
free( buffer );
return;
}
memcpy( &header, buffer, sizeof( header_size ));
if ( header.module_count == NULL )
{
LOG_ERROR( "weird error with module report" );
free( buffer );
return;
}
/*
* We are splitting up each packet here and passing them on one by one since
* if I am being honest it is just easier in c++ and that way the process
* is streamlined just like all other report packets.
*/
UINT64 base = ( UINT64 )buffer + sizeof( header_size );
for ( int i = 0; i < header.module_count; i++ )
{
memcpy(
&report,
PVOID( base + i * sizeof( global::report_structures::MODULE_VALIDATION_FAILURE ) ),
sizeof( global::report_structures::MODULE_VALIDATION_FAILURE )
);
this->report_interface->ReportViolation( &report );
/* sanity clear just in case ;) */
RtlZeroMemory( &report, sizeof( global::report_structures::MODULE_VALIDATION_FAILURE ) );
}
free( buffer );
}
void kernelmode::Driver::EnableObRegisterCallbacks()
{
}
void kernelmode::Driver::DisableObRegisterCallbacks()
{
}
void kernelmode::Driver::EnableProcessLoadNotifyCallbacks()
{
}
void kernelmode::Driver::DisableProcessLoadNotifyCallbacks()
{
}
void kernelmode::Driver::ValidateKPRCBThreads()
{
}
void kernelmode::Driver::CheckForHypervisor()
{
}
void kernelmode::Driver::VerifySystemModulesIOCTLDispatchHandler()
{
}
void kernelmode::Driver::CheckDriverHeartbeat()
{
}

View file

@ -6,6 +6,9 @@
#include "../threadpool.h"
#include "../report.h"
#define IOCCTL_RUN_NMI_CALLBACKS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2001, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_VALIDATE_DRIVER_OBJECTS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2002, METHOD_BUFFERED, FILE_ANY_ACCESS)
namespace kernelmode
{
class Driver
@ -15,9 +18,19 @@ namespace kernelmode
std::shared_ptr<global::Report> report_interface;
public:
std::shared_ptr<global::ThreadPool> thread_pool;
Driver(LPCWSTR DriverName, std::shared_ptr<global::Report> ReportInterface );
void RunNmiCallbacks();
void VerifySystemModules();
void EnableObRegisterCallbacks();
void DisableObRegisterCallbacks();
void EnableProcessLoadNotifyCallbacks();
void DisableProcessLoadNotifyCallbacks();
void ValidateKPRCBThreads();
void CheckForHypervisor();
void VerifySystemModulesIOCTLDispatchHandler();
void CheckDriverHeartbeat();
/* todo: driver integrity check */
};
}

View file

@ -5,3 +5,13 @@ kernelmode::KManager::KManager( LPCWSTR DriverName, std::shared_ptr<global::Thre
this->driver_interface = std::make_unique<Driver>(DriverName, ReportInterface);
this->thread_pool = ThreadPool;
}
void kernelmode::KManager::RunNmiCallbacks()
{
this->thread_pool->QueueJob( [ this ]() {this->RunNmiCallbacks(); } );
}
void kernelmode::KManager::VerifySystemModules()
{
this->thread_pool->QueueJob( [ this ]() {this->VerifySystemModules(); } );
}

View file

@ -16,6 +16,9 @@ namespace kernelmode
std::shared_ptr<global::ThreadPool> thread_pool;
public:
KManager( LPCWSTR DriverName, std::shared_ptr<global::ThreadPool> ThreadPool, std::shared_ptr<global::Report> ReportInterface);
void RunNmiCallbacks();
void VerifySystemModules();
};
}

View file

@ -25,9 +25,13 @@ DWORD WINAPI Init(HINSTANCE hinstDLL)
std::shared_ptr<global::Report> report_interface = std::make_shared<global::Report>( thread_pool, pipe_name );
usermode::UManager umanager( thread_pool, report_interface );
//kernelmode::KManager kmanager( L"DonnaAC", thread_pool);
umanager.ValidateProcessModules();
umanager.ValidateProcessMemory();
kernelmode::KManager kmanager( L"DonnaAC", thread_pool, report_interface);
kmanager.RunNmiCallbacks();
kmanager.VerifySystemModules();
//umanager.ValidateProcessModules();
//umanager.ValidateProcessMemory();
while ( !GetAsyncKeyState( VK_DELETE ) )
{

View file

@ -9,11 +9,14 @@
#define REPORT_BUFFER_SIZE 1024
#define MAX_SIGNATURE_SIZE 256
#define MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT 5
#define REPORT_CODE_MODULE_VERIFICATION 10
#define REPORT_CODE_START_ADDRESS_VERIFICATION 20
#define REPORT_PAGE_PROTECTION_VERIFICATION 30
#define REPORT_PATTERN_SCAN_FAILURE 40
#define REPORT_NMI_CALLBACK_FAILURE 50
#define REPORT_MODULE_VALIDATION_FAILURE 60
@ -80,6 +83,27 @@ namespace global
INT signature_id;
UINT64 address;
};
struct NMI_CALLBACK_FAILURE
{
INT report_code;
INT were_nmis_disabled;
UINT64 kthread_address;
UINT64 invalid_rip;
};
struct MODULE_VALIDATION_FAILURE_HEADER
{
INT module_count;
};
struct MODULE_VALIDATION_FAILURE
{
INT report_code;
UINT64 driver_base_address;
UINT64 driver_size;
BYTE driver_name[ 128 ];
};
}
}

View file

@ -15,14 +15,14 @@ usermode::UManager::UManager( std::shared_ptr<global::ThreadPool> ThreadPool, st
usermode::UManager::~UManager()
{
/* Wait for our jobs to be finished, then safely stop our pool */
while ( true )
{
if ( this->thread_pool->Busy() == FALSE )
{
this->thread_pool->Stop();
break;
}
}
//while ( true )
//{
// if ( this->thread_pool->Busy() == FALSE )
// {
// this->thread_pool->Stop();
// break;
// }
//}
}
void usermode::UManager::ValidateProcessThreads()