mirror of
https://github.com/8damon/Blackbird-Platform
synced 2026-06-21 13:41:12 +00:00
789 lines
24 KiB
C
789 lines
24 KiB
C
#include <ntddk.h>
|
|
#include "..\core\control.h"
|
|
#include "..\core\protection_utils.h"
|
|
#include "..\core\pool_compat.h"
|
|
#include "..\core\tempus_debug.h"
|
|
#include "..\telemetry\etw.h"
|
|
#include "..\correlation\intent_store.h"
|
|
#include "..\correlation\hollowing_engine.h"
|
|
#include "thread_monitor.h"
|
|
|
|
#ifndef ThreadQuerySetWin32StartAddress
|
|
#define ThreadQuerySetWin32StartAddress ((THREADINFOCLASS)9)
|
|
#endif
|
|
|
|
#ifndef THREAD_QUERY_INFORMATION
|
|
#define THREAD_QUERY_INFORMATION (0x0040)
|
|
#endif
|
|
|
|
#ifndef THREAD_QUERY_LIMITED_INFORMATION
|
|
#define THREAD_QUERY_LIMITED_INFORMATION (0x0800)
|
|
#endif
|
|
|
|
#ifndef PROCESS_VM_READ
|
|
#define PROCESS_VM_READ 0x0010
|
|
#endif
|
|
|
|
#ifndef PROCESS_QUERY_LIMITED_INFORMATION
|
|
#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
|
|
#endif
|
|
|
|
#ifndef PROCESS_VM_OPERATION
|
|
#define PROCESS_VM_OPERATION 0x0008
|
|
#endif
|
|
|
|
#ifndef RTL_WALK_USER_MODE_STACK
|
|
#define RTL_WALK_USER_MODE_STACK 0x00000001
|
|
#endif
|
|
|
|
#ifndef MEM_IMAGE
|
|
#define MEM_IMAGE 0x01000000
|
|
#endif
|
|
|
|
typedef struct _MEMORY_BASIC_INFORMATION
|
|
{
|
|
PVOID BaseAddress;
|
|
PVOID AllocationBase;
|
|
ULONG AllocationProtect;
|
|
SIZE_T RegionSize;
|
|
ULONG State;
|
|
ULONG Protect;
|
|
ULONG Type;
|
|
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
|
|
|
|
typedef enum _BK_MEMORY_INFORMATION_CLASS
|
|
{
|
|
BkcthrMemoryBasicInformation = 0
|
|
} BK_MEMORY_INFORMATION_CLASS;
|
|
|
|
typedef enum _BK_NOTIFY_MODE
|
|
{
|
|
BkcthrNotifyNone = 0,
|
|
BkcthrNotifyEx
|
|
} BK_NOTIFY_MODE;
|
|
|
|
typedef NTSTATUS(NTAPI *PBK_PS_SET_CREATE_THREAD_NOTIFY_ROUTINE_EX)(_In_ PSCREATETHREADNOTIFYTYPE NotifyType,
|
|
_In_ PVOID NotifyRoutine);
|
|
|
|
#define BK_THREAD_MAX_OUTSTANDING_WORK 4096
|
|
#define BK_THREAD_CORRELATION_WINDOW_MS 5000
|
|
#define BK_THREAD_IMAGE_CACHE_SIZE 128
|
|
#define BK_THREAD_IMAGE_CACHE_TTL_MS 2000
|
|
|
|
static BK_NOTIFY_MODE g_NotifyMode = BkcthrNotifyNone;
|
|
|
|
NTSYSAPI NTSTATUS NTAPI ZwQueryInformationThread(_In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass,
|
|
_Out_writes_bytes_(ThreadInformationLength) PVOID ThreadInformation,
|
|
_In_ ULONG ThreadInformationLength, _Out_opt_ PULONG ReturnLength);
|
|
|
|
NTSYSAPI NTSTATUS NTAPI ZwOpenThread(_Out_ PHANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess,
|
|
_In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_ PCLIENT_ID ClientId);
|
|
|
|
NTSYSAPI NTSTATUS NTAPI ZwQueryVirtualMemory(_In_ HANDLE ProcessHandle, _In_opt_ PVOID BaseAddress,
|
|
_In_ BK_MEMORY_INFORMATION_CLASS MemoryInformationClass,
|
|
_Out_writes_bytes_(MemoryInformationLength) PVOID MemoryInformation,
|
|
_In_ SIZE_T MemoryInformationLength, _Out_opt_ PSIZE_T ReturnLength);
|
|
|
|
NTKERNELAPI PVOID PsGetProcessSectionBaseAddress(_In_ PEPROCESS Process);
|
|
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(_In_ HANDLE ProcessId, _Outptr_ PEPROCESS *Process);
|
|
|
|
static volatile LONG g_OutstandingWork = 0;
|
|
static volatile LONG g_DroppedWork = 0;
|
|
static KEVENT g_AllWorkDone;
|
|
static BOOLEAN g_ThreadNotifyRegistered = FALSE;
|
|
static volatile LONG g_ThreadMonitorStopping = 0;
|
|
static volatile LONG g_ThreadDropLogCounter = 0;
|
|
static volatile LONG g_ThreadLookupFailureCounter = 0;
|
|
static volatile LONG g_ThreadAllocFailureCounter = 0;
|
|
static volatile LONG g_ThreadInitFailureCounter = 0;
|
|
static PBK_PS_SET_CREATE_THREAD_NOTIFY_ROUTINE_EX g_SetNotifyRoutineEx = NULL;
|
|
static KSPIN_LOCK g_ThreadImageCacheLock;
|
|
static volatile LONG g_ThreadImageCacheWriteIndex = -1;
|
|
static ULONGLONG g_ThreadImageCacheQpcFrequency = 1;
|
|
|
|
typedef struct _BK_THREAD_IMAGE_CACHE_ENTRY
|
|
{
|
|
HANDLE ProcessId;
|
|
PVOID ImageBase;
|
|
SIZE_T ImageSize;
|
|
INT64 TimestampQpc;
|
|
} BK_THREAD_IMAGE_CACHE_ENTRY, *PBK_THREAD_IMAGE_CACHE_ENTRY;
|
|
|
|
static BK_THREAD_IMAGE_CACHE_ENTRY g_ThreadImageCache[BK_THREAD_IMAGE_CACHE_SIZE];
|
|
|
|
typedef struct _BK_THREAD_WORK
|
|
{
|
|
WORK_QUEUE_ITEM WorkItem;
|
|
HANDLE ProcessId;
|
|
HANDLE ThreadId;
|
|
HANDLE CreatorProcessId; // heuristic only
|
|
PEPROCESS Process; // referenced
|
|
} BK_THREAD_WORK, *PBK_THREAD_WORK;
|
|
|
|
static BOOLEAN BkcthrThreadTryAcquireWorkSlot(VOID)
|
|
{
|
|
LONG current;
|
|
LONG dropCounter;
|
|
|
|
for (;;)
|
|
{
|
|
current = InterlockedCompareExchange(&g_OutstandingWork, 0, 0);
|
|
if (current >= BK_THREAD_MAX_OUTSTANDING_WORK)
|
|
{
|
|
InterlockedIncrement(&g_DroppedWork);
|
|
dropCounter = InterlockedIncrement(&g_ThreadDropLogCounter);
|
|
if (dropCounter == 1 || ((dropCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL,
|
|
"BK: thread callback drop (work queue full max=%lu) totalDrops=%lu outstanding=%ld.\n",
|
|
BK_THREAD_MAX_OUTSTANDING_WORK, (ULONG)dropCounter, current);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if (InterlockedCompareExchange(&g_OutstandingWork, current + 1, current) == current)
|
|
{
|
|
if (current == 0)
|
|
{
|
|
KeClearEvent(&g_AllWorkDone);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static PBK_PS_SET_CREATE_THREAD_NOTIFY_ROUTINE_EX BkcthrResolvePsSetCreateThreadNotifyRoutineEx(VOID)
|
|
{
|
|
UNICODE_STRING routineName;
|
|
|
|
RtlInitUnicodeString(&routineName, L"PsSetCreateThreadNotifyRoutineEx");
|
|
return (PBK_PS_SET_CREATE_THREAD_NOTIFY_ROUTINE_EX)MmGetSystemRoutineAddress(&routineName);
|
|
}
|
|
|
|
static VOID BkcthrThreadReleaseWorkSlot(VOID)
|
|
{
|
|
if (InterlockedDecrement(&g_OutstandingWork) == 0)
|
|
{
|
|
KeSetEvent(&g_AllWorkDone, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
}
|
|
|
|
static ULONGLONG BkcthrThreadMsToQpc(_In_ UINT32 Milliseconds)
|
|
{
|
|
ULONGLONG ticks;
|
|
|
|
if (Milliseconds == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ticks = ((ULONGLONG)Milliseconds * g_ThreadImageCacheQpcFrequency) / 1000ULL;
|
|
return (ticks == 0) ? 1 : ticks;
|
|
}
|
|
|
|
static BOOLEAN BkcthrThreadImageCacheLookup(_In_ HANDLE ProcessId, _Out_ PVOID *ImageBase, _Out_ SIZE_T *ImageSize)
|
|
{
|
|
KIRQL oldIrql;
|
|
INT64 nowQpc;
|
|
ULONGLONG maxAgeQpc;
|
|
UINT32 i;
|
|
|
|
if (ProcessId == NULL || ImageBase == NULL || ImageSize == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
nowQpc = KeQueryPerformanceCounter(NULL).QuadPart;
|
|
maxAgeQpc = BkcthrThreadMsToQpc(BK_THREAD_IMAGE_CACHE_TTL_MS);
|
|
|
|
KeAcquireSpinLock(&g_ThreadImageCacheLock, &oldIrql);
|
|
for (i = 0; i < RTL_NUMBER_OF(g_ThreadImageCache); ++i)
|
|
{
|
|
const BK_THREAD_IMAGE_CACHE_ENTRY *entry = &g_ThreadImageCache[i];
|
|
INT64 ageQpc;
|
|
|
|
if (entry->TimestampQpc == 0 || entry->ProcessId != ProcessId || entry->ImageBase == NULL ||
|
|
entry->ImageSize == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ageQpc = nowQpc - entry->TimestampQpc;
|
|
if (ageQpc < 0 || (ULONGLONG)ageQpc > maxAgeQpc)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
*ImageBase = entry->ImageBase;
|
|
*ImageSize = entry->ImageSize;
|
|
KeReleaseSpinLock(&g_ThreadImageCacheLock, oldIrql);
|
|
return TRUE;
|
|
}
|
|
KeReleaseSpinLock(&g_ThreadImageCacheLock, oldIrql);
|
|
return FALSE;
|
|
}
|
|
|
|
static VOID BkcthrThreadImageCacheStore(_In_ HANDLE ProcessId, _In_ PVOID ImageBase, _In_ SIZE_T ImageSize)
|
|
{
|
|
KIRQL oldIrql;
|
|
LONG index;
|
|
|
|
if (ProcessId == NULL || ImageBase == NULL || ImageSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
index = InterlockedIncrement(&g_ThreadImageCacheWriteIndex);
|
|
if (index < 0)
|
|
{
|
|
index = 0;
|
|
InterlockedExchange(&g_ThreadImageCacheWriteIndex, 0);
|
|
}
|
|
|
|
KeAcquireSpinLock(&g_ThreadImageCacheLock, &oldIrql);
|
|
{
|
|
BK_THREAD_IMAGE_CACHE_ENTRY *entry = &g_ThreadImageCache[(ULONG)index % RTL_NUMBER_OF(g_ThreadImageCache)];
|
|
entry->ProcessId = ProcessId;
|
|
entry->ImageBase = ImageBase;
|
|
entry->ImageSize = ImageSize;
|
|
entry->TimestampQpc = KeQueryPerformanceCounter(NULL).QuadPart;
|
|
}
|
|
KeReleaseSpinLock(&g_ThreadImageCacheLock, oldIrql);
|
|
}
|
|
|
|
static VOID BkcthrLogThreadTelemetry(_In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _In_ HANDLE CreatorPid,
|
|
_In_ PVOID StartAddress, _In_ PVOID ImageBase, _In_ SIZE_T ImageSize,
|
|
_In_ BOOLEAN GotStart, _In_ BOOLEAN GotRange, _In_ BOOLEAN IsRemoteCreator,
|
|
_In_ BOOLEAN OutsideMainImage, _In_ UINT32 CorrelationFlags,
|
|
_In_ UINT32 CorrelationAccessMask, _In_ UINT32 CorrelationAgeMs,
|
|
_In_ ULONG StartRegionProtect, _In_ ULONG StartRegionState,
|
|
_In_ ULONG StartRegionType, _In_ NTSTATUS StartRegionStatus)
|
|
{
|
|
PVOID frames[8] = {0};
|
|
ULONG frameCount = 0;
|
|
BOOLEAN startRegionExecutable;
|
|
BOOLEAN captureFrames;
|
|
|
|
captureFrames = IsRemoteCreator || OutsideMainImage || (CorrelationFlags != 0);
|
|
if (captureFrames)
|
|
{
|
|
frameCount = RtlWalkFrameChain(frames, RTL_NUMBER_OF(frames), RTL_WALK_USER_MODE_STACK);
|
|
if (frameCount > RTL_NUMBER_OF(frames))
|
|
{
|
|
frameCount = RTL_NUMBER_OF(frames);
|
|
}
|
|
}
|
|
|
|
startRegionExecutable = BkprotIsExecutableProtection(StartRegionProtect);
|
|
|
|
BketwLogThreadEvent(ProcessId, ThreadId, CreatorPid, StartAddress, ImageBase, ImageSize, GotStart, GotRange,
|
|
IsRemoteCreator, OutsideMainImage, CorrelationFlags, CorrelationAccessMask, CorrelationAgeMs,
|
|
StartRegionProtect, StartRegionState, StartRegionType, StartRegionStatus, frameCount, frames);
|
|
|
|
{
|
|
UINT32 flags = 0;
|
|
if (GotStart)
|
|
{
|
|
flags |= BK_THREAD_FLAG_GOT_START;
|
|
}
|
|
if (GotRange)
|
|
{
|
|
flags |= BK_THREAD_FLAG_GOT_RANGE;
|
|
}
|
|
if (IsRemoteCreator)
|
|
{
|
|
flags |= BK_THREAD_FLAG_REMOTE_CREATOR;
|
|
}
|
|
if (OutsideMainImage)
|
|
{
|
|
flags |= BK_THREAD_FLAG_OUTSIDE_MAIN_IMG;
|
|
}
|
|
if (CorrelationFlags != 0)
|
|
{
|
|
flags |= BK_THREAD_FLAG_CORRELATED_INTENT;
|
|
}
|
|
if ((CorrelationFlags & BK_INTENT_PROCESS_MEMORY) != 0)
|
|
{
|
|
flags |= BK_THREAD_FLAG_CORR_MEMORY;
|
|
}
|
|
if ((CorrelationFlags & BK_INTENT_THREAD_CONTEXT) != 0)
|
|
{
|
|
flags |= BK_THREAD_FLAG_CORR_THREAD_CTX;
|
|
}
|
|
if ((CorrelationFlags & BK_INTENT_DUP_HANDLE) != 0)
|
|
{
|
|
flags |= BK_THREAD_FLAG_CORR_DUP_HANDLE;
|
|
}
|
|
if (startRegionExecutable)
|
|
{
|
|
flags |= BK_THREAD_FLAG_START_REGION_EXEC;
|
|
}
|
|
|
|
if (BkctlIsArmedFast())
|
|
{
|
|
BkctlPublishThreadEvent((UINT64)(ULONG_PTR)ProcessId, (UINT64)(ULONG_PTR)ThreadId,
|
|
(UINT64)(ULONG_PTR)CreatorPid, (UINT64)(ULONG_PTR)StartAddress,
|
|
(UINT64)(ULONG_PTR)ImageBase, (UINT64)ImageSize, flags, frameCount, frames);
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOLEAN BkcthrQueryThreadStartAddress(_In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _Out_ PVOID *StartAddress)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES oa;
|
|
CLIENT_ID cid;
|
|
HANDLE hThread = NULL;
|
|
|
|
*StartAddress = NULL;
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
cid.UniqueProcess = ProcessId;
|
|
cid.UniqueThread = ThreadId;
|
|
|
|
status = ZwOpenThread(&hThread, THREAD_QUERY_LIMITED_INFORMATION, &oa, &cid);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
status = ZwOpenThread(&hThread, THREAD_QUERY_INFORMATION, &oa, &cid);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
status =
|
|
ZwQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, StartAddress, sizeof(*StartAddress), NULL);
|
|
|
|
ZwClose(hThread);
|
|
return NT_SUCCESS(status);
|
|
}
|
|
|
|
static BOOLEAN BkcthrGetProcessImageRange(_In_ HANDLE ProcessId, _In_ PEPROCESS Process, _Out_ PVOID *ImageBase,
|
|
_Out_ SIZE_T *ImageSize)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES oa;
|
|
CLIENT_ID cid;
|
|
HANDLE hProcess = NULL;
|
|
PVOID base = PsGetProcessSectionBaseAddress(Process);
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
|
|
*ImageBase = NULL;
|
|
*ImageSize = 0;
|
|
|
|
if (BkcthrThreadImageCacheLookup(ProcessId, ImageBase, ImageSize))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (base == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
cid.UniqueProcess = ProcessId;
|
|
cid.UniqueThread = NULL;
|
|
|
|
status = ZwOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, &oa, &cid);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(hProcess, base, BkcthrMemoryBasicInformation, &mbi, sizeof(mbi), NULL);
|
|
|
|
ZwClose(hProcess);
|
|
|
|
if (!NT_SUCCESS(status) || mbi.RegionSize == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*ImageBase = base;
|
|
*ImageSize = mbi.RegionSize;
|
|
BkcthrThreadImageCacheStore(ProcessId, base, mbi.RegionSize);
|
|
return TRUE;
|
|
}
|
|
|
|
static NTSTATUS BkcthrQueryAddressRegion(_In_ HANDLE ProcessId, _In_ PVOID Address, _Out_ ULONG *Protect,
|
|
_Out_ ULONG *State, _Out_ ULONG *Type)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES oa;
|
|
CLIENT_ID cid;
|
|
HANDLE hProcess = NULL;
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
|
|
if (Protect != NULL)
|
|
{
|
|
*Protect = 0;
|
|
}
|
|
if (State != NULL)
|
|
{
|
|
*State = 0;
|
|
}
|
|
if (Type != NULL)
|
|
{
|
|
*Type = 0;
|
|
}
|
|
|
|
if (Address == NULL)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
cid.UniqueProcess = ProcessId;
|
|
cid.UniqueThread = NULL;
|
|
|
|
status = ZwOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, &oa, &cid);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(hProcess, Address, BkcthrMemoryBasicInformation, &mbi, sizeof(mbi), NULL);
|
|
ZwClose(hProcess);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
if (Protect != NULL)
|
|
{
|
|
*Protect = mbi.Protect;
|
|
}
|
|
if (State != NULL)
|
|
{
|
|
*State = mbi.State;
|
|
}
|
|
if (Type != NULL)
|
|
{
|
|
*Type = mbi.Type;
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static VOID BkcthrThreadWorkRoutine(_In_ PVOID Context)
|
|
{
|
|
PBK_THREAD_WORK w = (PBK_THREAD_WORK)Context;
|
|
PVOID threadStart = NULL;
|
|
PVOID imageBase = NULL;
|
|
SIZE_T imageSize = 0;
|
|
BOOLEAN gotStart = FALSE;
|
|
BOOLEAN gotRange = FALSE;
|
|
BOOLEAN outsideMainImage = FALSE;
|
|
BOOLEAN isRemoteCreator;
|
|
UINT32 correlationFlags = 0;
|
|
UINT32 correlationAccessMask = 0;
|
|
UINT32 correlationAgeMs = 0;
|
|
ULONG startRegionProtect = 0;
|
|
ULONG startRegionState = 0;
|
|
ULONG startRegionType = 0;
|
|
NTSTATUS startRegionStatus = STATUS_NOT_FOUND;
|
|
BOOLEAN startRegionExecutable = FALSE;
|
|
BOOLEAN startRegionNonImage = FALSE;
|
|
HANDLE creatorPidForTelemetry;
|
|
BOOLEAN hasCorrelation = FALSE;
|
|
BOOLEAN shouldCaptureDetailed = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (InterlockedCompareExchange(&g_ThreadMonitorStopping, 0, 0) != 0)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
creatorPidForTelemetry = w->CreatorProcessId;
|
|
if (creatorPidForTelemetry == NULL)
|
|
{
|
|
creatorPidForTelemetry = w->ProcessId;
|
|
}
|
|
|
|
hasCorrelation = BkhloResolveThreadCorrelation(w->ProcessId, creatorPidForTelemetry,
|
|
BK_THREAD_CORRELATION_WINDOW_MS, &creatorPidForTelemetry,
|
|
&correlationFlags, &correlationAccessMask, &correlationAgeMs);
|
|
if (!hasCorrelation)
|
|
{
|
|
correlationFlags = 0;
|
|
correlationAccessMask = 0;
|
|
correlationAgeMs = 0;
|
|
}
|
|
|
|
isRemoteCreator = (creatorPidForTelemetry != w->ProcessId);
|
|
shouldCaptureDetailed = (isRemoteCreator || hasCorrelation);
|
|
|
|
if (shouldCaptureDetailed)
|
|
{
|
|
gotStart = BkcthrQueryThreadStartAddress(w->ProcessId, w->ThreadId, &threadStart);
|
|
if (gotStart && threadStart != NULL)
|
|
{
|
|
gotRange = BkcthrGetProcessImageRange(w->ProcessId, w->Process, &imageBase, &imageSize);
|
|
}
|
|
|
|
if (gotStart && gotRange && threadStart != NULL && imageBase != NULL && imageSize != 0)
|
|
{
|
|
ULONG_PTR start = (ULONG_PTR)threadStart;
|
|
ULONG_PTR base = (ULONG_PTR)imageBase;
|
|
ULONG_PTR end = base + (ULONG_PTR)imageSize;
|
|
|
|
if (end < base)
|
|
{
|
|
outsideMainImage = TRUE;
|
|
}
|
|
else
|
|
{
|
|
outsideMainImage = (start < base) || (start >= end);
|
|
}
|
|
}
|
|
|
|
if (gotStart && threadStart != NULL)
|
|
{
|
|
startRegionStatus = BkcthrQueryAddressRegion(w->ProcessId, threadStart, &startRegionProtect,
|
|
&startRegionState, &startRegionType);
|
|
}
|
|
if (NT_SUCCESS(startRegionStatus))
|
|
{
|
|
startRegionExecutable = BkprotIsExecutableProtection(startRegionProtect);
|
|
startRegionNonImage = (startRegionType != MEM_IMAGE);
|
|
}
|
|
}
|
|
|
|
if (InterlockedCompareExchange(&g_ThreadMonitorStopping, 0, 0) != 0)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
BkcthrLogThreadTelemetry(w->ProcessId, w->ThreadId, creatorPidForTelemetry, threadStart, imageBase, imageSize,
|
|
gotStart, gotRange, isRemoteCreator, outsideMainImage, correlationFlags,
|
|
correlationAccessMask, correlationAgeMs, startRegionProtect, startRegionState,
|
|
startRegionType, startRegionStatus);
|
|
BkhloObserveThread(w->ProcessId, creatorPidForTelemetry, outsideMainImage, gotStart, startRegionExecutable,
|
|
startRegionNonImage, correlationFlags, correlationAccessMask, correlationAgeMs);
|
|
|
|
Exit:
|
|
if (w->Process)
|
|
{
|
|
ObDereferenceObject(w->Process);
|
|
}
|
|
|
|
ExFreePoolWithTag(w, 'traT');
|
|
|
|
BkcthrThreadReleaseWorkSlot();
|
|
}
|
|
|
|
VOID BkcthrThreadNotifyRoutine(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create)
|
|
{
|
|
ULONGLONG tempusStartQpc = BktmpEnter(BktmpSubsystemThreadMonitor);
|
|
LONG failureCounter;
|
|
HANDLE creatorProcessId;
|
|
UINT32 processPid32;
|
|
UINT32 creatorPid32;
|
|
UINT32 secondaryPid32;
|
|
|
|
if (!Create)
|
|
{
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
if (InterlockedCompareExchange(&g_ThreadMonitorStopping, 0, 0) != 0)
|
|
{
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
if (!BkctlIsArmedFast())
|
|
{
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
|
|
creatorProcessId = PsGetCurrentProcessId();
|
|
processPid32 = (UINT32)(ULONG_PTR)ProcessId;
|
|
creatorPid32 = (UINT32)(ULONG_PTR)creatorProcessId;
|
|
secondaryPid32 = (creatorPid32 != processPid32) ? creatorPid32 : 0;
|
|
if (!BkctlHasPidInterest(processPid32, secondaryPid32, BK_STREAM_THREAD))
|
|
{
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
|
|
if (!BkcthrThreadTryAcquireWorkSlot())
|
|
{
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
|
|
PEPROCESS process = NULL;
|
|
NTSTATUS status = PsLookupProcessByProcessId(ProcessId, &process);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
failureCounter = InterlockedIncrement(&g_ThreadLookupFailureCounter);
|
|
if (failureCounter == 1 || ((failureCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL,
|
|
"BK: thread callback lookup failure pid=%p tid=%p status=0x%08X total=%lu.\n", ProcessId,
|
|
ThreadId, status, (ULONG)failureCounter);
|
|
}
|
|
BkcthrThreadReleaseWorkSlot();
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
|
|
PBK_THREAD_WORK w = (PBK_THREAD_WORK)BkpoolAllocateCompat(POOL_FLAG_NON_PAGED, sizeof(*w), 'traT');
|
|
if (!w)
|
|
{
|
|
failureCounter = InterlockedIncrement(&g_ThreadAllocFailureCounter);
|
|
if (failureCounter == 1 || ((failureCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
|
|
"BK: thread callback alloc failure pid=%p tid=%p total=%lu.\n", ProcessId, ThreadId,
|
|
(ULONG)failureCounter);
|
|
}
|
|
ObDereferenceObject(process);
|
|
BkcthrThreadReleaseWorkSlot();
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(w, sizeof(*w));
|
|
w->ProcessId = ProcessId;
|
|
w->ThreadId = ThreadId;
|
|
w->CreatorProcessId = creatorProcessId; // heuristic only
|
|
w->Process = process; // already referenced by lookup
|
|
|
|
ExInitializeWorkItem(&w->WorkItem, BkcthrThreadWorkRoutine, w);
|
|
|
|
ExQueueWorkItem(&w->WorkItem, DelayedWorkQueue);
|
|
BktmpLeave(BktmpSubsystemThreadMonitor, tempusStartQpc);
|
|
}
|
|
|
|
NTSTATUS
|
|
BkcthrInitialize(VOID)
|
|
{
|
|
NTSTATUS status;
|
|
LONG failureCounter;
|
|
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
return STATUS_INVALID_DEVICE_STATE;
|
|
}
|
|
if (g_ThreadNotifyRegistered)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
InterlockedExchange(&g_ThreadMonitorStopping, 0);
|
|
InterlockedExchange(&g_OutstandingWork, 0);
|
|
InterlockedExchange(&g_DroppedWork, 0);
|
|
KeInitializeEvent(&g_AllWorkDone, NotificationEvent, TRUE);
|
|
KeInitializeSpinLock(&g_ThreadImageCacheLock);
|
|
RtlZeroMemory(g_ThreadImageCache, sizeof(g_ThreadImageCache));
|
|
InterlockedExchange(&g_ThreadImageCacheWriteIndex, -1);
|
|
{
|
|
LARGE_INTEGER freq = KeQueryPerformanceCounter(NULL);
|
|
g_ThreadImageCacheQpcFrequency = (freq.QuadPart > 0) ? (ULONGLONG)freq.QuadPart : 1;
|
|
}
|
|
|
|
g_SetNotifyRoutineEx = BkcthrResolvePsSetCreateThreadNotifyRoutineEx();
|
|
if (g_SetNotifyRoutineEx == NULL)
|
|
{
|
|
return STATUS_PROCEDURE_NOT_FOUND;
|
|
}
|
|
|
|
status = g_SetNotifyRoutineEx(PsCreateThreadNotifyNonSystem, (PVOID)BkcthrThreadNotifyRoutine);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
failureCounter = InterlockedIncrement(&g_ThreadInitFailureCounter);
|
|
if (failureCounter == 1 || ((failureCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
|
|
"BK: thread monitor callback registration failure status=0x%08X total=%lu.\n", status,
|
|
(ULONG)failureCounter);
|
|
}
|
|
g_SetNotifyRoutineEx = NULL;
|
|
return status;
|
|
}
|
|
|
|
g_ThreadNotifyRegistered = TRUE;
|
|
g_NotifyMode = BkcthrNotifyEx;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID BkcthrUninitialize(VOID)
|
|
{
|
|
NTSTATUS status;
|
|
LARGE_INTEGER waitInterval;
|
|
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
return;
|
|
}
|
|
if (!g_ThreadNotifyRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InterlockedExchange(&g_ThreadMonitorStopping, 1);
|
|
status = PsRemoveCreateThreadNotifyRoutine(BkcthrThreadNotifyRoutine);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
|
|
"BK: thread monitor callback removal failed; monitor remains registered (status=0x%08X).\n", status);
|
|
return;
|
|
}
|
|
|
|
g_NotifyMode = BkcthrNotifyNone;
|
|
g_ThreadNotifyRegistered = FALSE;
|
|
g_SetNotifyRoutineEx = NULL;
|
|
|
|
waitInterval.QuadPart = -(LONGLONG)1000 * 10000;
|
|
while (InterlockedCompareExchange(&g_OutstandingWork, 0, 0) != 0)
|
|
{
|
|
NTSTATUS waitStatus;
|
|
|
|
waitStatus = KeWaitForSingleObject(&g_AllWorkDone, Executive, KernelMode, FALSE, &waitInterval);
|
|
if (waitStatus == STATUS_TIMEOUT)
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "BK: thread monitor draining (outstanding=%ld).\n",
|
|
InterlockedCompareExchange(&g_OutstandingWork, 0, 0));
|
|
}
|
|
}
|
|
RtlZeroMemory(g_ThreadImageCache, sizeof(g_ThreadImageCache));
|
|
InterlockedExchange(&g_ThreadImageCacheWriteIndex, -1);
|
|
InterlockedExchange(&g_ThreadMonitorStopping, 0);
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "BK: thread monitor uninitialized (dropped=%ld).\n",
|
|
InterlockedCompareExchange(&g_DroppedWork, 0, 0));
|
|
}
|
|
|
|
BOOLEAN
|
|
BkcthrSelfCheck(VOID)
|
|
{
|
|
LONG outstanding;
|
|
|
|
if (!g_ThreadNotifyRegistered)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (g_NotifyMode != BkcthrNotifyEx || g_SetNotifyRoutineEx == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
outstanding = InterlockedCompareExchange(&g_OutstandingWork, 0, 0);
|
|
if (outstanding < 0 || outstanding > BK_THREAD_MAX_OUTSTANDING_WORK)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|