mirror of
https://github.com/donnaskiez/ac.git
synced 2024-11-21 22:24:08 +01:00
refactor cid detection, remove old kpcrb detection
This commit is contained in:
parent
0a1e01e8e7
commit
47adcab90f
6 changed files with 117 additions and 132 deletions
|
@ -8,19 +8,17 @@ open source anti cheat (lol) which I made for fun.
|
|||
- Process module .text section integrity checks
|
||||
- NMI stackwalking via isr iretq
|
||||
- APC stackwalking via RtlCaptureStackBackTrace
|
||||
- DPC stackwalking via RtlCaptureStackBackTrace (harder to disable)
|
||||
- DPC stackwalking via RtlCaptureStackBackTrace
|
||||
- Handle stripping via obj callbacks
|
||||
- Process handle table enumeration
|
||||
- System module verification
|
||||
- System module .text integrity checks (see known issues)
|
||||
- Unlinked process detection
|
||||
- Hidden thread detection via KPRCB
|
||||
- Hidden thread detection via PspCid table
|
||||
- Removed thread PspCidTable entry detection
|
||||
- Dispatch routine validation
|
||||
- Extraction of hardware identifiers
|
||||
- EPT hook detection (currently detects hyperdbg and DdiMon)
|
||||
- Driver integrity checks both locally and over server
|
||||
- Test signing detection
|
||||
- Hypervisor detection
|
||||
|
||||
# planned features
|
||||
|
|
|
@ -1424,4 +1424,12 @@ _IRQL_requires_same_
|
|||
VOID
|
||||
KeSignalCallDpcDone(_In_ PVOID SystemArgument1);
|
||||
|
||||
PEPROCESS
|
||||
NTAPI
|
||||
PsGetNextProcess(IN PEPROCESS OldProcess OPTIONAL);
|
||||
|
||||
PETHREAD
|
||||
NTAPI
|
||||
PsGetNextProcessThread(IN PEPROCESS Process, IN PETHREAD Thread OPTIONAL);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -358,7 +358,7 @@ DeviceControl(_In_ PDRIVER_OBJECT DriverObject, _Inout_ PIRP Irp)
|
|||
|
||||
DEBUG_INFO("IOCTL_VALIDATE_KPRCB_CURRENT_THREAD Received");
|
||||
|
||||
ValidateKPCRBThreads();
|
||||
//ValidateKPCRBThreads();
|
||||
|
||||
break;
|
||||
|
||||
|
@ -518,7 +518,8 @@ _Dispatch_type_(IRP_MJ_CREATE) NTSTATUS
|
|||
PAGED_CODE();
|
||||
|
||||
DEBUG_INFO("Handle to driver opened.");
|
||||
|
||||
DEBUG_VERBOSE("HELOO??");
|
||||
HandleNmiIOCTL(Irp);
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return Irp->IoStatus.Status;
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
#include "ioctl.h"
|
||||
#include "ia32.h"
|
||||
|
||||
#include "thread.h"
|
||||
|
||||
#define WHITELISTED_MODULE_TAG 'whte'
|
||||
|
||||
#define NMI_DELAY 200 * 10000
|
||||
|
@ -801,7 +803,8 @@ IsInstructionPointerInInvalidRegion(_In_ UINT64 RIP,
|
|||
}
|
||||
|
||||
/*
|
||||
* todo: rename this to analyse stackwalk or something
|
||||
* todo: i think we should split this function up into each analysis i.e one for the interrupted
|
||||
* rip, one for the cid etc.
|
||||
*/
|
||||
STATIC
|
||||
NTSTATUS
|
||||
|
@ -848,6 +851,52 @@ AnalyseNmiData(_In_ PNMI_CONTEXT NmiContext, _In_ PSYSTEM_MODULES SystemModules,
|
|||
core,
|
||||
NmiContext[core].callback_count);
|
||||
|
||||
/*
|
||||
* Our NMI callback allows us to interrupt every running thread on each core. Now it
|
||||
* is common practice for malicious programs to either unlink their thread from the
|
||||
* KTHREAD linked list or remove their threads entry from the PspCidTable or both.
|
||||
* Now the reason an unlinked thread can still be scheduled is because the scheduler
|
||||
* keeps a seperate list that it uses to schedule threads. It then places these
|
||||
* threads in the KPRCB in either the CurrentThread, IdleThread or NextThread.
|
||||
*
|
||||
* Since you can't just set a threads affinity to enumerate over all cores and read
|
||||
* the KPCRB->CurrentThread (since it will just show your thread) we have to
|
||||
* interrupt the thread. So below we are validating that the thread is indeed in our
|
||||
* own threads list using our callback routine and then using PsGetThreadId
|
||||
*
|
||||
* I also want to integrate a way to SAFELY determine whether a thread has been
|
||||
* removed from the KTHREADs linked list, maybe PsGetNextProcess ?
|
||||
*/
|
||||
|
||||
if (!ValidateThreadsPspCidTableEntry(NmiContext[core].kthread))
|
||||
{
|
||||
DEBUG_WARNING("Thread: %llx was not found in the pspcid table.",
|
||||
NmiContext[core].kthread);
|
||||
|
||||
PHIDDEN_SYSTEM_THREAD_REPORT report =
|
||||
ExAllocatePool2(POOL_FLAG_NON_PAGED,
|
||||
sizeof(HIDDEN_SYSTEM_THREAD_REPORT),
|
||||
REPORT_POOL_TAG);
|
||||
|
||||
if (!report)
|
||||
continue;
|
||||
|
||||
report->report_code = REPORT_HIDDEN_SYSTEM_THREAD;
|
||||
report->found_in_kthreadlist = FALSE; // wip
|
||||
report->found_in_pspcidtable = FALSE;
|
||||
report->thread_id = PsGetThreadId(NmiContext[core].kthread);
|
||||
report->thread_address = NmiContext[core].kthread;
|
||||
|
||||
RtlCopyMemory(
|
||||
report->thread, NmiContext[core].kthread, sizeof(report->thread));
|
||||
|
||||
InsertReportToQueue(report);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_VERBOSE("Thread: %llx was found in PspCidTable", NmiContext[core].kthread);
|
||||
}
|
||||
|
||||
if (NmiContext[core].user_thread)
|
||||
continue;
|
||||
|
||||
|
@ -992,16 +1041,13 @@ HandleNmiIOCTL(_Inout_ PIRP Irp)
|
|||
SYSTEM_MODULES system_modules = {0};
|
||||
PNMI_CONTEXT nmi_context = NULL;
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
@ -1373,9 +1419,9 @@ _IRQL_requires_(DISPATCH_LEVEL)
|
|||
_IRQL_requires_same_
|
||||
VOID
|
||||
DpcStackwalkCallbackRoutine(_In_ PKDPC Dpc,
|
||||
_In_opt_ PVOID DeferredContext,
|
||||
_In_opt_ PVOID SystemArgument1,
|
||||
_In_opt_ PVOID SystemArgument2)
|
||||
_In_opt_ PVOID DeferredContext,
|
||||
_In_opt_ PVOID SystemArgument1,
|
||||
_In_opt_ PVOID SystemArgument2)
|
||||
{
|
||||
PDPC_CONTEXT context = &((PDPC_CONTEXT)DeferredContext)[KeGetCurrentProcessorNumber()];
|
||||
|
||||
|
|
154
driver/thread.c
154
driver/thread.c
|
@ -8,131 +8,53 @@
|
|||
#include "queue.h"
|
||||
|
||||
#ifdef ALLOC_PRAGMA
|
||||
# pragma alloc_text(PAGE, ValidateKPCRBThreads)
|
||||
# pragma alloc_text(PAGE, DetectThreadsAttachedToProtectedProcess)
|
||||
# pragma alloc_text(PAGE, ValidateThreadsPspCidTableEntry)
|
||||
#endif
|
||||
|
||||
typedef struct _KPRCB_THREAD_VALIDATION_CTX
|
||||
{
|
||||
UINT64 current_kpcrb_thread;
|
||||
UINT8 thread_found_in_pspcidtable;
|
||||
UINT8 thread_found_in_kthreadlist;
|
||||
BOOLEAN finished;
|
||||
|
||||
} KPRCB_THREAD_VALIDATION_CTX, *PKPRCB_THREAD_VALIDATION_CTX;
|
||||
|
||||
_IRQL_always_function_min_(DISPATCH_LEVEL) STATIC VOID
|
||||
KPRCBThreadValidationProcessCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
|
||||
_Inout_opt_ PVOID Context)
|
||||
{
|
||||
UINT32 thread_id = 0;
|
||||
PKPRCB_THREAD_VALIDATION_CTX context = (PKPRCB_THREAD_VALIDATION_CTX)Context;
|
||||
|
||||
if (!Context || context->finished == TRUE)
|
||||
return;
|
||||
|
||||
if (ThreadListEntry->thread == context->current_kpcrb_thread)
|
||||
{
|
||||
context->thread_found_in_kthreadlist = TRUE;
|
||||
|
||||
thread_id = PsGetThreadId(ThreadListEntry->thread);
|
||||
|
||||
if (thread_id != NULL)
|
||||
{
|
||||
context->thread_found_in_pspcidtable = TRUE;
|
||||
context->finished = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* How this will work:
|
||||
*
|
||||
* 1. The KPCRB (processor control block) contains 3 pointers to 3 threads:
|
||||
*
|
||||
* +0x008 CurrentThread : Ptr64 _KTHREAD
|
||||
* +0x010 NextThread : Ptr64 _KTHREAD
|
||||
* +0x018 IdleThread : Ptr64 _KTHREAD
|
||||
*
|
||||
* 2. These threads are stored in a list that is seperate to the KTHREADs linked list.
|
||||
* We know this because if you unlink a process, the threads are still scheduled by
|
||||
* the OS, meaning the OS has a seperate list that it uses to schedule these threads.
|
||||
*
|
||||
* 3. From here we can firstly check if the KTHREAD is within the KTHREAD linked list,
|
||||
* if it is we can then use this to check if its in the PspCidTable by passing it
|
||||
* to PsGetThreadId which returns the thread id by enumerating the PspCidTable and
|
||||
* finding the corresponding object pointer. If the thread id is not found, we know
|
||||
* that it's been removed from the PspCidTable, and if the thread is not in any
|
||||
* process' thread list , we know it's been removed from the KTHREAD linked list.
|
||||
*
|
||||
*/
|
||||
VOID
|
||||
ValidateKPCRBThreads()
|
||||
BOOLEAN
|
||||
ValidateThreadsPspCidTableEntry(_In_ PETHREAD Thread)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
UINT64 kpcr = 0;
|
||||
UINT64 kprcb = 0;
|
||||
KAFFINITY old_affinity = {0};
|
||||
KPRCB_THREAD_VALIDATION_CTX context = {0};
|
||||
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
||||
HANDLE thread_id = NULL;
|
||||
PETHREAD thread = NULL;
|
||||
|
||||
for (LONG processor_index = 0; processor_index < KeQueryActiveProcessorCount(0);
|
||||
processor_index++)
|
||||
/*
|
||||
* PsGetThreadId simply returns ETHREAD->Cid.UniqueThread
|
||||
*/
|
||||
thread_id = PsGetThreadId(Thread);
|
||||
|
||||
/*
|
||||
* For each core on the processor, the first x threads equal to x cores will be assigned a
|
||||
* cid equal to its equivalent core. These threads are generally executing the HLT
|
||||
* instruction or some other boring stuff while the processor is not busy. The reason this
|
||||
* is important is because passing in a handle value of 0 which, even though is a valid cid,
|
||||
* returns a non success status meaning we mark it an invalid cid entry even though it is.
|
||||
* To combat this we simply add a little check here. The problem is this can be easily
|
||||
* bypassed by simply modifying the ETHREAD->Cid.UniqueThread identifier.. So while it isnt a
|
||||
* perfect detection method for now it's good enough.
|
||||
*/
|
||||
if ((UINT64)thread_id < (UINT64)KeQueryActiveProcessorCount(NULL))
|
||||
return TRUE;
|
||||
|
||||
/*
|
||||
* PsLookupThreadByThreadId will use a threads id to find its cid entry, and return
|
||||
* the pointer contained in the HANDLE_TABLE entry pointing to the thread object.
|
||||
* Meaning if we pass a valid thread id which we retrieved above and dont receive a
|
||||
* STATUS_SUCCESS the cid entry could potentially be removed or disrupted..
|
||||
*/
|
||||
status = PsLookupThreadByThreadId(thread_id, &thread);
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
old_affinity = KeSetSystemAffinityThreadEx((KAFFINITY)(1ull << processor_index));
|
||||
|
||||
while (KeGetCurrentProcessorNumber() != processor_index)
|
||||
YieldProcessor();
|
||||
|
||||
kpcr = __readmsr(IA32_GS_BASE);
|
||||
kprcb = kpcr + KPRCB_OFFSET_FROM_GS_BASE;
|
||||
|
||||
/* sanity check */
|
||||
if (!MmIsAddressValid(kprcb + KPCRB_CURRENT_THREAD))
|
||||
continue;
|
||||
|
||||
context.current_kpcrb_thread = *(UINT64*)(kprcb + KPCRB_CURRENT_THREAD);
|
||||
|
||||
DEBUG_VERBOSE("Proc number: %lx, Current thread: %llx",
|
||||
processor_index,
|
||||
context.current_kpcrb_thread);
|
||||
|
||||
if (!context.current_kpcrb_thread)
|
||||
continue;
|
||||
|
||||
EnumerateThreadListWithCallbackRoutine(KPRCBThreadValidationProcessCallback,
|
||||
&context);
|
||||
|
||||
DEBUG_VERBOSE("Was thread found in kthread: %lx, Was thread found in cid table: %lx",
|
||||
(UINT32)context.thread_found_in_kthreadlist,
|
||||
(UINT32)context.thread_found_in_pspcidtable);
|
||||
|
||||
if (context.current_kpcrb_thread == FALSE ||
|
||||
context.thread_found_in_pspcidtable == FALSE)
|
||||
{
|
||||
PHIDDEN_SYSTEM_THREAD_REPORT report =
|
||||
ExAllocatePool2(POOL_FLAG_NON_PAGED,
|
||||
sizeof(HIDDEN_SYSTEM_THREAD_REPORT),
|
||||
REPORT_POOL_TAG);
|
||||
|
||||
if (!report)
|
||||
goto increment;
|
||||
|
||||
report->report_code = REPORT_HIDDEN_SYSTEM_THREAD;
|
||||
report->found_in_kthreadlist = context.thread_found_in_kthreadlist;
|
||||
report->found_in_pspcidtable = context.thread_found_in_pspcidtable;
|
||||
report->thread_id = PsGetThreadId(context.current_kpcrb_thread);
|
||||
report->thread_address = context.current_kpcrb_thread;
|
||||
|
||||
RtlCopyMemory(
|
||||
report->thread, context.current_kpcrb_thread, sizeof(report->thread));
|
||||
|
||||
InsertReportToQueue(report);
|
||||
}
|
||||
|
||||
increment:
|
||||
KeRevertToUserAffinityThreadEx(old_affinity);
|
||||
DEBUG_WARNING(
|
||||
"Failed to lookup thread by id. PspCidTable entry potentially removed.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_IRQL_always_function_min_(DISPATCH_LEVEL) STATIC VOID
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <ntifs.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "callbacks.h"
|
||||
|
||||
typedef struct _HIDDEN_SYSTEM_THREAD_REPORT
|
||||
{
|
||||
|
@ -24,8 +25,17 @@ typedef struct _ATTACH_PROCESS_REPORT
|
|||
|
||||
} ATTACH_PROCESS_REPORT, *PATTACH_PROCESS_REPORT;
|
||||
|
||||
VOID
|
||||
ValidateKPCRBThreads();
|
||||
typedef struct _KPRCB_THREAD_VALIDATION_CTX
|
||||
{
|
||||
UINT64 thread;
|
||||
BOOLEAN thread_found_in_pspcidtable;
|
||||
//BOOLEAN thread_found_in_kthreadlist;
|
||||
BOOLEAN finished;
|
||||
|
||||
} KPRCB_THREAD_VALIDATION_CTX, *PKPRCB_THREAD_VALIDATION_CTX;
|
||||
|
||||
BOOLEAN
|
||||
ValidateThreadsPspCidTableEntry(_In_ PETHREAD Thread);
|
||||
|
||||
VOID
|
||||
DetectThreadsAttachedToProtectedProcess();
|
||||
|
|
Loading…
Reference in a new issue