implement thread list

This commit is contained in:
lhodges1 2023-10-09 01:07:49 +11:00
parent 7dab235001
commit f8b04d8b5e
8 changed files with 325 additions and 80 deletions

View file

@ -6,6 +6,29 @@
#include "pool.h"
#include "thread.h"
/*
* Interlocked intrinsics are only atomic with respect to other InterlockedXxx functions,
* so all reads and writes to the THREAD_LIST->active flag must be with Interlocked instrinsics.
*/
typedef struct _THREAD_LIST
{
SINGLE_LIST_ENTRY start;
volatile BOOLEAN active;
KSPIN_LOCK lock;
}THREAD_LIST, * PTHREAD_LIST;
typedef struct _THREAD_LIST_ENTRY
{
SINGLE_LIST_ENTRY list;
PKTHREAD thread;
}THREAD_LIST_ENTRY, *PTHREAD_LIST_ENTRY;
/* todo: maybe put this in the global config? hmm.. I kinda like how its encapsulated here tho hm.. */
PTHREAD_LIST thread_list = NULL;
STATIC
BOOLEAN
EnumHandleCallback(
@ -20,8 +43,129 @@ EnumHandleCallback(
#pragma alloc_text(PAGE, EnumHandleCallback)
#pragma alloc_text(PAGE, EnumerateProcessHandles)
#pragma alloc_text(PAGE, EnumerateProcessListWithCallbackFunction)
#pragma alloc_text(PAGE, InitialiseThreadList)
#endif
VOID
CleanupThreadListOnDriverUnload()
{
InterlockedExchange(&thread_list->active, FALSE);
PsRemoveCreateThreadNotifyRoutine(ThreadCreateNotifyRoutine);
for (;;)
{
if (!ListFreeFirstEntry(&thread_list->start, &thread_list->lock))
{
ExFreePoolWithTag(thread_list, POOL_TAG_THREAD_LIST);
return;
}
}
}
/*
* Safely enumerate the threads list.
*/
VOID
EnumerateThreadListWithCallbackRoutine(
_In_ PVOID CallbackRoutine,
_In_opt_ PVOID Context
)
{
if (!CallbackRoutine)
return;
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock(&thread_list->lock, &irql);
PTHREAD_LIST_ENTRY entry = thread_list->start.Next;
while (entry)
{
VOID(*callback_function_ptr)(PKTHREAD, PVOID) = CallbackRoutine;
(*callback_function_ptr)(entry->thread, Context);
entry = entry->list.Next;
}
KeReleaseSpinLock(&thread_list->lock, irql);
}
NTSTATUS
InitialiseThreadList()
{
thread_list =
ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(THREAD_LIST), POOL_TAG_THREAD_LIST);
if (!thread_list)
return STATUS_MEMORY_NOT_ALLOCATED;
thread_list->active = TRUE;
ListInit(&thread_list->start, &thread_list->lock);
return STATUS_SUCCESS;
}
STATIC
VOID
FindThreadListEntryByThreadAddress(
_In_ PKTHREAD Thread,
_Inout_ PTHREAD_LIST_ENTRY* Entry
)
{
KIRQL irql = KeGetCurrentIrql();
*Entry = NULL;
KeAcquireSpinLock(&thread_list->lock, &irql);
PTHREAD_LIST_ENTRY entry = (PTHREAD_LIST_ENTRY)thread_list->start.Next;
while (entry)
{
if (entry->thread == Thread)
{
*Entry = entry;
goto unlock;
}
}
unlock:
KeReleaseSpinLock(&thread_list->lock, irql);
}
VOID
ThreadCreateNotifyRoutine(
_In_ HANDLE ProcessId,
_In_ HANDLE ThreadId,
_In_ BOOLEAN Create
)
{
PTHREAD_LIST_ENTRY entry = NULL;
PKTHREAD thread = NULL;
/* ensure we don't insert new entries if we are unloading */
if (InterlockedExchange(&thread_list->active, thread_list->active) == FALSE)
return;
PsLookupThreadByThreadId(ThreadId, &thread);
if (!thread)
return;
if (Create)
{
entry = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(THREAD_LIST_ENTRY), POOL_TAG_THREAD_LIST);
if (!entry)
return;
ListInsert(&thread_list->start, &entry->list, &thread_list->lock);
DEBUG_LOG("Thread inserted: %llx", (UINT64)thread);
}
else
{
FindThreadListEntryByThreadAddress(thread, &entry);
ListRemoveEntry(&thread_list->start, entry, &thread_list->lock);
DEBUG_LOG("Thread removed: %llx", (UINT64)thread);
}
}
VOID
ObPostOpCallbackRoutine(
_In_ PVOID RegistrationContext,

View file

@ -80,4 +80,17 @@ EnumerateProcessHandles(
_In_ PEPROCESS Process
);
NTSTATUS
InitialiseThreadList();
VOID
ThreadCreateNotifyRoutine(
_In_ HANDLE ProcessId,
_In_ HANDLE ThreadId,
_In_ BOOLEAN Create
);
VOID
CleanupThreadListOnDriverUnload();
#endif

View file

@ -35,6 +35,7 @@
#define REPORT_POOL_TAG 'repo'
#define MODULES_REPORT_POOL_TAG 'modu'
#define POOL_TAG_LIST_ITEM 'tsil'
#define POOL_TAG_THREAD_LIST 'list'
#define IA32_APERF_MSR 0x000000E8
@ -49,8 +50,11 @@
#define KTHREAD_START_ADDRESS_OFFSET 0x450
#define KTHREAD_MISC_FLAGS_OFFSET 0x074
#define KTHREAD_WAIT_IRQL_OFFSET 0x186
#define KTHREAD_PREVIOUS_MODE_OFFSET 0x232
#define KTHREAD_STATE_OFFSET 0x184
#define KTHREAD_MISC_FLAGS_APC_QUEUEABLE 14
#define KTHREAD_MISC_FLAGS_ALERTABLE 4
#define EPROCESS_PEAK_VIRTUAL_SIZE_OFFSET 0x490
#define EPROCESS_VAD_ROOT_OFFSET 0x7d8

View file

@ -141,6 +141,7 @@ EnableCallbackRoutinesOnProcessRun()
OB_CALLBACK_REGISTRATION callback_registration = { 0 };
OB_OPERATION_REGISTRATION operation_registration = { 0 };
PCREATE_PROCESS_NOTIFY_ROUTINE_EX notify_routine = { 0 };
operation_registration.ObjectType = PsProcessType;
operation_registration.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
@ -176,6 +177,28 @@ end:
return status;
}
STATIC
NTSTATUS
EnableCallbackRoutinesOnDriverEntry()
{
NTSTATUS status;
status = InitialiseThreadList();
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("InitialiseThreadList failed with status %x", status);
return status;
}
status = PsSetCreateThreadNotifyRoutine(ThreadCreateNotifyRoutine);
if (!NT_SUCCESS(status))
DEBUG_ERROR("PsSetCreateProcessNotifyRoutine failed with status %x", status);
return status;
}
STATIC
NTSTATUS
AllocateCallbackStructure()
@ -843,6 +866,7 @@ DriverUnload(
/* This is safe to call even if the callbacks have already been disabled */
CleanupDriverCallbacksOnDriverUnload();
CleanupThreadListOnDriverUnload();
CleanupDriverConfigOnUnload();
IoDeleteDevice(DriverObject->DeviceObject);
@ -947,6 +971,18 @@ DriverEntry(
return STATUS_FAILED_DRIVER_ENTRY;
}
status = EnableCallbackRoutinesOnDriverEntry();
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to init callback routines on driver entry");
FreeGlobalReportQueueObjects();
FreeDriverConfigurationStringBuffers();
IoDeleteSymbolicLink(&driver_config.device_symbolic_link);
IoDeleteDevice(DriverObject->DeviceObject);
return STATUS_FAILED_DRIVER_ENTRY;
}
DEBUG_LOG("DonnaAC Driver Entry Complete");
return STATUS_SUCCESS;

View file

@ -7,7 +7,7 @@
#define NMI_DELAY 200 * 10000
#define WHITELISTED_MODULE_COUNT 7
#define WHITELISTED_MODULE_COUNT 11
#define MODULE_MAX_STRING_SIZE 256
#define NTOSKRNL 0
@ -26,10 +26,14 @@ CHAR WHITELISTED_MODULES[WHITELISTED_MODULE_COUNT][MODULE_MAX_STRING_SIZE] =
"ntoskrnl.exe",
"CLASSPNP.SYS",
"Wdf01000.sys",
"HIDCLASS.sys",
"HIDCLASS.SYS",
"storport.sys",
"dxgkrnl.sys",
"ndis.sys"
"ndis.sys",
"ks.sys",
"portcls.sys",
"rdbss.sys",
"LXCORE.SYS"
};
#define MODULE_REPORT_DRIVER_NAME_BUFFER_SIZE 128
@ -1124,7 +1128,7 @@ ApcKernelRoutine(
context = (PAPC_STACKWALK_CONTEXT)Apc->NormalContext;
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, 0x200, POOL_TAG_APC);
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, STACK_FRAME_POOL_SIZE, POOL_TAG_APC);
if (!buffer)
goto free;
@ -1161,7 +1165,8 @@ ApcKernelRoutine(
if (flag == FALSE)
{
PAPC_STACKWALK_REPORT report = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(APC_STACKWALK_REPORT), POOL_TAG_APC);
PAPC_STACKWALK_REPORT report =
ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(APC_STACKWALK_REPORT), POOL_TAG_APC);
if (!report)
goto free;
@ -1202,7 +1207,7 @@ ApcNormalRoutine(
}
BOOLEAN
VOID
FlipKThreadMiscFlagsFlag(
_In_ PKTHREAD Thread,
_In_ LONG FlagIndex,
@ -1212,17 +1217,16 @@ FlipKThreadMiscFlagsFlag(
PLONG misc_flags = (PLONG)((UINT64)Thread + KTHREAD_MISC_FLAGS_OFFSET);
LONG mask = 1U << FlagIndex;
if (!MmIsAddressValid(misc_flags))
return FALSE;
if (NewValue)
*misc_flags |= mask;
else
*misc_flags &= ~mask;
return TRUE;
}
#define THREAD_STATE_TERMINATED 4
#define THREAD_STATE_WAIT 5
#define THREAD_STATE_INIT 0
STATIC
VOID
ValidateThreadViaKernelApcCallback(
@ -1237,21 +1241,29 @@ ValidateThreadViaKernelApcCallback(
PKAPC apc = NULL;
BOOLEAN apc_status;
PLONG misc_flags = NULL;
PCHAR previous_mode = NULL;
PUCHAR state = NULL;
BOOLEAN apc_queueable = FALSE;
PAPC_STACKWALK_CONTEXT context = (PAPC_STACKWALK_CONTEXT)Context;
LPCSTR process_name = PsGetProcessImageFileName(Process);
/* we dont want to schedule an apc to threads owned by the kernel */
if (Process == PsInitialSystemProcess)
if (Process == PsInitialSystemProcess || !Context)
return;
/* We are not interested in these processess.. for now lol */
if (!strcmp(process_name, "svchost.exe") ||
!strcmp(process_name, "Registry") ||
!strcmp(process_name, "smss.exe") ||
!strcmp(process_name, "csrss.exe"))
!strcmp(process_name, "csrss.exe") ||
!strcmp(process_name, "explorer.exe") ||
!strcmp(process_name, "svchost.exe") ||
!strcmp(process_name, "lsass.exe") ||
!strcmp(process_name, "MemCompression"))
return;
DEBUG_LOG("Process: %s", process_name);
thread_list_head = (PLIST_ENTRY)((UINT64)Process + KPROCESS_THREADLIST_OFFSET);
thread_list_entry = thread_list_head->Flink;
@ -1263,7 +1275,6 @@ ValidateThreadViaKernelApcCallback(
if (current_thread == KeGetCurrentThread() || !current_thread)
goto increment;
/*
* 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
@ -1271,23 +1282,55 @@ ValidateThreadViaKernelApcCallback(
* threads this should be fine... c:
*/
misc_flags = (PLONG)((UINT64)current_thread + KTHREAD_MISC_FLAGS_OFFSET);
previous_mode = (PCHAR)((UINT64)current_thread + KTHREAD_PREVIOUS_MODE_OFFSET);
state = (PUCHAR)((UINT64)current_thread + KTHREAD_STATE_OFFSET);
/* sanity check */
if (!MmIsAddressValid(misc_flags))
if (!MmIsAddressValid(current_thread))
goto increment;
/* we dont care about user mode threads */
//if (*previous_mode == UserMode)
// goto increment;
/* todo: We should also flag all threads that have the flag set to false */
if (*misc_flags >> KTHREAD_MISC_FLAGS_APC_QUEUEABLE == FALSE)
{
if (!FlipKThreadMiscFlagsFlag(current_thread, KTHREAD_MISC_FLAGS_APC_QUEUEABLE, TRUE))
goto increment;
}
FlipKThreadMiscFlagsFlag(current_thread, KTHREAD_MISC_FLAGS_APC_QUEUEABLE, TRUE);
/* force thread into an alertable state */
if (*misc_flags >> KTHREAD_MISC_FLAGS_ALERTABLE == FALSE)
FlipKThreadMiscFlagsFlag(current_thread, KTHREAD_MISC_FLAGS_ALERTABLE, TRUE);
apc = (PKAPC)ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(KAPC), POOL_TAG_APC);
if (!apc)
goto increment;
/*
* KTHREAD->State values:
*
* 0 is INITIALIZED;
* 1 is READY;
* 2 is RUNNING;
* 3 is STANDBY;
* 4 is TERMINATED;
* 5 is WAIT;
* 6 is TRANSITION.
*
* Since we are unsafely enumerating the threads linked list, it's best just
* to make sure we don't queue an APC to a terminated thread. We also check after
* we've allocated memory for the apc to ensure the window between queuing our APC
* and checking the thread state is as small as possible.
*/
if (*state == THREAD_STATE_TERMINATED || THREAD_STATE_INIT)
{
ExFreePoolWithTag(apc, POOL_TAG_APC);
apc = NULL;
goto increment;
}
DEBUG_LOG("Apc: %llx", (UINT64)apc);
KeInitializeApc(
apc,
current_thread,

View file

@ -285,81 +285,88 @@ end:
VOID
ListInit(
_Inout_ PLIST_HEAD ListHead
_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PKSPIN_LOCK Lock
)
{
KeInitializeSpinLock(&ListHead->lock);
ListHead->start = NULL;
KeInitializeSpinLock(Lock);
Head->Next = NULL;
}
PLIST_ITEM
VOID
ListInsert(
_Inout_ PLIST_HEAD ListHead,
_Inout_ PLIST_ITEM NewEntry
_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PSINGLE_LIST_ENTRY NewEntry,
_In_ PKSPIN_LOCK Lock
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock(&ListHead->lock, &irql);
KeAcquireSpinLock(Lock, &irql);
PLIST_ITEM old_entry = ListHead->start;
PSINGLE_LIST_ENTRY old_entry = Head->Next;
ListHead->start = NewEntry;
NewEntry->next = old_entry;
Head->Next = NewEntry;
NewEntry->Next = old_entry;
KeReleaseSpinLock(&ListHead->lock, irql);
KeReleaseSpinLock(Lock, irql);
}
PVOID
ListRemoveFirst(
_Inout_ PLIST_HEAD ListHead
BOOLEAN
ListFreeFirstEntry(
_Inout_ PSINGLE_LIST_ENTRY Head,
_In_ PKSPIN_LOCK Lock
)
{
BOOLEAN result = FALSE;
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock(&ListHead->lock, &irql);
KeAcquireSpinLock(Lock, &irql);
if (ListHead->start)
if (Head->Next)
{
PLIST_ITEM entry = ListHead->start;
ListHead->start = ListHead->start->next;
ExFreePoolWithTag(entry, POOL_TAG_APC);
PSINGLE_LIST_ENTRY entry = Head->Next;
Head->Next = Head->Next->Next;
ExFreePoolWithTag(entry, POOL_TAG_THREAD_LIST);
result = TRUE;
}
KeReleaseSpinLock(&ListHead->lock, irql);
KeReleaseSpinLock(Lock, irql);
return result;
}
PVOID
ListRemoveItem(
_Inout_ PLIST_HEAD ListHead,
_Inout_ PLIST_ITEM ListItem
VOID
ListRemoveEntry(
_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PSINGLE_LIST_ENTRY Entry,
_In_ PKSPIN_LOCK Lock
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock(&ListHead->lock, &irql);
KeAcquireSpinLock(Lock, &irql);
PLIST_ITEM entry = ListHead->start;
PSINGLE_LIST_ENTRY entry = Head->Next;
if (!entry)
goto unlock;
if (entry == ListItem)
if (entry == Entry)
{
ListHead->start = entry->next;
ExFreePoolWithTag(ListItem, POOL_TAG_APC);
Head->Next = entry->Next;
ExFreePoolWithTag(Entry, POOL_TAG_THREAD_LIST);
goto unlock;
}
while (entry->next)
while (entry->Next)
{
if (entry->next == ListItem)
if (entry->Next == Entry)
{
entry->next = ListItem->next;
ExFreePoolWithTag(ListItem, POOL_TAG_APC);
entry->Next = Entry->Next;
ExFreePoolWithTag(Entry, POOL_TAG_THREAD_LIST);
goto unlock;
}
entry = entry->next;
entry = entry->Next;
}
unlock:
KeReleaseSpinLock(&ListHead->lock, irql);
KeReleaseSpinLock(Lock, irql);
}

View file

@ -34,19 +34,6 @@ typedef struct _REPORT_HEADER
}REPORT_HEADER, * PREPORT_HEADER;
typedef struct _LIST_ITEM
{
struct _LIST_ITEM* next;
}LIST_ITEM, * PLIST_ITEM;
typedef struct _LIST_HEAD
{
PLIST_ITEM start;
KSPIN_LOCK lock;
}LIST_HEAD, * PLIST_HEAD;
#define LIST_POOL_TAG 'list'
VOID
@ -80,24 +67,34 @@ FreeGlobalReportQueueObjects();
VOID
ListInit(
_Inout_ PLIST_HEAD ListHead
_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PKSPIN_LOCK Lock
);
PLIST_ITEM
VOID
ListInsert(
_Inout_ PLIST_HEAD ListHead,
_Inout_ PLIST_ITEM Data
_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PSINGLE_LIST_ENTRY NewEntry,
_In_ PKSPIN_LOCK Lock
);
PVOID
ListRemoveFirst(
_Inout_ PLIST_HEAD ListHead
BOOLEAN
ListFreeFirstEntry(
_Inout_ PSINGLE_LIST_ENTRY Head,
_In_ PKSPIN_LOCK Lock
);
PVOID
ListRemoveItem(
_Inout_ PLIST_HEAD ListHead,
_Inout_ PLIST_ITEM ListItem
VOID
ListRemoveEntry(
_Inout_ PSINGLE_LIST_ENTRY Head,
_Inout_ PSINGLE_LIST_ENTRY Entry,
_In_ PKSPIN_LOCK Lock
);
VOID
EnumerateThreadListWithCallbackRoutine(
_In_ PVOID CallbackRoutine,
_In_opt_ PVOID Context
);
#endif

View file

@ -71,7 +71,7 @@ DWORD WINAPI Init(HINSTANCE hinstDLL)
kmanager.CheckForAttachedThreads();
break;
case 7:
//kmanager.InitiateApcStackwalkOperation();
kmanager.InitiateApcStackwalkOperation();
break;
case 8:
kmanager.CheckForHiddenThreads();
@ -81,6 +81,7 @@ DWORD WINAPI Init(HINSTANCE hinstDLL)
break;
}
kmanager.InitiateApcStackwalkOperation();
kmanager.MonitorCallbackReports();
std::this_thread::sleep_for(std::chrono::seconds(10));