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
- Process module .text section integrity checks
- NMI and APC stackwalking
- IPI stackwalking which is a relatively unknown method compared to NMIs and APCs
- NMI stackwalking via isr iretq
- APC stackwalking via RtlCaptureStackBackTrace
- DPC stackwalking via RtlCaptureStackBackTrace (harder to disable)
- Handle stripping via obj callbacks
- Process handle table enumeration
- 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 /debug on
```
3. Restart Windows
## building and running the project
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`
- `C/C++ -> Treat Warnings As Errors` to `No`
- `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
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!
8. Click `Register Service`. *Do NOT click* `Start Service`!
9. Restart Windows.
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
11. Open your dll injector program of choice as administrator (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
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 - 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.

View file

@ -44,6 +44,7 @@
#define INVALID_DRIVER_LIST_HEAD_POOL 'rwar'
#define INVALID_DRIVER_LIST_ENTRY_POOL 'gaah'
#define POOL_TAG_APC 'apcc'
#define POOL_TAG_DPC 'apcc'
#define SYSTEM_MODULES_POOL 'halb'
#define THREAD_DATA_POOL 'doof'
#define PROC_AFFINITY_POOL 'eeee'
@ -119,6 +120,7 @@
#define REPORT_HIDDEN_SYSTEM_THREAD 90
#define REPORT_ILLEGAL_ATTACH_PROCESS 100
#define REPORT_APC_STACKWALK 110
#define REPORT_DPC_STACKWALK 120
/*
* Generic macros that allow you to quickly determine whether
@ -898,23 +900,6 @@ typedef struct _DUMP_HEADER
struct _KDDEBUGGER_DATA64* KdDebuggerDataBlock;
} 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
{
struct
@ -1411,4 +1396,32 @@ C_ASSERT(FIELD_OFFSET(DUMP_HEADER, KdDebuggerDataBlock) == 0x80);
# define DUMP_BLOCK_SIZE 0x40000
#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

View file

@ -1416,15 +1416,6 @@ DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
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.");
return STATUS_SUCCESS;
}

View file

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

View file

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

View file

@ -83,6 +83,9 @@
<ClInclude Include="thread.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ia32.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<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);
}
DEBUG_INFO("Successfully retrieved hard disk serial number.");
DEBUG_VERBOSE("Successfully retrieved hard disk serial number.");
end:
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)
#define IOCTL_CHECK_FOR_EPT_HOOK \
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)
#define IOCTL_VALIDATE_SYSTEM_MODULES \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20020, METHOD_BUFFERED, FILE_ANY_ACCESS)
@ -446,17 +446,6 @@ DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
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:
DEBUG_INFO("IOCTL_VALIDATE_SYSTEM_MODULES Received");
@ -472,6 +461,19 @@ DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
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:
DEBUG_WARNING("Invalid IOCTL passed to driver: %lx",
stack_location->Parameters.DeviceIoControl.IoControlCode);

View file

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

View file

@ -16,6 +16,17 @@ typedef struct NMI_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
{
INT report_code;
@ -26,8 +37,6 @@ typedef struct _MODULE_VALIDATION_FAILURE
} MODULE_VALIDATION_FAILURE, *PMODULE_VALIDATION_FAILURE;
#define APC_STACKWALK_BUFFER_SIZE 4096
typedef struct _APC_STACKWALK_REPORT
{
INT report_code;
@ -99,6 +108,6 @@ BOOLEAN
FlipKThreadMiscFlagsFlag(_In_ PKTHREAD Thread, _In_ ULONG FlagIndex, _In_ BOOLEAN NewValue);
NTSTATUS
LaunchInterProcessInterrupt(_In_ PIRP Irp);
DispatchStackwalkToEachCpuViaDpc();
#endif

View file

@ -4,6 +4,7 @@
#include "callbacks.h"
#include "queue.h"
#include "ia32.h"
#define PAGE_BASE_SIZE 0x1000
#define POOL_TAG_SIZE 0x004
@ -444,9 +445,9 @@ WalkKernelPageTables(_In_ PPROCESS_SCAN_CONTEXT Context)
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);

View file

@ -267,6 +267,16 @@ HandlePeriodicGlobalReportQueueQuery(_Inout_ PIRP Irp)
total_size += sizeof(HIDDEN_SYSTEM_THREAD_REPORT);
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 */

View file

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

View file

@ -26,6 +26,7 @@
#define REPORT_HIDDEN_SYSTEM_THREAD 90
#define REPORT_ILLEGAL_ATTACH_PROCESS 100
#define REPORT_APC_STACKWALK 110
#define REPORT_DPC_STACKWALK 120
#define TEST_STEAM_64_ID 123456789;
@ -205,6 +206,14 @@ struct APC_STACKWALK_REPORT
UINT64 invalid_rip;
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>(
buffer, &total_size, &hidden_report);
break;
case REPORT_DPC_STACKWALK:
ReportTypeFromReportQueue<DPC_STACKWALK_REPORT>(
buffer, &total_size, &hidden_report);
break;
default: break;
}
}
@ -521,15 +525,15 @@ kernelmode::Driver::CheckForEptHooks()
}
VOID
kernelmode::Driver::LaunchIpiInterrupt()
kernelmode::Driver::StackwalkThreadsViaDpc()
{
BOOLEAN status = FALSE;
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)
LOG_ERROR("failed to launch ipi interrupt %x", GetLastError());
LOG_ERROR("failed to stackwalk threads via dpc %x", GetLastError());
}
VOID

View file

@ -40,7 +40,7 @@
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20017, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_CHECK_FOR_EPT_HOOK \
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)
#define IOCTL_VALIDATE_SYSTEM_MODULES \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x20020, METHOD_BUFFERED, FILE_ANY_ACCESS)
@ -97,7 +97,7 @@ class Driver
VOID SendClientHardwareInformation();
VOID CheckForHiddenThreads();
VOID CheckForEptHooks();
VOID LaunchIpiInterrupt();
VOID StackwalkThreadsViaDpc();
VOID ValidateSystemModules();
BOOLEAN InitiateApcOperation(INT OperationId);
};

View file

@ -100,9 +100,9 @@ kernelmode::KManager::CheckForEptHooks()
}
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

View file

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

View file

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