refactor nmi, dpc and some other stuf

This commit is contained in:
lhodges1 2023-12-30 03:20:32 +11:00
parent fc8e1792dd
commit 0a1e01e8e7
20 changed files with 26823 additions and 322 deletions

View file

@ -6,8 +6,9 @@ open source anti cheat (lol) which I made for fun.
- Attached thread detection - Attached thread detection
- Process module .text section integrity checks - Process module .text section integrity checks
- NMI and APC stackwalking - NMI stackwalking via isr iretq
- IPI stackwalking which is a relatively unknown method compared to NMIs and APCs - APC stackwalking via RtlCaptureStackBackTrace
- DPC stackwalking via RtlCaptureStackBackTrace (harder to disable)
- Handle stripping via obj callbacks - Handle stripping via obj callbacks
- Process handle table enumeration - Process handle table enumeration
- System module verification - System module verification
@ -68,6 +69,9 @@ Before we continue, ensure you enable test signing mode as this driver is not si
bcdedit -set TESTSIGNING on bcdedit -set TESTSIGNING on
bcdedit /debug on bcdedit /debug on
``` ```
3. Restart Windows
## building and running the project ## building and running the project
1. Clone the project i.e `git clone git@github.com:donnaskiez/ac.git` 1. Clone the project i.e `git clone git@github.com:donnaskiez/ac.git`
@ -77,16 +81,16 @@ bcdedit /debug on
- `Inf2Cat -> General -> Use Local Time` to `Yes` - `Inf2Cat -> General -> Use Local Time` to `Yes`
- `C/C++ -> Treat Warnings As Errors` to `No` - `C/C++ -> Treat Warnings As Errors` to `No`
- `C/C++ -> Spectre Mitigation` to `Disabled` - `C/C++ -> Spectre Mitigation` to `Disabled`
5. Move the `driver.sys` file located in `ac\x64\Release` into the `Windows\System32\Drivers` directory 5. Move the `driver.sys` file located in `ac\x64\Release - No Server\` into the `Windows\System32\Drivers` directory
- You can rename the driver if you would like - You can rename the driver if you would like
6. Use the [OSR Loader](https://www.osronline.com/article.cfm%5Earticle=157.htm) and select `driver.sys` (or whatever you named it) that you moved to the Windows drivers folder. DO NOT REGISTER THE SERVICE YET. 6. Use the [OSR Loader](https://www.osronline.com/article.cfm%5Earticle=157.htm) and select `driver.sys` (or whatever you named it) that you moved to the Windows drivers folder. *DO NOT REGISTER THE SERVICE YET*.
7. Under `Service Start` select `System`. This is VERY important! 7. Under `Service Start` select `System`. This is VERY important!
8. Click `Register Service`. *Do NOT click* `Start Service`! 8. Click `Register Service`. *Do NOT click* `Start Service`!
9. Restart Windows. 9. Restart Windows.
10. Once restarted, open the program you would like to protect. This could be anything i.e cs2, notepad etc. 10. Once restarted, open the program you would like to protect. This could be anything i.e cs2, notepad etc.
- if you do use a game to test, ensure the games anti-cheat is turned off before testing - if you do use a game to test, ensure the games anti-cheat is turned off before testing
11. Open your dll injector program of choice as administrator (I simply use [Process Hacker](https://processhacker.sourceforge.io/)) 11. Open your dll injector of choice (I simply use [Process Hacker](https://processhacker.sourceforge.io/))
12. Inject the dll found in `ac\x64\Release` named `user.dll` into the target program 12. Inject the dll found in `ac\x64\Release - No Server\` named `user.dll` into the target program
Logs will be printed to both the terminal output and the kernel debugger. See below for configuring kernel debugger output. Logs will be printed to both the terminal output and the kernel debugger. See below for configuring kernel debugger output.

View file

@ -44,6 +44,7 @@
#define INVALID_DRIVER_LIST_HEAD_POOL 'rwar' #define INVALID_DRIVER_LIST_HEAD_POOL 'rwar'
#define INVALID_DRIVER_LIST_ENTRY_POOL 'gaah' #define INVALID_DRIVER_LIST_ENTRY_POOL 'gaah'
#define POOL_TAG_APC 'apcc' #define POOL_TAG_APC 'apcc'
#define POOL_TAG_DPC 'apcc'
#define SYSTEM_MODULES_POOL 'halb' #define SYSTEM_MODULES_POOL 'halb'
#define THREAD_DATA_POOL 'doof' #define THREAD_DATA_POOL 'doof'
#define PROC_AFFINITY_POOL 'eeee' #define PROC_AFFINITY_POOL 'eeee'
@ -119,6 +120,7 @@
#define REPORT_HIDDEN_SYSTEM_THREAD 90 #define REPORT_HIDDEN_SYSTEM_THREAD 90
#define REPORT_ILLEGAL_ATTACH_PROCESS 100 #define REPORT_ILLEGAL_ATTACH_PROCESS 100
#define REPORT_APC_STACKWALK 110 #define REPORT_APC_STACKWALK 110
#define REPORT_DPC_STACKWALK 120
/* /*
* Generic macros that allow you to quickly determine whether * Generic macros that allow you to quickly determine whether
@ -898,23 +900,6 @@ typedef struct _DUMP_HEADER
struct _KDDEBUGGER_DATA64* KdDebuggerDataBlock; struct _KDDEBUGGER_DATA64* KdDebuggerDataBlock;
} DUMP_HEADER, *PDUMP_HEADER; } DUMP_HEADER, *PDUMP_HEADER;
typedef union _DIRECTORY_TABLE_BASE
{
struct
{
UINT64 Ignored0 : 3; /* 2:0 */
UINT64 PageWriteThrough : 1; /* 3 */
UINT64 PageCacheDisable : 1; /* 4 */
UINT64 _Ignored1 : 7; /* 11:5 */
UINT64 PhysicalAddress : 36; /* 47:12 */
UINT64 _Reserved0 : 16; /* 63:48 */
} Bits;
UINT64 BitAddress;
} CR3, DIR_TABLE_BASE;
typedef union _VIRTUAL_MEMORY_ADDRESS typedef union _VIRTUAL_MEMORY_ADDRESS
{ {
struct struct
@ -1411,4 +1396,32 @@ C_ASSERT(FIELD_OFFSET(DUMP_HEADER, KdDebuggerDataBlock) == 0x80);
# define DUMP_BLOCK_SIZE 0x40000 # define DUMP_BLOCK_SIZE 0x40000
#endif #endif
#define IA32_GS_BASE 0xc0000101
#define KPCR_TSS_BASE_OFFSET 0x008
#define TSS_IST_OFFSET 0x01c
#define WINDOWS_USERMODE_MAX_ADDRESS 0x00007FFFFFFFFFFF
typedef struct _MACHINE_FRAME
{
UINT64 rip;
UINT64 cs;
UINT64 eflags;
UINT64 rsp;
UINT64 ss;
} MACHINE_FRAME, *PMACHINE_FRAME;
NTKERNELAPI
_IRQL_requires_max_(APC_LEVEL)
_IRQL_requires_min_(PASSIVE_LEVEL)
_IRQL_requires_same_
VOID
KeGenericCallDpc(_In_ PKDEFERRED_ROUTINE Routine, _In_opt_ PVOID Context);
NTKERNELAPI
_IRQL_requires_(DISPATCH_LEVEL)
_IRQL_requires_same_
VOID
KeSignalCallDpcDone(_In_ PVOID SystemArgument1);
#endif #endif

View file

@ -1416,15 +1416,6 @@ DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
return STATUS_FAILED_DRIVER_ENTRY; return STATUS_FAILED_DRIVER_ENTRY;
} }
LPCSTR driver_name = NULL;
GetDriverName(&driver_name);
DEBUG_VERBOSE("Driver name: %s", driver_name);
// ValidateSystemModules();
// ValidateNtoskrnl();
// LaunchInterProcessInterrupt(NULL);
// EnumerateBigPoolAllocations();
DEBUG_VERBOSE("Driver Entry Complete."); DEBUG_VERBOSE("Driver Entry Complete.");
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }

View file

@ -185,7 +185,4 @@ _Releases_lock_(_Lock_kind_mutex_)
VOID VOID
GetDriverSymbolicLink(_Out_ PUNICODE_STRING DeviceSymbolicLink); GetDriverSymbolicLink(_Out_ PUNICODE_STRING DeviceSymbolicLink);
NTSTATUS
SelfReferenceDriver();
#endif #endif

View file

@ -34,6 +34,7 @@
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">x64</Platform> <Platform Condition="'$(Platform)' == ''">x64</Platform>
<RootNamespace>driver</RootNamespace> <RootNamespace>driver</RootNamespace>
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
@ -113,6 +114,7 @@
<RunCodeAnalysis>true</RunCodeAnalysis> <RunCodeAnalysis>true</RunCodeAnalysis>
<EnableClangTidyCodeAnalysis>false</EnableClangTidyCodeAnalysis> <EnableClangTidyCodeAnalysis>false</EnableClangTidyCodeAnalysis>
<EnableMicrosoftCodeAnalysis>false</EnableMicrosoftCodeAnalysis> <EnableMicrosoftCodeAnalysis>false</EnableMicrosoftCodeAnalysis>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
@ -201,6 +203,7 @@
<ClInclude Include="common.h" /> <ClInclude Include="common.h" />
<ClInclude Include="driver.h" /> <ClInclude Include="driver.h" />
<ClInclude Include="hv.h" /> <ClInclude Include="hv.h" />
<ClInclude Include="ia32.h" />
<ClInclude Include="integrity.h" /> <ClInclude Include="integrity.h" />
<ClInclude Include="ioctl.h" /> <ClInclude Include="ioctl.h" />
<ClInclude Include="modules.h" /> <ClInclude Include="modules.h" />

View file

@ -83,6 +83,9 @@
<ClInclude Include="thread.h"> <ClInclude Include="thread.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="ia32.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<MASM Include="asm.asm"> <MASM Include="asm.asm">

26454
driver/ia32.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -1241,7 +1241,7 @@ GetHardDiskDriveSerialNumber(_Inout_ PVOID ConfigDrive0Serial, _In_ SIZE_T Confi
RtlCopyMemory(ConfigDrive0Serial, serial_number, serial_length); RtlCopyMemory(ConfigDrive0Serial, serial_number, serial_length);
} }
DEBUG_INFO("Successfully retrieved hard disk serial number."); DEBUG_VERBOSE("Successfully retrieved hard disk serial number.");
end: end:
if (handle) if (handle)

View file

@ -54,7 +54,7 @@ DispatchApcOperation(_In_ PAPC_OPERATION_ID Operation);
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20017, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20017, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_CHECK_FOR_EPT_HOOK \ #define IOCTL_CHECK_FOR_EPT_HOOK \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20018, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20018, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_LAUNCH_IPI_INTERRUPT \ #define IOCTL_LAUNCH_DPC_STACKWALK \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20019, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20019, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_VALIDATE_SYSTEM_MODULES \ #define IOCTL_VALIDATE_SYSTEM_MODULES \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20020, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20020, METHOD_BUFFERED, FILE_ANY_ACCESS)
@ -446,17 +446,6 @@ DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
break; break;
case IOCTL_LAUNCH_IPI_INTERRUPT:
DEBUG_INFO("IOCTL_LAUNCH_IPI_INTERRUPT Received");
status = LaunchInterProcessInterrupt(Irp);
if (!NT_SUCCESS(status))
DEBUG_ERROR("LaunchInterProcessInterrupt failed with status %x", status);
break;
case IOCTL_VALIDATE_SYSTEM_MODULES: case IOCTL_VALIDATE_SYSTEM_MODULES:
DEBUG_INFO("IOCTL_VALIDATE_SYSTEM_MODULES Received"); DEBUG_INFO("IOCTL_VALIDATE_SYSTEM_MODULES Received");
@ -472,6 +461,19 @@ DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
break; break;
case IOCTL_LAUNCH_DPC_STACKWALK:
DEBUG_INFO("IOCTL_LAUNCH_DPC_STACKWALK Received");
status = DispatchStackwalkToEachCpuViaDpc();
if (!NT_SUCCESS(status))
DEBUG_ERROR("DispatchStackwalkToEachCpuViaDpc failed with status %x",
status);
break;
default: default:
DEBUG_WARNING("Invalid IOCTL passed to driver: %lx", DEBUG_WARNING("Invalid IOCTL passed to driver: %lx",
stack_location->Parameters.DeviceIoControl.IoControlCode); stack_location->Parameters.DeviceIoControl.IoControlCode);

View file

@ -3,6 +3,7 @@
#include "callbacks.h" #include "callbacks.h"
#include "driver.h" #include "driver.h"
#include "ioctl.h" #include "ioctl.h"
#include "ia32.h"
#define WHITELISTED_MODULE_TAG 'whte' #define WHITELISTED_MODULE_TAG 'whte'
@ -58,12 +59,6 @@ typedef struct _NMI_POOLS
} NMI_POOLS, *PNMI_POOLS; } NMI_POOLS, *PNMI_POOLS;
typedef struct _NMI_CORE_CONTEXT
{
INT nmi_callbacks_run;
} NMI_CORE_CONTEXT, *PNMI_CORE_CONTEXT;
typedef struct _MODULE_VALIDATION_FAILURE_HEADER typedef struct _MODULE_VALIDATION_FAILURE_HEADER
{ {
INT module_count; INT module_count;
@ -72,26 +67,14 @@ typedef struct _MODULE_VALIDATION_FAILURE_HEADER
typedef struct _NMI_CONTEXT typedef struct _NMI_CONTEXT
{ {
PVOID thread_data_pool; UINT64 interrupted_rip;
PVOID stack_frames; UINT64 interrupted_rsp;
PVOID nmi_core_context; UINT64 kthread;
INT core_count; UINT32 callback_count;
BOOLEAN user_thread;
} NMI_CONTEXT, *PNMI_CONTEXT; } 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;
typedef struct _INVALID_DRIVER typedef struct _INVALID_DRIVER
{ {
struct _INVALID_DRIVER* next; struct _INVALID_DRIVER* next;
@ -609,8 +592,9 @@ ValidateDriverObjects(_In_ PSYSTEM_MODULES SystemModules,
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
DEBUG_ERROR("ValidateDriverIOCTLDispatchRegion failed with status %x", DEBUG_ERROR(
status); "ValidateDriverIOCTLDispatchRegion failed with status %x",
status);
goto end; goto end;
} }
@ -781,6 +765,10 @@ end:
return status; return status;
} }
/*
* TODO: this probably doesnt need to return an NTSTATUS, we can just return a boolean and remove
* the out variable.
*/
NTSTATUS NTSTATUS
IsInstructionPointerInInvalidRegion(_In_ UINT64 RIP, IsInstructionPointerInInvalidRegion(_In_ UINT64 RIP,
_In_ PSYSTEM_MODULES SystemModules, _In_ PSYSTEM_MODULES SystemModules,
@ -813,27 +801,24 @@ IsInstructionPointerInInvalidRegion(_In_ UINT64 RIP,
} }
/* /*
* todo: rename this to analyse stackwalk or something * todo: rename this to analyse stackwalk or something
*/ */
STATIC STATIC
NTSTATUS NTSTATUS
AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules, _Inout_ PIRP Irp) AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules, _Inout_ PIRP Irp)
{ {
PAGED_CODE(); PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL;
BOOLEAN flag = FALSE;
if (!NmiContext || !SystemModules) if (!NmiContext || !SystemModules)
return STATUS_INVALID_PARAMETER; return STATUS_INVALID_PARAMETER;
NTSTATUS status = STATUS_UNSUCCESSFUL; for (INT core = 0; core < KeQueryActiveProcessorCount(0); core++)
for (INT core = 0; core < NmiContext->core_count; core++)
{ {
PNMI_CORE_CONTEXT context =
(PNMI_CORE_CONTEXT)((uintptr_t)NmiContext->nmi_core_context +
core * sizeof(NMI_CORE_CONTEXT));
/* Make sure our NMIs were run */ /* Make sure our NMIs were run */
if (!context->nmi_callbacks_run) if (!NmiContext[core].callback_count)
{ {
NTSTATUS status = NTSTATUS status =
ValidateIrpOutputBuffer(Irp, sizeof(NMI_CALLBACK_FAILURE)); ValidateIrpOutputBuffer(Irp, sizeof(NMI_CALLBACK_FAILURE));
@ -842,7 +827,7 @@ AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules,
{ {
DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x", DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x",
status); status);
return status; continue;
} }
NMI_CALLBACK_FAILURE report = {0}; NMI_CALLBACK_FAILURE report = {0};
@ -859,68 +844,56 @@ AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules,
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
PNMI_CALLBACK_DATA thread_data = DEBUG_VERBOSE("Analysing Nmi Data for: cpu number: %i callback count: %lx",
(PNMI_CALLBACK_DATA)((uintptr_t)NmiContext->thread_data_pool +
core * sizeof(NMI_CALLBACK_DATA));
DEBUG_VERBOSE("Analysing Nmi Data for: cpu number: %i callback count: %i",
core, core,
context->nmi_callbacks_run); NmiContext[core].callback_count);
/* Walk the stack */ if (NmiContext[core].user_thread)
for (INT frame = 0; frame < thread_data->num_frames_captured; frame++) continue;
status = IsInstructionPointerInInvalidRegion(
NmiContext[core].interrupted_rip, SystemModules, &flag);
if (!NT_SUCCESS(status))
{ {
BOOLEAN flag = TRUE; DEBUG_ERROR("IsInstructionPointerInInvalidRegion failed with status %x",
DWORD64 stack_frame = status);
*(DWORD64*)(((uintptr_t)NmiContext->stack_frames + continue;
thread_data->stack_frames_offset + frame * sizeof(PVOID))); }
status = if (!flag)
IsInstructionPointerInInvalidRegion(stack_frame, SystemModules, &flag); {
status = ValidateIrpOutputBuffer(Irp, sizeof(NMI_CALLBACK_FAILURE));
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
DEBUG_ERROR( DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x",
"IsInstructionPointerInInvalidRegion failed with status %x", status);
status); return status;
continue;
} }
if (flag == FALSE) /*
{ * Note: for now, we only handle 1 report at a time so we stop the
status = ValidateIrpOutputBuffer(Irp, sizeof(NMI_CALLBACK_FAILURE)); * 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.
*/
if (!NT_SUCCESS(status)) NMI_CALLBACK_FAILURE report = {0};
{ report.report_code = REPORT_NMI_CALLBACK_FAILURE;
DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x", report.kthread_address = NmiContext[core].kthread;
status); report.invalid_rip = NmiContext[core].interrupted_rip;
return status; report.were_nmis_disabled = FALSE;
}
/* Irp->IoStatus.Information = sizeof(NMI_CALLBACK_FAILURE);
* 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}; RtlCopyMemory(
report.report_code = REPORT_NMI_CALLBACK_FAILURE; Irp->AssociatedIrp.SystemBuffer, &report, sizeof(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); return STATUS_SUCCESS;
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer,
&report,
sizeof(NMI_CALLBACK_FAILURE));
return STATUS_SUCCESS;
}
} }
} }
@ -934,99 +907,67 @@ NmiCallback(_Inout_opt_ PVOID Context, _In_ BOOLEAN Handled)
{ {
UNREFERENCED_PARAMETER(Handled); UNREFERENCED_PARAMETER(Handled);
PKTHREAD current_thread = KeGetCurrentThread(); PNMI_CONTEXT nmi_context = (PNMI_CONTEXT)Context;
NMI_CALLBACK_DATA thread_data = {0}; ULONG proc_num = KeGetCurrentProcessorNumber();
PNMI_CONTEXT nmi_context = (PNMI_CONTEXT)Context; UINT64 kpcr = 0;
ULONG proc_num = KeGetCurrentProcessorNumber(); TASK_STATE_SEGMENT_64* tss = NULL;
PMACHINE_FRAME machine_frame = NULL;
if (!nmi_context)
return TRUE;
/* /*
* Cannot allocate pool in this function as it runs at IRQL >= dispatch level * To find the IRETQ frame (MACHINE_FRAME) we need to find the top of the NMI ISR stack.
* so ive just allocated a global pool with size equal to 0x200 * num_procs * 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.
*/ */
INT num_frames_captured = RtlCaptureStackBackTrace(NULL, kpcr = __readmsr(IA32_GS_BASE);
STACK_FRAME_POOL_SIZE / sizeof(UINT64), tss = *(TASK_STATE_SEGMENT_64**)(kpcr + KPCR_TSS_BASE_OFFSET);
(uintptr_t)nmi_context->stack_frames + machine_frame = tss->Ist3 - sizeof(MACHINE_FRAME);
proc_num * STACK_FRAME_POOL_SIZE,
NULL);
/* if (machine_frame->rip <= WINDOWS_USERMODE_MAX_ADDRESS)
* This function is run in the context of the interrupted thread hence we can nmi_context[proc_num].user_thread = TRUE;
* 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_context->thread_data_pool) + proc_num * sizeof(thread_data), nmi_context[proc_num].interrupted_rip = machine_frame->rip;
&thread_data, nmi_context[proc_num].interrupted_rsp = machine_frame->rsp;
sizeof(thread_data)); nmi_context[proc_num].kthread = PsGetCurrentThread();
nmi_context[proc_num].callback_count += 1;
PNMI_CORE_CONTEXT core_context = DEBUG_VERBOSE(
(PNMI_CORE_CONTEXT)((uintptr_t)nmi_context->nmi_core_context + "[NMI CALLBACK]: Core Number: %lx, Interrupted RIP: %llx, Interrupted RSP: %llx",
proc_num * sizeof(NMI_CORE_CONTEXT)); proc_num,
machine_frame->rip,
core_context->nmi_callbacks_run += 1; machine_frame->rsp);
return TRUE; return TRUE;
} }
#define NMI_DELAY_TIME 100 * 10000 #define NMI_DELAY_TIME 200 * 10000
STATIC STATIC
NTSTATUS NTSTATUS
LaunchNonMaskableInterrupt(_Inout_ PNMI_CONTEXT NmiContext) LaunchNonMaskableInterrupt()
{ {
PAGED_CODE(); PAGED_CODE();
if (!NmiContext)
return STATUS_INVALID_PARAMETER;
PKAFFINITY_EX ProcAffinityPool = PKAFFINITY_EX ProcAffinityPool =
ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(KAFFINITY_EX), PROC_AFFINITY_POOL); ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(KAFFINITY_EX), PROC_AFFINITY_POOL);
if (!ProcAffinityPool) if (!ProcAffinityPool)
return STATUS_MEMORY_NOT_ALLOCATED; return STATUS_MEMORY_NOT_ALLOCATED;
NmiContext->stack_frames = ExAllocatePool2(
POOL_FLAG_NON_PAGED, NmiContext->core_count * STACK_FRAME_POOL_SIZE, STACK_FRAMES_POOL);
if (!NmiContext->stack_frames)
{
ExFreePoolWithTag(ProcAffinityPool, PROC_AFFINITY_POOL);
return STATUS_MEMORY_NOT_ALLOCATED;
}
NmiContext->thread_data_pool =
ExAllocatePool2(POOL_FLAG_NON_PAGED,
NmiContext->core_count * sizeof(NMI_CALLBACK_DATA),
THREAD_DATA_POOL);
if (!NmiContext->thread_data_pool)
{
ExFreePoolWithTag(NmiContext->stack_frames, STACK_FRAMES_POOL);
ExFreePoolWithTag(ProcAffinityPool, PROC_AFFINITY_POOL);
return STATUS_MEMORY_NOT_ALLOCATED;
}
LARGE_INTEGER delay = {0}; LARGE_INTEGER delay = {0};
delay.QuadPart -= NMI_DELAY_TIME; delay.QuadPart -= NMI_DELAY_TIME;
for (ULONG core = 0; core < NmiContext->core_count; core++) for (ULONG core = 0; core < KeQueryActiveProcessorCount(0); core++)
{ {
KeInitializeAffinityEx(ProcAffinityPool); KeInitializeAffinityEx(ProcAffinityPool);
KeAddProcessorAffinityEx(ProcAffinityPool, core); KeAddProcessorAffinityEx(ProcAffinityPool, core);
DEBUG_VERBOSE("Sending NMI");
HalSendNMI(ProcAffinityPool); HalSendNMI(ProcAffinityPool);
/* /*
@ -1047,29 +988,30 @@ HandleNmiIOCTL(_Inout_ PIRP Irp)
PAGED_CODE(); PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL; NTSTATUS status = STATUS_UNSUCCESSFUL;
SYSTEM_MODULES system_modules = {0};
NMI_CONTEXT nmi_context = {0};
PVOID callback_handle = NULL; PVOID callback_handle = NULL;
SYSTEM_MODULES system_modules = {0};
PNMI_CONTEXT nmi_context = NULL;
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);
if (!nmi_context.nmi_core_context)
nmi_context = ExAllocatePool2(POOL_FLAG_NON_PAGED,
KeQueryActiveProcessorCount(0) * sizeof(NMI_CONTEXT),
NMI_CONTEXT_POOL);
if (!nmi_context)
return STATUS_MEMORY_NOT_ALLOCATED; return STATUS_MEMORY_NOT_ALLOCATED;
/* /*
* We want to register and unregister our callback each time so it becomes harder * 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 * for people to hook our callback and get up to some funny business
*/ */
callback_handle = KeRegisterNmiCallback(NmiCallback, &nmi_context); callback_handle = KeRegisterNmiCallback(NmiCallback, nmi_context);
if (!callback_handle) if (!callback_handle)
{ {
DEBUG_ERROR("KeRegisterNmiCallback failed with no status."); DEBUG_ERROR("KeRegisterNmiCallback failed with no status.");
ExFreePoolWithTag(nmi_context.nmi_core_context, NMI_CONTEXT_POOL); ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
return STATUS_UNSUCCESSFUL; return STATUS_UNSUCCESSFUL;
} }
@ -1081,37 +1023,30 @@ HandleNmiIOCTL(_Inout_ PIRP Irp)
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
DEBUG_ERROR("GetSystemModuleInformation failed with status %x", status); KeDeregisterNmiCallback(callback_handle);
ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
DEBUG_ERROR("Error retriving system module information");
return status; return status;
} }
status = LaunchNonMaskableInterrupt(&nmi_context);
status = LaunchNonMaskableInterrupt();
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
DEBUG_ERROR("LaunchNonMaskableInterrupt failed with status %x", status); DEBUG_ERROR("Error running NMI callbacks");
KeDeregisterNmiCallback(callback_handle);
if (system_modules.address) ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL); ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
return status; return status;
} }
status = AnalyseNmiData(&nmi_context, &system_modules, Irp);
status = AnalyseNmiData(nmi_context, &system_modules, Irp);
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
DEBUG_ERROR("AnalyseNmiData failed with status %x", status); DEBUG_ERROR("Error analysing nmi data");
if (system_modules.address)
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
if (nmi_context.nmi_core_context)
ExFreePoolWithTag(nmi_context.nmi_core_context, NMI_CONTEXT_POOL);
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);
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL);
ExFreePoolWithTag(nmi_context, NMI_CONTEXT_POOL);
KeDeregisterNmiCallback(callback_handle); KeDeregisterNmiCallback(callback_handle);
return status; return status;
@ -1132,8 +1067,8 @@ ApcRundownRoutine(_In_ PRKAPC Apc)
} }
/* /*
* The KernelRoutine is executed in kernel mode at APC_LEVEL before the APC is delivered. This * The KernelRoutine is executed in kernel mode at APC_LEVEL before the APC is delivered.
* is also where we want to free our APC object. * This is also where we want to free our APC object.
*/ */
_IRQL_requires_max_(APC_LEVEL) _IRQL_requires_max_(APC_LEVEL)
STATIC STATIC
@ -1169,7 +1104,7 @@ ApcKernelRoutine(_In_ PRKAPC Apc,
frames_captured = frames_captured =
RtlCaptureStackBackTrace(NULL, STACK_FRAME_POOL_SIZE / sizeof(UINT64), buffer, NULL); RtlCaptureStackBackTrace(NULL, STACK_FRAME_POOL_SIZE / sizeof(UINT64), buffer, NULL);
if (frames_captured == NULL) if (!frames_captured)
goto free; goto free;
for (INT index = 0; index < frames_captured; index++) for (INT index = 0; index < frames_captured; index++)
@ -1177,8 +1112,8 @@ ApcKernelRoutine(_In_ PRKAPC Apc,
stack_frame = *(UINT64*)((UINT64)buffer + index * sizeof(UINT64)); stack_frame = *(UINT64*)((UINT64)buffer + index * sizeof(UINT64));
/* /*
* Apc->NormalContext holds the address of our context data structure that we passed * Apc->NormalContext holds the address of our context data structure that
* into KeInitializeApc as the last argument. * we passed into KeInitializeApc as the last argument.
*/ */
status = IsInstructionPointerInInvalidRegion(stack_frame, context->modules, &flag); status = IsInstructionPointerInInvalidRegion(stack_frame, context->modules, &flag);
@ -1285,16 +1220,16 @@ ValidateThreadViaKernelApcCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
return; return;
DEBUG_VERBOSE("Validating thread: %llx, process name: %s via kernel APC stackwalk.", DEBUG_VERBOSE("Validating thread: %llx, process name: %s via kernel APC stackwalk.",
ThreadListEntry->thread, ThreadListEntry->thread,
process_name); process_name);
if (ThreadListEntry->thread == KeGetCurrentThread() || !ThreadListEntry->thread) if (ThreadListEntry->thread == KeGetCurrentThread() || !ThreadListEntry->thread)
return; return;
/* /*
* Its possible to set the KThread->ApcQueueable flag to false ensuring that no APCs can be * Its possible to set the KThread->ApcQueueable flag to false ensuring that no APCs
* queued to the thread, as KeInsertQueueApc will check this flag before queueing an APC so * can be queued to the thread, as KeInsertQueueApc will check this flag before
* lets make sure we flip this before before queueing ours. Since we filter out any system * queueing an APC so lets make sure we flip this before before queueing ours. Since
* threads this should be fine... c: * we filter out any system threads this should be fine... c:
*/ */
misc_flags = (PLONG)((UINT64)ThreadListEntry->thread + KTHREAD_MISC_FLAGS_OFFSET); misc_flags = (PLONG)((UINT64)ThreadListEntry->thread + KTHREAD_MISC_FLAGS_OFFSET);
previous_mode = (PCHAR)((UINT64)ThreadListEntry->thread + KTHREAD_PREVIOUS_MODE_OFFSET); previous_mode = (PCHAR)((UINT64)ThreadListEntry->thread + KTHREAD_PREVIOUS_MODE_OFFSET);
@ -1310,8 +1245,8 @@ ValidateThreadViaKernelApcCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
ThreadListEntry->thread, KTHREAD_MISC_FLAGS_APC_QUEUEABLE, TRUE); ThreadListEntry->thread, KTHREAD_MISC_FLAGS_APC_QUEUEABLE, TRUE);
/* /*
* force thread into an alertable state, noting that this does not guarantee that our APC * force thread into an alertable state, noting that this does not guarantee that
* will be run. * our APC will be run.
*/ */
if (*misc_flags >> KTHREAD_MISC_FLAGS_ALERTABLE == FALSE) if (*misc_flags >> KTHREAD_MISC_FLAGS_ALERTABLE == FALSE)
FlipKThreadMiscFlagsFlag( FlipKThreadMiscFlagsFlag(
@ -1348,9 +1283,9 @@ ValidateThreadViaKernelApcCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
/* /*
* Since NMIs are only executed on the thread that is running on each logical core, it makes * 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 * sense to make use of APCs that, while can be masked off, provide us to easily issue a
* routine to threads we want a stack trace of. Hence by utilising both APCs and NMIs we get * callback routine to threads we want a stack trace of. Hence by utilising both APCs and
* excellent coverage of the entire system. * NMIs we get excellent coverage of the entire system.
*/ */
NTSTATUS NTSTATUS
ValidateThreadsViaKernelApc() ValidateThreadsViaKernelApc()
@ -1421,58 +1356,122 @@ FreeApcStackwalkApcContextInformation(_Inout_ PAPC_STACKWALK_CONTEXT Context)
ExFreePoolWithTag(Context->modules, POOL_TAG_APC); 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);
}
}
}
}
/* /*
* Since NMI evasion methods are becoming commonplace, we can use interprocess * Lets use DPCs as another form of stackwalking rather then inter-process interrupts
* interrupts. For now i am just using the same nmi methods. To accomplish this * because DPCs run at IRQL = DISPATCH_LEVEL, allowing us to use functions such as
* we can use KeIpiGenericCall, which runs a specified routine on all processors * RtlCaptureStackBackTrace whereas IPIs run at IRQL = IPI_LEVEL. DPCs are also harder
* simultaneously. The callback routine runs at IRQL IPI_LEVEL which is > DIRQL. * to mask compared to APCs which can be masked with the flip of a bit in the KTHREAD
* structure.
*/ */
NTSTATUS NTSTATUS
LaunchInterProcessInterrupt(_In_ PIRP Irp) DispatchStackwalkToEachCpuViaDpc()
{ {
PAGED_CODE(); NTSTATUS status = STATUS_UNSUCCESSFUL;
PDPC_CONTEXT context = NULL;
SYSTEM_MODULES modules = {0};
NTSTATUS status = STATUS_UNSUCCESSFUL; context = ExAllocatePool2(POOL_FLAG_NON_PAGED,
SYSTEM_MODULES system_modules = {0}; KeQueryActiveProcessorCount(0) * sizeof(DPC_CONTEXT),
NMI_CONTEXT ipi_context = {0}; POOL_TAG_DPC);
PVOID callback_handle = NULL;
DEBUG_VERBOSE("Launching Inter Process Interrupt"); if (!context)
ipi_context.core_count = KeQueryActiveProcessorCountEx(0);
ipi_context.nmi_core_context =
ExAllocatePool2(POOL_FLAG_NON_PAGED,
ipi_context.core_count * sizeof(NMI_CORE_CONTEXT),
NMI_CONTEXT_POOL);
if (!ipi_context.nmi_core_context)
return STATUS_MEMORY_NOT_ALLOCATED; return STATUS_MEMORY_NOT_ALLOCATED;
ipi_context.stack_frames = ExAllocatePool2( status = GetSystemModuleInformation(&modules);
POOL_FLAG_NON_PAGED, ipi_context.core_count * STACK_FRAME_POOL_SIZE, STACK_FRAMES_POOL);
if (!ipi_context.stack_frames)
{
status = STATUS_MEMORY_NOT_ALLOCATED;
goto end;
}
ipi_context.thread_data_pool =
ExAllocatePool2(POOL_FLAG_NON_PAGED,
ipi_context.core_count * sizeof(NMI_CALLBACK_DATA),
THREAD_DATA_POOL);
if (!ipi_context.thread_data_pool)
{
status = STATUS_MEMORY_NOT_ALLOCATED;
goto end;
}
/*
* We query the system modules each time since they can potentially
* change at any time
*/
status = GetSystemModuleInformation(&system_modules);
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
@ -1480,29 +1479,29 @@ LaunchInterProcessInterrupt(_In_ PIRP Irp)
goto end; goto end;
} }
KeIpiGenericCall(NmiCallback, &ipi_context); /* 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))
* since the routines are run simultaneously, once we've reached here we can be sure YieldProcessor();
* all routines have run.
*/ status = ValidateDpcCapturedStack(&modules, context);
status = AnalyseNmiData(&ipi_context, &system_modules, Irp);
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
DEBUG_ERROR("AnalyseNmiData failed with status %x", status); {
DEBUG_ERROR("ValidateDpcCapturedStack failed with status %x", status);
goto end;
}
DEBUG_VERBOSE("Finished validating cores via dpc");
end: end:
if (system_modules.address) if (modules.address)
ExFreePoolWithTag(system_modules.address, SYSTEM_MODULES_POOL); ExFreePoolWithTag(modules.address, SYSTEM_MODULES_POOL);
if (context)
if (ipi_context.nmi_core_context) ExFreePoolWithTag(context, POOL_TAG_DPC);
ExFreePoolWithTag(ipi_context.nmi_core_context, NMI_CONTEXT_POOL);
if (ipi_context.stack_frames)
ExFreePoolWithTag(ipi_context.stack_frames, STACK_FRAMES_POOL);
if (ipi_context.thread_data_pool)
ExFreePoolWithTag(ipi_context.thread_data_pool, THREAD_DATA_POOL);
return status; return status;
} }

View file

@ -16,6 +16,17 @@ typedef struct NMI_CALLBACK_FAILURE
} NMI_CALLBACK_FAILURE, *PNMI_CALLBACK_FAILURE; } NMI_CALLBACK_FAILURE, *PNMI_CALLBACK_FAILURE;
#define APC_STACKWALK_BUFFER_SIZE 4096
typedef struct _DPC_STACKWALK_REPORT
{
UINT32 report_code;
UINT64 kthread_address;
UINT64 invalid_rip;
CHAR driver[APC_STACKWALK_BUFFER_SIZE];
} DPC_STACKWALK_REPORT, *PDPC_STACKWALK_REPORT;
typedef struct _MODULE_VALIDATION_FAILURE typedef struct _MODULE_VALIDATION_FAILURE
{ {
INT report_code; INT report_code;
@ -26,8 +37,6 @@ typedef struct _MODULE_VALIDATION_FAILURE
} MODULE_VALIDATION_FAILURE, *PMODULE_VALIDATION_FAILURE; } MODULE_VALIDATION_FAILURE, *PMODULE_VALIDATION_FAILURE;
#define APC_STACKWALK_BUFFER_SIZE 4096
typedef struct _APC_STACKWALK_REPORT typedef struct _APC_STACKWALK_REPORT
{ {
INT report_code; INT report_code;
@ -99,6 +108,6 @@ BOOLEAN
FlipKThreadMiscFlagsFlag(_In_ PKTHREAD Thread, _In_ ULONG FlagIndex, _In_ BOOLEAN NewValue); FlipKThreadMiscFlagsFlag(_In_ PKTHREAD Thread, _In_ ULONG FlagIndex, _In_ BOOLEAN NewValue);
NTSTATUS NTSTATUS
LaunchInterProcessInterrupt(_In_ PIRP Irp); DispatchStackwalkToEachCpuViaDpc();
#endif #endif

View file

@ -4,6 +4,7 @@
#include "callbacks.h" #include "callbacks.h"
#include "queue.h" #include "queue.h"
#include "ia32.h"
#define PAGE_BASE_SIZE 0x1000 #define PAGE_BASE_SIZE 0x1000
#define POOL_TAG_SIZE 0x004 #define POOL_TAG_SIZE 0x004
@ -444,9 +445,9 @@ WalkKernelPageTables(_In_ PPROCESS_SCAN_CONTEXT Context)
return; return;
} }
cr3.BitAddress = __readcr3(); cr3.AsUInt = __readcr3();
physical.QuadPart = cr3.Bits.PhysicalAddress << PAGE_4KB_SHIFT; physical.QuadPart = cr3.AddressOfPageDirectory << PAGE_4KB_SHIFT;
pml4_base.BitAddress = MmGetVirtualForPhysical(physical); pml4_base.BitAddress = MmGetVirtualForPhysical(physical);

View file

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

View file

@ -34,6 +34,7 @@
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">x64</Platform> <Platform Condition="'$(Platform)' == ''">x64</Platform>
<RootNamespace>testdrv</RootNamespace> <RootNamespace>testdrv</RootNamespace>
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
@ -104,6 +105,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release - No Server|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release - No Server|x64'">
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime> <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>

View file

@ -26,6 +26,7 @@
#define REPORT_HIDDEN_SYSTEM_THREAD 90 #define REPORT_HIDDEN_SYSTEM_THREAD 90
#define REPORT_ILLEGAL_ATTACH_PROCESS 100 #define REPORT_ILLEGAL_ATTACH_PROCESS 100
#define REPORT_APC_STACKWALK 110 #define REPORT_APC_STACKWALK 110
#define REPORT_DPC_STACKWALK 120
#define TEST_STEAM_64_ID 123456789; #define TEST_STEAM_64_ID 123456789;
@ -205,6 +206,14 @@ struct APC_STACKWALK_REPORT
UINT64 invalid_rip; UINT64 invalid_rip;
CHAR driver[4096]; CHAR driver[4096];
}; };
struct DPC_STACKWALK_REPORT
{
UINT32 report_code;
UINT64 kthread_address;
UINT64 invalid_rip;
CHAR driver[4096];
};
} }
} }

View file

@ -240,6 +240,10 @@ kernelmode::Driver::QueryReportQueue()
ReportTypeFromReportQueue<HIDDEN_SYSTEM_THREAD_REPORT>( ReportTypeFromReportQueue<HIDDEN_SYSTEM_THREAD_REPORT>(
buffer, &total_size, &hidden_report); buffer, &total_size, &hidden_report);
break; break;
case REPORT_DPC_STACKWALK:
ReportTypeFromReportQueue<DPC_STACKWALK_REPORT>(
buffer, &total_size, &hidden_report);
break;
default: break; default: break;
} }
} }
@ -521,15 +525,15 @@ kernelmode::Driver::CheckForEptHooks()
} }
VOID VOID
kernelmode::Driver::LaunchIpiInterrupt() kernelmode::Driver::StackwalkThreadsViaDpc()
{ {
BOOLEAN status = FALSE; BOOLEAN status = FALSE;
status = DeviceIoControl( status = DeviceIoControl(
this->driver_handle, IOCTL_LAUNCH_IPI_INTERRUPT, NULL, NULL, NULL, NULL, NULL, NULL); this->driver_handle, IOCTL_LAUNCH_DPC_STACKWALK, NULL, NULL, NULL, NULL, NULL, NULL);
if (status == NULL) if (status == NULL)
LOG_ERROR("failed to launch ipi interrupt %x", GetLastError()); LOG_ERROR("failed to stackwalk threads via dpc %x", GetLastError());
} }
VOID VOID

View file

@ -40,7 +40,7 @@
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20017, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20017, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_CHECK_FOR_EPT_HOOK \ #define IOCTL_CHECK_FOR_EPT_HOOK \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20018, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20018, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_LAUNCH_IPI_INTERRUPT \ #define IOCTL_LAUNCH_DPC_STACKWALK \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20019, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20019, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_VALIDATE_SYSTEM_MODULES \ #define IOCTL_VALIDATE_SYSTEM_MODULES \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20020, METHOD_BUFFERED, FILE_ANY_ACCESS) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20020, METHOD_BUFFERED, FILE_ANY_ACCESS)
@ -97,7 +97,7 @@ class Driver
VOID SendClientHardwareInformation(); VOID SendClientHardwareInformation();
VOID CheckForHiddenThreads(); VOID CheckForHiddenThreads();
VOID CheckForEptHooks(); VOID CheckForEptHooks();
VOID LaunchIpiInterrupt(); VOID StackwalkThreadsViaDpc();
VOID ValidateSystemModules(); VOID ValidateSystemModules();
BOOLEAN InitiateApcOperation(INT OperationId); BOOLEAN InitiateApcOperation(INT OperationId);
}; };

View file

@ -100,9 +100,9 @@ kernelmode::KManager::CheckForEptHooks()
} }
VOID VOID
kernelmode::KManager::LaunchIpiInterrupt() kernelmode::KManager::StackwalkThreadsViaDpc()
{ {
this->thread_pool->QueueJob([this]() { this->driver_interface->LaunchIpiInterrupt(); }); this->thread_pool->QueueJob([this]() { this->driver_interface->StackwalkThreadsViaDpc(); });
} }
VOID VOID

View file

@ -33,7 +33,7 @@ class KManager
VOID InitiateApcStackwalkOperation(); VOID InitiateApcStackwalkOperation();
VOID CheckForHiddenThreads(); VOID CheckForHiddenThreads();
VOID CheckForEptHooks(); VOID CheckForEptHooks();
VOID LaunchIpiInterrupt(); VOID StackwalkThreadsViaDpc();
VOID ValidateSystemModules(); VOID ValidateSystemModules();
}; };
} }

View file

@ -94,7 +94,7 @@ Init(HINSTANCE hinstDLL)
case 7: kmanager.InitiateApcStackwalkOperation(); break; case 7: kmanager.InitiateApcStackwalkOperation(); break;
case 8: kmanager.CheckForHiddenThreads(); break; case 8: kmanager.CheckForHiddenThreads(); break;
case 9: kmanager.CheckForEptHooks(); break; case 9: kmanager.CheckForEptHooks(); break;
case 10: kmanager.LaunchIpiInterrupt(); break; case 10: kmanager.StackwalkThreadsViaDpc(); break;
case 11: kmanager.ValidateSystemModules(); break; case 11: kmanager.ValidateSystemModules(); break;
} }