mirror of
https://github.com/8damon/Blackbird-Platform
synced 2026-06-21 13:41:12 +00:00
2936 lines
97 KiB
C
2936 lines
97 KiB
C
#include <ntddk.h>
|
|
#include <ntimage.h>
|
|
#include <intrin.h>
|
|
#include <ntstrsafe.h>
|
|
#include "..\core\control.h"
|
|
#include "..\core\tempus_debug.h"
|
|
#include "..\core\protection_utils.h"
|
|
#include "..\core\pool_compat.h"
|
|
#include "..\core\unicode_utils.h"
|
|
#include "..\telemetry\etw.h"
|
|
#include "..\monitors\apc_monitor.h"
|
|
#include "process_monitor.h"
|
|
#include "..\correlation\intent_store.h"
|
|
#include "handle_monitor.h"
|
|
|
|
#ifndef PROCESS_VM_READ
|
|
#define PROCESS_VM_READ 0x0010
|
|
#endif
|
|
|
|
#ifndef PROCESS_VM_WRITE
|
|
#define PROCESS_VM_WRITE 0x0020
|
|
#endif
|
|
|
|
#ifndef PROCESS_VM_OPERATION
|
|
#define PROCESS_VM_OPERATION 0x0008
|
|
#endif
|
|
|
|
#ifndef PROCESS_CREATE_THREAD
|
|
#define PROCESS_CREATE_THREAD 0x0002
|
|
#endif
|
|
|
|
#ifndef PROCESS_QUERY_LIMITED_INFORMATION
|
|
#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
|
|
#endif
|
|
|
|
#ifndef PROCESS_QUERY_INFORMATION
|
|
#define PROCESS_QUERY_INFORMATION 0x0400
|
|
#endif
|
|
|
|
#ifndef PROCESS_SET_INFORMATION
|
|
#define PROCESS_SET_INFORMATION 0x0200
|
|
#endif
|
|
|
|
#ifndef PROCESS_CREATE_PROCESS
|
|
#define PROCESS_CREATE_PROCESS 0x0080
|
|
#endif
|
|
|
|
#ifndef PROCESS_TERMINATE
|
|
#define PROCESS_TERMINATE 0x0001
|
|
#endif
|
|
|
|
#ifndef THREAD_SET_CONTEXT
|
|
#define THREAD_SET_CONTEXT 0x0010
|
|
#endif
|
|
|
|
#ifndef THREAD_SUSPEND_RESUME
|
|
#define THREAD_SUSPEND_RESUME 0x0002
|
|
#endif
|
|
|
|
#ifndef THREAD_GET_CONTEXT
|
|
#define THREAD_GET_CONTEXT 0x0008
|
|
#endif
|
|
|
|
#ifndef THREAD_QUERY_INFORMATION
|
|
#define THREAD_QUERY_INFORMATION 0x0040
|
|
#endif
|
|
|
|
#ifndef THREAD_QUERY_LIMITED_INFORMATION
|
|
#define THREAD_QUERY_LIMITED_INFORMATION 0x0800
|
|
#endif
|
|
|
|
#ifndef THREAD_ALL_ACCESS
|
|
#define THREAD_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF)
|
|
#endif
|
|
|
|
#define BK_PROTECTED_PROCESS_ALLOWED_ACCESS (PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE)
|
|
#define BK_PROTECTED_THREAD_ALLOWED_ACCESS (THREAD_QUERY_LIMITED_INFORMATION | SYNCHRONIZE)
|
|
|
|
#ifndef MEM_COMMIT
|
|
#define MEM_COMMIT 0x00001000
|
|
#endif
|
|
|
|
#ifndef MEM_IMAGE
|
|
#define MEM_IMAGE 0x01000000
|
|
#endif
|
|
|
|
#ifndef IMAGE_DIRECTORY_ENTRY_EXPORT
|
|
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
|
|
#endif
|
|
|
|
#ifndef IMAGE_DIRECTORY_ENTRY_EXCEPTION
|
|
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
|
|
#endif
|
|
|
|
#ifndef ThreadBasicInformation
|
|
#define ThreadBasicInformation ((THREADINFOCLASS)0)
|
|
#endif
|
|
|
|
#ifndef _MEMORY_BASIC_INFORMATION
|
|
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;
|
|
#endif
|
|
|
|
typedef struct _BK_THREAD_BASIC_INFORMATION
|
|
{
|
|
NTSTATUS ExitStatus;
|
|
PVOID TebBaseAddress;
|
|
CLIENT_ID ClientId;
|
|
KAFFINITY AffinityMask;
|
|
KPRIORITY Priority;
|
|
KPRIORITY BasePriority;
|
|
} BK_THREAD_BASIC_INFORMATION, *PBK_THREAD_BASIC_INFORMATION;
|
|
|
|
typedef struct _BK_NT_TIB
|
|
{
|
|
PVOID ExceptionList;
|
|
PVOID StackBase;
|
|
PVOID StackLimit;
|
|
PVOID SubSystemTib;
|
|
PVOID FiberData;
|
|
PVOID ArbitraryUserPointer;
|
|
PVOID Self;
|
|
} BK_NT_TIB, *PBK_NT_TIB;
|
|
|
|
typedef enum _BK_MEMORY_INFORMATION_CLASS
|
|
{
|
|
BkchdlMemoryBasicInformation = 0,
|
|
BkchdlMemorySectionName = 2
|
|
} BK_MEMORY_INFORMATION_CLASS;
|
|
|
|
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);
|
|
NTSYSAPI NTSTATUS NTAPI ZwOpenThread(_Out_ PHANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess,
|
|
_In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_ PCLIENT_ID ClientId);
|
|
NTSYSAPI NTSTATUS NTAPI ZwQueryInformationThread(_In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass,
|
|
_Out_writes_bytes_(ThreadInformationLength) PVOID ThreadInformation,
|
|
_In_ ULONG ThreadInformationLength, _Out_opt_ PULONG ReturnLength);
|
|
NTSYSAPI VOID NTAPI RtlCaptureContext(_Out_ PCONTEXT ContextRecord);
|
|
|
|
#ifndef RTL_WALK_USER_MODE_STACK
|
|
#define RTL_WALK_USER_MODE_STACK 0x00000001
|
|
#endif
|
|
|
|
NTKERNELAPI HANDLE PsGetThreadProcessId(_In_ PETHREAD Thread);
|
|
NTKERNELAPI HANDLE PsGetThreadId(_In_ PETHREAD Thread);
|
|
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(_In_ HANDLE ProcessId, _Outptr_ PEPROCESS *Process);
|
|
NTKERNELAPI PCHAR PsGetProcessImageFileName(_In_ PEPROCESS Process);
|
|
NTKERNELAPI NTSTATUS MmCopyVirtualMemory(_In_ PEPROCESS FromProcess, _In_ const VOID *FromAddress,
|
|
_In_ PEPROCESS ToProcess, _Out_writes_bytes_(BufferSize) PVOID ToAddress,
|
|
_In_ SIZE_T BufferSize, _In_ KPROCESSOR_MODE PreviousMode,
|
|
_Out_ PSIZE_T NumberOfBytesCopied);
|
|
|
|
#define BK_HANDLE_MAX_OUTSTANDING_WORK 2048
|
|
#define BK_DEEP_CAPTURE_MAX_BYTES 64
|
|
#define BK_DEEP_CACHE_RING_SIZE 64
|
|
#define BK_DEEP_CACHE_TTL_MS 3000
|
|
#define BK_SYSCALL_EXPORT_MATCH_WINDOW_BYTES 0x40u
|
|
|
|
static PVOID g_ProcessObRegistrationHandle = NULL;
|
|
static OB_OPERATION_REGISTRATION g_OperationRegistration[2];
|
|
static OB_CALLBACK_REGISTRATION g_CallbackRegistration;
|
|
static UNICODE_STRING g_CallbackAltitude;
|
|
static const WCHAR g_CallbackAltitudeBuffer[] = L"385000.424242";
|
|
static volatile LONG g_HandleOutstandingWork = 0;
|
|
static volatile LONG g_HandleDroppedWork = 0;
|
|
static volatile LONG g_HandleStackCaptureFaults = 0;
|
|
static KEVENT g_HandleAllWorkDone;
|
|
static volatile LONG g_HandleMonitorStopping = 0;
|
|
static BOOLEAN g_HandleMonitorRegistered = FALSE;
|
|
static volatile LONG g_HandleCallbackDropLogCounter = 0;
|
|
static volatile LONG g_HandleAllocFailureCounter = 0;
|
|
static volatile LONG g_HandleStackFaultLogCounter = 0;
|
|
static KSPIN_LOCK g_DeepCacheLock;
|
|
static volatile LONG g_DeepCacheWriteIndex = -1;
|
|
static ULONGLONG g_DeepCacheQpcFrequency = 1;
|
|
|
|
typedef struct _BK_DEEP_CACHE_ENTRY
|
|
{
|
|
UINT64 CallerPid;
|
|
UINT64 AllocationBase;
|
|
UINT64 SampleAddress;
|
|
UINT64 RegionSize;
|
|
ULONG RegionProtect;
|
|
ULONG RegionState;
|
|
ULONG RegionType;
|
|
UCHAR Sample[BK_DEEP_CAPTURE_MAX_BYTES];
|
|
UINT32 SampleSize;
|
|
INT64 TimestampQpc;
|
|
} BK_DEEP_CACHE_ENTRY, *PBK_DEEP_CACHE_ENTRY;
|
|
|
|
static BK_DEEP_CACHE_ENTRY g_DeepCache[BK_DEEP_CACHE_RING_SIZE];
|
|
|
|
/*
|
|
* Hot-path debug tracing is disabled by default to prevent KD console flooding.
|
|
* Define BK_VERBOSE_HOTPATH_DEBUG=1 for local deep diagnostics.
|
|
*/
|
|
#if !defined(BK_VERBOSE_HOTPATH_DEBUG)
|
|
#define BK_VERBOSE_HOTPATH_DEBUG 0
|
|
#endif
|
|
|
|
#if defined(DBG) && DBG && BK_VERBOSE_HOTPATH_DEBUG
|
|
#define BK_DBG_PRINT(_level, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, (_level), __VA_ARGS__)
|
|
#else
|
|
#define BK_DBG_PRINT(_level, ...) ((void)0)
|
|
#endif
|
|
|
|
typedef struct _BK_HANDLE_WORK
|
|
{
|
|
WORK_QUEUE_ITEM WorkItem;
|
|
HANDLE CallerPid;
|
|
HANDLE CallerTid;
|
|
HANDLE TargetPid;
|
|
HANDLE TargetTid;
|
|
UINT64 ObjectAddress;
|
|
ACCESS_MASK DesiredAccess;
|
|
BOOLEAN IsThreadObject;
|
|
BOOLEAN IsDuplicateOperation;
|
|
UINT32 CaptureFlags;
|
|
ULONG FrameCount;
|
|
PVOID Frames[8];
|
|
ULONG FullFrameCount;
|
|
PVOID FullFrames[BK_MAX_FULL_EVENT_FRAMES];
|
|
UINT64 RegRax;
|
|
UINT64 RegRbx;
|
|
UINT64 RegRcx;
|
|
UINT64 RegRdx;
|
|
UINT64 RegRsi;
|
|
UINT64 RegRdi;
|
|
UINT64 RegRbp;
|
|
UINT64 RegRsp;
|
|
UINT64 RegR8;
|
|
UINT64 RegR9;
|
|
UINT64 RegR10;
|
|
UINT64 RegR11;
|
|
UINT64 RegR12;
|
|
UINT64 RegR13;
|
|
UINT64 RegR14;
|
|
UINT64 RegR15;
|
|
UINT64 RegRip;
|
|
UINT64 RegEFlags;
|
|
UINT64 RegDr0;
|
|
UINT64 RegDr1;
|
|
UINT64 RegDr2;
|
|
UINT64 RegDr3;
|
|
UINT64 RegDr6;
|
|
UINT64 RegDr7;
|
|
UINT64 EarlyOriginAddress;
|
|
ULONG EarlyOriginSampleSize;
|
|
UCHAR EarlyOriginSample[BK_DEEP_CAPTURE_MAX_BYTES];
|
|
} BK_HANDLE_WORK, *PBK_HANDLE_WORK;
|
|
|
|
typedef enum _BK_HANDLE_CLASSIFICATION
|
|
{
|
|
BkchdlHandleUnknown = 0,
|
|
BkchdlHandleLegitimateSyscall,
|
|
BkchdlHandleDirectSyscallSuspect
|
|
} BK_HANDLE_CLASSIFICATION;
|
|
|
|
typedef struct _BK_HANDLE_TELEMETRY
|
|
{
|
|
PVOID OriginAddress;
|
|
PVOID AllocationBase;
|
|
SIZE_T RegionSize;
|
|
ULONG OriginProtect;
|
|
ULONG RegionState;
|
|
ULONG RegionType;
|
|
WCHAR OriginPath[512];
|
|
ULONG FrameCount;
|
|
PVOID Frames[8];
|
|
NTSTATUS OpenProcessStatus;
|
|
NTSTATUS BasicInfoStatus;
|
|
NTSTATUS SectionNameStatus;
|
|
ULONG DeepSampleSize;
|
|
UCHAR DeepSample[BK_DEEP_CAPTURE_MAX_BYTES];
|
|
BOOLEAN DeepPathCandidate;
|
|
BOOLEAN DeepPathCaptured;
|
|
BOOLEAN DeepPathCacheHit;
|
|
BOOLEAN ReturnAddressValid;
|
|
BOOLEAN StackValidated;
|
|
BOOLEAN StackSpoofSuspect;
|
|
BOOLEAN SyscallExportChecked;
|
|
BOOLEAN SyscallExportMatch;
|
|
BOOLEAN ModuleChainChecked;
|
|
BOOLEAN ModuleChainSane;
|
|
BOOLEAN UnwindMetadataChecked;
|
|
BOOLEAN UnwindMetadataValid;
|
|
BOOLEAN TebStackBoundsChecked;
|
|
BOOLEAN TebStackBoundsValid;
|
|
BOOLEAN FramesOutsideTebStack;
|
|
UINT32 CaptureFlags;
|
|
ULONG FullFrameCount;
|
|
PVOID FullFrames[BK_MAX_FULL_EVENT_FRAMES];
|
|
UINT64 RegRax;
|
|
UINT64 RegRbx;
|
|
UINT64 RegRcx;
|
|
UINT64 RegRdx;
|
|
UINT64 RegRsi;
|
|
UINT64 RegRdi;
|
|
UINT64 RegRbp;
|
|
UINT64 RegRsp;
|
|
UINT64 RegR8;
|
|
UINT64 RegR9;
|
|
UINT64 RegR10;
|
|
UINT64 RegR11;
|
|
UINT64 RegR12;
|
|
UINT64 RegR13;
|
|
UINT64 RegR14;
|
|
UINT64 RegR15;
|
|
UINT64 RegRip;
|
|
UINT64 RegEFlags;
|
|
UINT64 RegDr0;
|
|
UINT64 RegDr1;
|
|
UINT64 RegDr2;
|
|
UINT64 RegDr3;
|
|
UINT64 RegDr6;
|
|
UINT64 RegDr7;
|
|
UINT64 StackSnapshotAddress;
|
|
ULONG StackSnapshotSize;
|
|
UCHAR StackSnapshot[BK_MAX_STACK_SNAPSHOT_BYTES];
|
|
} BK_HANDLE_TELEMETRY, *PBK_HANDLE_TELEMETRY;
|
|
|
|
static VOID BkchdlCaptureRegisterSnapshot(_Out_ PBK_HANDLE_WORK Work)
|
|
{
|
|
#if defined(_M_AMD64)
|
|
CONTEXT context;
|
|
|
|
if (Work == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(&context, sizeof(context));
|
|
RtlCaptureContext(&context);
|
|
|
|
Work->RegRax = context.Rax;
|
|
Work->RegRbx = context.Rbx;
|
|
Work->RegRcx = context.Rcx;
|
|
Work->RegRdx = context.Rdx;
|
|
Work->RegRsi = context.Rsi;
|
|
Work->RegRdi = context.Rdi;
|
|
Work->RegRbp = context.Rbp;
|
|
Work->RegRsp = context.Rsp;
|
|
Work->RegR8 = context.R8;
|
|
Work->RegR9 = context.R9;
|
|
Work->RegR10 = context.R10;
|
|
Work->RegR11 = context.R11;
|
|
Work->RegR12 = context.R12;
|
|
Work->RegR13 = context.R13;
|
|
Work->RegR14 = context.R14;
|
|
Work->RegR15 = context.R15;
|
|
Work->RegRip = context.Rip;
|
|
Work->RegEFlags = context.EFlags;
|
|
Work->CaptureFlags |= BK_HANDLE_CAPTURE_CONTEXT_VALID;
|
|
|
|
__try
|
|
{
|
|
Work->RegDr0 = __readdr(0);
|
|
Work->RegDr1 = __readdr(1);
|
|
Work->RegDr2 = __readdr(2);
|
|
Work->RegDr3 = __readdr(3);
|
|
Work->RegDr6 = __readdr(6);
|
|
Work->RegDr7 = __readdr(7);
|
|
Work->CaptureFlags |= BK_HANDLE_CAPTURE_DEBUG_REGS_VALID;
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
#else
|
|
UNREFERENCED_PARAMETER(Work);
|
|
#endif
|
|
}
|
|
|
|
static VOID BkchdlApplyWorkCaptureToTelemetry(_In_opt_ const BK_HANDLE_WORK *Work,
|
|
_Inout_ PBK_HANDLE_TELEMETRY Telemetry)
|
|
{
|
|
ULONG safeFullCount;
|
|
|
|
if (Work == NULL || Telemetry == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Telemetry->CaptureFlags = Work->CaptureFlags;
|
|
|
|
safeFullCount = (Work->FullFrameCount > RTL_NUMBER_OF(Telemetry->FullFrames)) ? RTL_NUMBER_OF(Telemetry->FullFrames)
|
|
: Work->FullFrameCount;
|
|
Telemetry->FullFrameCount = safeFullCount;
|
|
if (safeFullCount != 0)
|
|
{
|
|
RtlCopyMemory(Telemetry->FullFrames, Work->FullFrames, safeFullCount * sizeof(PVOID));
|
|
Telemetry->CaptureFlags |= BK_HANDLE_CAPTURE_FULL_FRAMES_VALID;
|
|
}
|
|
|
|
Telemetry->RegRax = Work->RegRax;
|
|
Telemetry->RegRbx = Work->RegRbx;
|
|
Telemetry->RegRcx = Work->RegRcx;
|
|
Telemetry->RegRdx = Work->RegRdx;
|
|
Telemetry->RegRsi = Work->RegRsi;
|
|
Telemetry->RegRdi = Work->RegRdi;
|
|
Telemetry->RegRbp = Work->RegRbp;
|
|
Telemetry->RegRsp = Work->RegRsp;
|
|
Telemetry->RegR8 = Work->RegR8;
|
|
Telemetry->RegR9 = Work->RegR9;
|
|
Telemetry->RegR10 = Work->RegR10;
|
|
Telemetry->RegR11 = Work->RegR11;
|
|
Telemetry->RegR12 = Work->RegR12;
|
|
Telemetry->RegR13 = Work->RegR13;
|
|
Telemetry->RegR14 = Work->RegR14;
|
|
Telemetry->RegR15 = Work->RegR15;
|
|
Telemetry->RegRip = Work->RegRip;
|
|
Telemetry->RegEFlags = Work->RegEFlags;
|
|
Telemetry->RegDr0 = Work->RegDr0;
|
|
Telemetry->RegDr1 = Work->RegDr1;
|
|
Telemetry->RegDr2 = Work->RegDr2;
|
|
Telemetry->RegDr3 = Work->RegDr3;
|
|
Telemetry->RegDr6 = Work->RegDr6;
|
|
Telemetry->RegDr7 = Work->RegDr7;
|
|
}
|
|
|
|
static VOID BkchdlCaptureStackSnapshot(_In_ PEPROCESS SourceProcess, _In_ UINT64 StackPointer,
|
|
_Inout_ PBK_HANDLE_TELEMETRY Telemetry)
|
|
{
|
|
NTSTATUS status;
|
|
SIZE_T bytesRead = 0;
|
|
PEPROCESS localProcess;
|
|
|
|
if (SourceProcess == NULL || Telemetry == NULL || StackPointer == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
localProcess = PsGetCurrentProcess();
|
|
status = MmCopyVirtualMemory(SourceProcess, (PVOID)(ULONG_PTR)StackPointer, localProcess, Telemetry->StackSnapshot,
|
|
sizeof(Telemetry->StackSnapshot), KernelMode, &bytesRead);
|
|
if (!NT_SUCCESS(status) || bytesRead == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bytesRead > RTL_NUMBER_OF(Telemetry->StackSnapshot))
|
|
{
|
|
bytesRead = RTL_NUMBER_OF(Telemetry->StackSnapshot);
|
|
}
|
|
|
|
Telemetry->StackSnapshotAddress = StackPointer;
|
|
Telemetry->StackSnapshotSize = (ULONG)bytesRead;
|
|
Telemetry->CaptureFlags |= BK_HANDLE_CAPTURE_STACK_SNAPSHOT_VALID;
|
|
}
|
|
|
|
static BOOLEAN BkchdlHandleTryAcquireWorkSlot(VOID)
|
|
{
|
|
LONG current;
|
|
|
|
for (;;)
|
|
{
|
|
current = InterlockedCompareExchange(&g_HandleOutstandingWork, 0, 0);
|
|
if (current >= BK_HANDLE_MAX_OUTSTANDING_WORK)
|
|
{
|
|
InterlockedIncrement(&g_HandleDroppedWork);
|
|
BK_DBG_PRINT(DPFLTR_WARNING_LEVEL, "BK[DBG]: handle monitor work queue full (max=%lu).\n",
|
|
BK_HANDLE_MAX_OUTSTANDING_WORK);
|
|
return FALSE;
|
|
}
|
|
|
|
if (InterlockedCompareExchange(&g_HandleOutstandingWork, current + 1, current) == current)
|
|
{
|
|
if (current == 0)
|
|
{
|
|
KeClearEvent(&g_HandleAllWorkDone);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static VOID BkchdlHandleReleaseWorkSlot(VOID)
|
|
{
|
|
if (InterlockedDecrement(&g_HandleOutstandingWork) == 0)
|
|
{
|
|
KeSetEvent(&g_HandleAllWorkDone, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
}
|
|
|
|
static ULONGLONG BkchdlDeepCacheMsToQpc(_In_ UINT32 Ms)
|
|
{
|
|
ULONGLONG ticks;
|
|
|
|
if (Ms == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ticks = ((ULONGLONG)Ms * g_DeepCacheQpcFrequency) / 1000ULL;
|
|
return (ticks == 0) ? 1 : ticks;
|
|
}
|
|
|
|
static BOOLEAN BkchdlDeepCacheLookup(_In_ HANDLE CallerPid, _In_ PVOID AllocationBase, _In_ PVOID SampleAddress,
|
|
_In_ SIZE_T RegionSize, _In_ ULONG RegionProtect, _In_ ULONG RegionState,
|
|
_In_ ULONG RegionType, _Out_writes_bytes_(BK_DEEP_CAPTURE_MAX_BYTES) UCHAR *Sample,
|
|
_Out_ UINT32 *SampleSize)
|
|
{
|
|
KIRQL oldIrql;
|
|
UINT32 i;
|
|
INT64 nowQpc;
|
|
ULONGLONG maxAgeQpc;
|
|
|
|
if (Sample == NULL || SampleSize == NULL || AllocationBase == NULL || SampleAddress == NULL || RegionSize == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
nowQpc = KeQueryPerformanceCounter(NULL).QuadPart;
|
|
maxAgeQpc = BkchdlDeepCacheMsToQpc(BK_DEEP_CACHE_TTL_MS);
|
|
KeAcquireSpinLock(&g_DeepCacheLock, &oldIrql);
|
|
for (i = 0; i < RTL_NUMBER_OF(g_DeepCache); ++i)
|
|
{
|
|
const BK_DEEP_CACHE_ENTRY *e = &g_DeepCache[i];
|
|
INT64 ageQpc;
|
|
UINT32 safeSize;
|
|
|
|
if (e->TimestampQpc == 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (e->CallerPid != (UINT64)(ULONG_PTR)CallerPid || e->AllocationBase != (UINT64)(ULONG_PTR)AllocationBase ||
|
|
e->SampleAddress != (UINT64)(ULONG_PTR)SampleAddress || e->RegionSize != (UINT64)RegionSize ||
|
|
e->RegionProtect != RegionProtect || e->RegionState != RegionState || e->RegionType != RegionType)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ageQpc = nowQpc - e->TimestampQpc;
|
|
if (ageQpc < 0 || (ULONGLONG)ageQpc > maxAgeQpc)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
safeSize = (e->SampleSize > BK_DEEP_CAPTURE_MAX_BYTES) ? BK_DEEP_CAPTURE_MAX_BYTES : e->SampleSize;
|
|
*SampleSize = safeSize;
|
|
if (safeSize != 0)
|
|
{
|
|
RtlCopyMemory(Sample, e->Sample, safeSize);
|
|
}
|
|
KeReleaseSpinLock(&g_DeepCacheLock, oldIrql);
|
|
return TRUE;
|
|
}
|
|
KeReleaseSpinLock(&g_DeepCacheLock, oldIrql);
|
|
return FALSE;
|
|
}
|
|
|
|
static VOID BkchdlDeepCacheStore(_In_ HANDLE CallerPid, _In_ PVOID AllocationBase, _In_ PVOID SampleAddress,
|
|
_In_ SIZE_T RegionSize, _In_ ULONG RegionProtect, _In_ ULONG RegionState,
|
|
_In_ ULONG RegionType, _In_reads_bytes_(SampleSize) const UCHAR *Sample,
|
|
_In_ UINT32 SampleSize)
|
|
{
|
|
LONG idx;
|
|
UINT32 slot;
|
|
KIRQL oldIrql;
|
|
UINT32 safeSize;
|
|
|
|
if (AllocationBase == NULL || SampleAddress == NULL || RegionSize == 0 || Sample == NULL || SampleSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
safeSize = (SampleSize > BK_DEEP_CAPTURE_MAX_BYTES) ? BK_DEEP_CAPTURE_MAX_BYTES : SampleSize;
|
|
|
|
idx = InterlockedIncrement(&g_DeepCacheWriteIndex);
|
|
if (idx < 0)
|
|
{
|
|
idx = 0;
|
|
}
|
|
slot = (UINT32)idx % RTL_NUMBER_OF(g_DeepCache);
|
|
|
|
KeAcquireSpinLock(&g_DeepCacheLock, &oldIrql);
|
|
g_DeepCache[slot].CallerPid = (UINT64)(ULONG_PTR)CallerPid;
|
|
g_DeepCache[slot].AllocationBase = (UINT64)(ULONG_PTR)AllocationBase;
|
|
g_DeepCache[slot].SampleAddress = (UINT64)(ULONG_PTR)SampleAddress;
|
|
g_DeepCache[slot].RegionSize = (UINT64)RegionSize;
|
|
g_DeepCache[slot].RegionProtect = RegionProtect;
|
|
g_DeepCache[slot].RegionState = RegionState;
|
|
g_DeepCache[slot].RegionType = RegionType;
|
|
g_DeepCache[slot].SampleSize = safeSize;
|
|
RtlCopyMemory(g_DeepCache[slot].Sample, Sample, safeSize);
|
|
g_DeepCache[slot].TimestampQpc = KeQueryPerformanceCounter(NULL).QuadPart;
|
|
KeReleaseSpinLock(&g_DeepCacheLock, oldIrql);
|
|
}
|
|
|
|
static BOOLEAN BkchdlReadProcessBytes(_In_ PEPROCESS SourceProcess, _In_ const VOID *Address,
|
|
_Out_writes_bytes_(Size) VOID *Buffer, _In_ SIZE_T Size)
|
|
{
|
|
NTSTATUS status;
|
|
SIZE_T bytesRead = 0;
|
|
PEPROCESS localProcess;
|
|
|
|
if (SourceProcess == NULL || Address == NULL || Buffer == NULL || Size == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
localProcess = PsGetCurrentProcess();
|
|
status = MmCopyVirtualMemory(SourceProcess, Address, localProcess, Buffer, Size, KernelMode, &bytesRead);
|
|
return NT_SUCCESS(status) && bytesRead == Size;
|
|
}
|
|
|
|
static VOID BkchdlCaptureImmediateOriginSample(_In_ HANDLE CallerProcessId, _In_ PVOID OriginAddress,
|
|
_Inout_ PBK_HANDLE_WORK Work)
|
|
{
|
|
NTSTATUS status;
|
|
PEPROCESS sourceProcess = NULL;
|
|
PEPROCESS localProcess;
|
|
SIZE_T bytesRead = 0;
|
|
|
|
if (CallerProcessId == NULL || OriginAddress == NULL || Work == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
status = PsLookupProcessByProcessId(CallerProcessId, &sourceProcess);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return;
|
|
}
|
|
|
|
localProcess = PsGetCurrentProcess();
|
|
status = MmCopyVirtualMemory(sourceProcess, OriginAddress, localProcess, Work->EarlyOriginSample,
|
|
sizeof(Work->EarlyOriginSample), KernelMode, &bytesRead);
|
|
ObDereferenceObject(sourceProcess);
|
|
if (!NT_SUCCESS(status) || bytesRead == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bytesRead > sizeof(Work->EarlyOriginSample))
|
|
{
|
|
bytesRead = sizeof(Work->EarlyOriginSample);
|
|
}
|
|
|
|
Work->EarlyOriginAddress = (UINT64)(ULONG_PTR)OriginAddress;
|
|
Work->EarlyOriginSampleSize = (ULONG)bytesRead;
|
|
}
|
|
|
|
static CHAR BkchdlAsciiUpper(_In_ CHAR Value)
|
|
{
|
|
if (Value >= 'a' && Value <= 'z')
|
|
{
|
|
return (CHAR)(Value - ('a' - 'A'));
|
|
}
|
|
return Value;
|
|
}
|
|
|
|
static BOOLEAN BkchdlAsciiEqualsInsensitive(_In_z_ const CHAR *Left, _In_z_ const CHAR *Right)
|
|
{
|
|
SIZE_T i;
|
|
|
|
if (Left == NULL || Right == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0;; ++i)
|
|
{
|
|
CHAR a = BkchdlAsciiUpper(Left[i]);
|
|
CHAR b = BkchdlAsciiUpper(Right[i]);
|
|
if (a != b)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (a == '\0')
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOLEAN BkchdlExtractSyscallNumberNearAddress(_In_ PEPROCESS SourceProcess, _In_ const VOID *Address,
|
|
_Out_ ULONG *SyscallNumber)
|
|
{
|
|
UCHAR bytes[64];
|
|
ULONG_PTR base;
|
|
SIZE_T readLen;
|
|
SIZE_T offsetIntoRead;
|
|
SIZE_T i;
|
|
BOOLEAN found = FALSE;
|
|
ULONG foundValue = 0;
|
|
SIZE_T bestDistance = (SIZE_T)-1;
|
|
|
|
if (SourceProcess == NULL || Address == NULL || SyscallNumber == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
base = (ULONG_PTR)Address;
|
|
if (base < 32)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
base -= 32;
|
|
readLen = sizeof(bytes);
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)base, bytes, readLen))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
offsetIntoRead = (SIZE_T)((ULONG_PTR)Address - base);
|
|
for (i = 0; i + 7 < readLen; ++i)
|
|
{
|
|
ULONG extracted;
|
|
SIZE_T j;
|
|
|
|
if (bytes[i] != 0xB8)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RtlCopyMemory(&extracted, &bytes[i + 1], sizeof(extracted));
|
|
for (j = i + 5; j + 1 < readLen && j <= i + 12; ++j)
|
|
{
|
|
SIZE_T distance;
|
|
|
|
if (bytes[j] != 0x0F || bytes[j + 1] != 0x05)
|
|
{
|
|
continue;
|
|
}
|
|
if (j > offsetIntoRead + 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
distance = offsetIntoRead - j;
|
|
if (distance < bestDistance)
|
|
{
|
|
bestDistance = distance;
|
|
found = TRUE;
|
|
foundValue = extracted;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*SyscallNumber = foundValue;
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlValidateReturnAddress(_In_ PEPROCESS SourceProcess, _In_ PVOID ReturnAddress)
|
|
{
|
|
UCHAR tail[8];
|
|
ULONG_PTR addrValue;
|
|
|
|
if (SourceProcess == NULL || ReturnAddress == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
addrValue = (ULONG_PTR)ReturnAddress;
|
|
if (addrValue < sizeof(tail))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)(addrValue - sizeof(tail)), tail, sizeof(tail)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (tail[3] == 0xE8)
|
|
{
|
|
return TRUE;
|
|
}
|
|
if (tail[6] == 0xFF && ((tail[7] & 0x38) == 0x10))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlValidateStackFrames(_In_ HANDLE ProcessHandle, _In_ ULONG FrameCount,
|
|
_In_reads_(FrameCount) PVOID *Frames, _Out_ BOOLEAN *SpoofSuspect)
|
|
{
|
|
ULONG i;
|
|
PVOID priorFrame = NULL;
|
|
|
|
if (SpoofSuspect != NULL)
|
|
{
|
|
*SpoofSuspect = FALSE;
|
|
}
|
|
|
|
if (ProcessHandle == NULL || Frames == NULL || FrameCount == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < FrameCount; ++i)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
NTSTATUS status;
|
|
PVOID frame = Frames[i];
|
|
BOOLEAN execProtect;
|
|
|
|
if (frame == NULL || (ULONG_PTR)frame < 0x10000)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (frame == priorFrame)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(ProcessHandle, frame, BkchdlMemoryBasicInformation, &mbi, sizeof(mbi), NULL);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
execProtect = BkprotIsExecutableProtection(mbi.Protect);
|
|
if (mbi.State != MEM_COMMIT || !execProtect)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (mbi.Type != MEM_IMAGE && SpoofSuspect != NULL)
|
|
{
|
|
*SpoofSuspect = TRUE;
|
|
}
|
|
|
|
priorFrame = frame;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static PCSTR BkchdlGetExpectedSyscallExport(_In_ BOOLEAN IsThreadObject, _In_ BOOLEAN IsDuplicateOperation)
|
|
{
|
|
if (IsDuplicateOperation)
|
|
{
|
|
return "NtDuplicateObject";
|
|
}
|
|
if (IsThreadObject)
|
|
{
|
|
return "NtOpenThread";
|
|
}
|
|
return "NtOpenProcess";
|
|
}
|
|
|
|
static BOOLEAN BkchdlIsNtdllPath(_In_ const UNICODE_STRING *Path)
|
|
{
|
|
if (Path == NULL || Path->Buffer == NULL || Path->Length == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return BkstrUnicodeContainsInsensitive(Path, L"ntdll.dll", 9);
|
|
}
|
|
|
|
static BOOLEAN BkchdlIsSyscallStubModulePath(_In_ const UNICODE_STRING *Path)
|
|
{
|
|
if (Path == NULL || Path->Buffer == NULL || Path->Length == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (BkchdlIsNtdllPath(Path))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return BkstrUnicodeContainsInsensitive(Path, L"win32u.dll", 10);
|
|
}
|
|
|
|
static BOOLEAN BkchdlGetNtdllBaseFromFrames(_In_ HANDLE ProcessHandle, _In_ ULONG FrameCount,
|
|
_In_reads_(FrameCount) PVOID *Frames, _Out_ PVOID *NtdllBase)
|
|
{
|
|
ULONG i;
|
|
WCHAR sectionPath[512];
|
|
|
|
if (NtdllBase == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
*NtdllBase = NULL;
|
|
|
|
if (ProcessHandle == NULL || Frames == NULL || FrameCount == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < FrameCount; ++i)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
UCHAR sectionNameRaw[1024];
|
|
PUNICODE_STRING sectionName;
|
|
UNICODE_STRING sectionUs;
|
|
NTSTATUS status;
|
|
|
|
if (Frames[i] == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(ProcessHandle, Frames[i], BkchdlMemoryBasicInformation, &mbi, sizeof(mbi), NULL);
|
|
if (!NT_SUCCESS(status) || mbi.AllocationBase == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RtlZeroMemory(sectionNameRaw, sizeof(sectionNameRaw));
|
|
status = ZwQueryVirtualMemory(ProcessHandle, Frames[i], BkchdlMemorySectionName, sectionNameRaw,
|
|
sizeof(sectionNameRaw), NULL);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sectionName = (PUNICODE_STRING)sectionNameRaw;
|
|
RtlZeroMemory(sectionPath, sizeof(sectionPath));
|
|
BkstrSafeCopyUnicode(sectionName, sectionPath, RTL_NUMBER_OF(sectionPath));
|
|
RtlInitUnicodeString(§ionUs, sectionPath);
|
|
if (!BkchdlIsNtdllPath(§ionUs))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
*NtdllBase = mbi.AllocationBase;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlAddressWithinExportWindow(_In_opt_ PVOID Address, _In_opt_ PVOID ExportAddress)
|
|
{
|
|
ULONG_PTR address;
|
|
ULONG_PTR exportAddress;
|
|
|
|
if (Address == NULL || ExportAddress == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
address = (ULONG_PTR)Address;
|
|
exportAddress = (ULONG_PTR)ExportAddress;
|
|
return address >= exportAddress && (address - exportAddress) < BK_SYSCALL_EXPORT_MATCH_WINDOW_BYTES;
|
|
}
|
|
|
|
static BOOLEAN BkchdlBuildZwAlias(_In_z_ PCSTR ExportName, _Out_writes_(AliasChars) PCHAR Alias, _In_ SIZE_T AliasChars)
|
|
{
|
|
SIZE_T i;
|
|
|
|
if (ExportName == NULL || Alias == NULL || AliasChars < 3)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (ExportName[0] != 'N' || ExportName[1] != 't')
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
Alias[0] = 'Z';
|
|
Alias[1] = 'w';
|
|
for (i = 2; i < AliasChars - 1; ++i)
|
|
{
|
|
Alias[i] = ExportName[i];
|
|
if (ExportName[i] == '\0')
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
Alias[AliasChars - 1] = '\0';
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlResolveExportSyscallNumber(_In_ PEPROCESS SourceProcess, _In_ PVOID ModuleBase,
|
|
_In_z_ PCSTR ExportName, _Out_ ULONG *SyscallNumber,
|
|
_Out_opt_ PVOID *ExportAddress)
|
|
{
|
|
IMAGE_DOS_HEADER dos;
|
|
IMAGE_NT_HEADERS64 nt;
|
|
IMAGE_EXPORT_DIRECTORY exports;
|
|
ULONG namesSize;
|
|
ULONG ordinalsSize;
|
|
ULONG funcsSize;
|
|
ULONG *nameRvas = NULL;
|
|
USHORT *ordinals = NULL;
|
|
ULONG *funcRvas = NULL;
|
|
ULONG i;
|
|
BOOLEAN success = FALSE;
|
|
PVOID resolvedAddress = NULL;
|
|
|
|
if (SourceProcess == NULL || ModuleBase == NULL || ExportName == NULL || SyscallNumber == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (ExportAddress != NULL)
|
|
{
|
|
*ExportAddress = NULL;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, ModuleBase, &dos, sizeof(dos)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (dos.e_magic != IMAGE_DOS_SIGNATURE)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (dos.e_lfanew <= 0 || dos.e_lfanew > 0x2000)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + (ULONG_PTR)dos.e_lfanew), &nt,
|
|
sizeof(nt)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (nt.Signature != IMAGE_NT_SIGNATURE || nt.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (nt.OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||
|
|
nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size < sizeof(exports))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(
|
|
SourceProcess,
|
|
(const VOID *)((ULONG_PTR)ModuleBase +
|
|
nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress),
|
|
&exports, sizeof(exports)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (exports.NumberOfNames == 0 || exports.NumberOfFunctions == 0 || exports.AddressOfNames == 0 ||
|
|
exports.AddressOfNameOrdinals == 0 || exports.AddressOfFunctions == 0 || exports.NumberOfNames > 8192 ||
|
|
exports.NumberOfFunctions > 8192)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
namesSize = exports.NumberOfNames * sizeof(ULONG);
|
|
ordinalsSize = exports.NumberOfNames * sizeof(USHORT);
|
|
funcsSize = exports.NumberOfFunctions * sizeof(ULONG);
|
|
|
|
nameRvas = (ULONG *)BkpoolAllocateCompat(POOL_FLAG_PAGED, namesSize, 'ndtT');
|
|
ordinals = (USHORT *)BkpoolAllocateCompat(POOL_FLAG_PAGED, ordinalsSize, 'odtT');
|
|
funcRvas = (ULONG *)BkpoolAllocateCompat(POOL_FLAG_PAGED, funcsSize, 'fdtT');
|
|
if (nameRvas == NULL || ordinals == NULL || funcRvas == NULL)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + exports.AddressOfNames), nameRvas,
|
|
namesSize) ||
|
|
!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + exports.AddressOfNameOrdinals),
|
|
ordinals, ordinalsSize) ||
|
|
!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + exports.AddressOfFunctions),
|
|
funcRvas, funcsSize))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
for (i = 0; i < exports.NumberOfNames; ++i)
|
|
{
|
|
CHAR nameBuffer[64];
|
|
SIZE_T j;
|
|
USHORT ordinal;
|
|
ULONG funcRva;
|
|
PVOID funcAddress;
|
|
|
|
if (nameRvas[i] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RtlZeroMemory(nameBuffer, sizeof(nameBuffer));
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + nameRvas[i]), nameBuffer,
|
|
sizeof(nameBuffer) - 1))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (j = 0; j < sizeof(nameBuffer); ++j)
|
|
{
|
|
if (nameBuffer[j] == '\0')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (j == sizeof(nameBuffer))
|
|
{
|
|
continue;
|
|
}
|
|
if (!BkchdlAsciiEqualsInsensitive(nameBuffer, ExportName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ordinal = ordinals[i];
|
|
if (ordinal >= exports.NumberOfFunctions)
|
|
{
|
|
break;
|
|
}
|
|
|
|
funcRva = funcRvas[ordinal];
|
|
if (funcRva == 0)
|
|
{
|
|
break;
|
|
}
|
|
funcAddress = (PVOID)((ULONG_PTR)ModuleBase + funcRva);
|
|
success = BkchdlExtractSyscallNumberNearAddress(SourceProcess, funcAddress, SyscallNumber);
|
|
if (success)
|
|
{
|
|
resolvedAddress = funcAddress;
|
|
}
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
if (success && ExportAddress != NULL)
|
|
{
|
|
*ExportAddress = resolvedAddress;
|
|
}
|
|
if (funcRvas != NULL)
|
|
{
|
|
ExFreePoolWithTag(funcRvas, 'fdtT');
|
|
}
|
|
if (ordinals != NULL)
|
|
{
|
|
ExFreePoolWithTag(ordinals, 'odtT');
|
|
}
|
|
if (nameRvas != NULL)
|
|
{
|
|
ExFreePoolWithTag(nameRvas, 'ndtT');
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static BOOLEAN BkchdlResolveExpectedSyscallExport(_In_ PEPROCESS SourceProcess, _In_ PVOID NtdllBase,
|
|
_In_z_ PCSTR ExportName, _In_opt_ PVOID OriginAddress,
|
|
_Out_ ULONG *SyscallNumber, _Out_ BOOLEAN *OriginInsideExpectedExport)
|
|
{
|
|
ULONG primaryNumber = 0;
|
|
ULONG aliasNumber = 0;
|
|
PVOID primaryAddress = NULL;
|
|
PVOID aliasAddress = NULL;
|
|
CHAR aliasName[64];
|
|
BOOLEAN primaryResolved;
|
|
BOOLEAN aliasResolved = FALSE;
|
|
BOOLEAN primaryInside;
|
|
BOOLEAN aliasInside;
|
|
|
|
if (SyscallNumber == NULL || OriginInsideExpectedExport == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*SyscallNumber = 0;
|
|
*OriginInsideExpectedExport = FALSE;
|
|
|
|
primaryResolved =
|
|
BkchdlResolveExportSyscallNumber(SourceProcess, NtdllBase, ExportName, &primaryNumber, &primaryAddress);
|
|
if (BkchdlBuildZwAlias(ExportName, aliasName, RTL_NUMBER_OF(aliasName)))
|
|
{
|
|
aliasResolved =
|
|
BkchdlResolveExportSyscallNumber(SourceProcess, NtdllBase, aliasName, &aliasNumber, &aliasAddress);
|
|
}
|
|
|
|
primaryInside = primaryResolved && BkchdlAddressWithinExportWindow(OriginAddress, primaryAddress);
|
|
aliasInside = aliasResolved && BkchdlAddressWithinExportWindow(OriginAddress, aliasAddress);
|
|
|
|
if (primaryInside)
|
|
{
|
|
*SyscallNumber = primaryNumber;
|
|
*OriginInsideExpectedExport = TRUE;
|
|
return TRUE;
|
|
}
|
|
if (aliasInside)
|
|
{
|
|
*SyscallNumber = aliasNumber;
|
|
*OriginInsideExpectedExport = TRUE;
|
|
return TRUE;
|
|
}
|
|
if (primaryResolved)
|
|
{
|
|
*SyscallNumber = primaryNumber;
|
|
return TRUE;
|
|
}
|
|
if (aliasResolved)
|
|
{
|
|
*SyscallNumber = aliasNumber;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlQueryFrameModuleInfo(_In_ HANDLE ProcessHandle, _In_ PVOID Frame, _Out_ PVOID *AllocationBase,
|
|
_Out_ BOOLEAN *Executable, _Out_ BOOLEAN *ImageBacked,
|
|
_Out_ BOOLEAN *IsSyscallStubModule)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
UCHAR sectionNameRaw[1024];
|
|
PUNICODE_STRING sectionName;
|
|
WCHAR sectionPath[512];
|
|
UNICODE_STRING sectionUs;
|
|
NTSTATUS status;
|
|
|
|
if (ProcessHandle == NULL || Frame == NULL || AllocationBase == NULL || Executable == NULL || ImageBacked == NULL ||
|
|
IsSyscallStubModule == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*AllocationBase = NULL;
|
|
*Executable = FALSE;
|
|
*ImageBacked = FALSE;
|
|
*IsSyscallStubModule = FALSE;
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(ProcessHandle, Frame, BkchdlMemoryBasicInformation, &mbi, sizeof(mbi), NULL);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*AllocationBase = mbi.AllocationBase;
|
|
*Executable = (mbi.State == MEM_COMMIT) && BkprotIsExecutableProtection(mbi.Protect);
|
|
*ImageBacked = (mbi.Type == MEM_IMAGE);
|
|
if (mbi.AllocationBase == NULL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
RtlZeroMemory(sectionNameRaw, sizeof(sectionNameRaw));
|
|
status = ZwQueryVirtualMemory(ProcessHandle, Frame, BkchdlMemorySectionName, sectionNameRaw, sizeof(sectionNameRaw),
|
|
NULL);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
sectionName = (PUNICODE_STRING)sectionNameRaw;
|
|
RtlZeroMemory(sectionPath, sizeof(sectionPath));
|
|
BkstrSafeCopyUnicode(sectionName, sectionPath, RTL_NUMBER_OF(sectionPath));
|
|
RtlInitUnicodeString(§ionUs, sectionPath);
|
|
*IsSyscallStubModule = BkchdlIsSyscallStubModulePath(§ionUs);
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlValidateModuleChainSanity(_In_ HANDLE ProcessHandle, _In_ ULONG FrameCount,
|
|
_In_reads_(FrameCount) PVOID *Frames)
|
|
{
|
|
ULONG i;
|
|
ULONG inspectCount;
|
|
BOOLEAN sawSyscallStubModule = FALSE;
|
|
BOOLEAN sawHeadSyscallStubModule = FALSE;
|
|
BOOLEAN sawNonSyscallStubModule = FALSE;
|
|
PVOID syscallStubModuleBase = NULL;
|
|
|
|
if (ProcessHandle == NULL || Frames == NULL || FrameCount == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
inspectCount = (FrameCount > 6U) ? 6U : FrameCount;
|
|
for (i = 0; i < inspectCount; ++i)
|
|
{
|
|
PVOID frameBase;
|
|
BOOLEAN exec;
|
|
BOOLEAN image;
|
|
BOOLEAN isSyscallStubModule;
|
|
|
|
if (!BkchdlQueryFrameModuleInfo(ProcessHandle, Frames[i], &frameBase, &exec, &image, &isSyscallStubModule))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (!exec || !image || frameBase == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (isSyscallStubModule)
|
|
{
|
|
sawSyscallStubModule = TRUE;
|
|
if (i <= 2U)
|
|
{
|
|
sawHeadSyscallStubModule = TRUE;
|
|
}
|
|
if (syscallStubModuleBase == NULL)
|
|
{
|
|
syscallStubModuleBase = frameBase;
|
|
}
|
|
else if (syscallStubModuleBase != frameBase)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sawNonSyscallStubModule = TRUE;
|
|
}
|
|
}
|
|
|
|
return (sawSyscallStubModule && sawHeadSyscallStubModule && sawNonSyscallStubModule);
|
|
}
|
|
|
|
static BOOLEAN BkchdlModuleContainsUnwindForRva(_In_ PEPROCESS SourceProcess, _In_ PVOID ModuleBase, _In_ ULONG Rva)
|
|
{
|
|
IMAGE_DOS_HEADER dos;
|
|
IMAGE_NT_HEADERS64 nt;
|
|
ULONG exceptionRva;
|
|
ULONG exceptionSize;
|
|
ULONG count;
|
|
SIZE_T tableBytes;
|
|
IMAGE_RUNTIME_FUNCTION_ENTRY *table = NULL;
|
|
LONG low;
|
|
LONG high;
|
|
BOOLEAN found = FALSE;
|
|
|
|
if (SourceProcess == NULL || ModuleBase == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, ModuleBase, &dos, sizeof(dos)) || dos.e_magic != IMAGE_DOS_SIGNATURE ||
|
|
dos.e_lfanew <= 0 || dos.e_lfanew > 0x2000)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + (ULONG_PTR)dos.e_lfanew), &nt,
|
|
sizeof(nt)) ||
|
|
nt.Signature != IMAGE_NT_SIGNATURE || nt.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC ||
|
|
nt.OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXCEPTION)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
exceptionRva = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
|
|
exceptionSize = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
|
|
if (exceptionRva == 0 || exceptionSize < sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
count = exceptionSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY);
|
|
if (count == 0 || count > 65536)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
tableBytes = (SIZE_T)count * sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY);
|
|
table = (IMAGE_RUNTIME_FUNCTION_ENTRY *)BkpoolAllocateCompat(POOL_FLAG_PAGED, tableBytes, 'udtT');
|
|
if (table == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!BkchdlReadProcessBytes(SourceProcess, (const VOID *)((ULONG_PTR)ModuleBase + exceptionRva), table, tableBytes))
|
|
{
|
|
ExFreePoolWithTag(table, 'udtT');
|
|
return FALSE;
|
|
}
|
|
|
|
low = 0;
|
|
high = (LONG)count - 1;
|
|
while (low <= high)
|
|
{
|
|
LONG mid = low + ((high - low) / 2);
|
|
ULONG begin = table[mid].BeginAddress;
|
|
ULONG end = table[mid].EndAddress;
|
|
|
|
if (Rva < begin)
|
|
{
|
|
high = mid - 1;
|
|
continue;
|
|
}
|
|
if (Rva >= end)
|
|
{
|
|
low = mid + 1;
|
|
continue;
|
|
}
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
|
|
ExFreePoolWithTag(table, 'udtT');
|
|
return found;
|
|
}
|
|
|
|
static BOOLEAN BkchdlValidateUnwindMetadata(_In_ HANDLE ProcessHandle, _In_ PEPROCESS SourceProcess,
|
|
_In_ ULONG FrameCount, _In_reads_(FrameCount) PVOID *Frames)
|
|
{
|
|
ULONG i;
|
|
ULONG inspectCount;
|
|
|
|
if (ProcessHandle == NULL || SourceProcess == NULL || Frames == NULL || FrameCount == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
inspectCount = (FrameCount > 6U) ? 6U : FrameCount;
|
|
for (i = 0; i < inspectCount; ++i)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
NTSTATUS status;
|
|
ULONG_PTR addr;
|
|
ULONG_PTR base;
|
|
ULONG rva;
|
|
|
|
if (Frames[i] == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(ProcessHandle, Frames[i], BkchdlMemoryBasicInformation, &mbi, sizeof(mbi), NULL);
|
|
if (!NT_SUCCESS(status) || mbi.AllocationBase == NULL || mbi.Type != MEM_IMAGE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
addr = (ULONG_PTR)Frames[i];
|
|
base = (ULONG_PTR)mbi.AllocationBase;
|
|
if (addr < base || (addr - base) > MAXULONG)
|
|
{
|
|
return FALSE;
|
|
}
|
|
rva = (ULONG)(addr - base);
|
|
if (!BkchdlModuleContainsUnwindForRva(SourceProcess, mbi.AllocationBase, rva))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlQueryTebStackBounds(_In_ HANDLE CallerProcessId, _In_ HANDLE CallerThreadId,
|
|
_In_ PEPROCESS SourceProcess, _Out_ ULONG_PTR *StackLimit,
|
|
_Out_ ULONG_PTR *StackBase)
|
|
{
|
|
OBJECT_ATTRIBUTES oa;
|
|
CLIENT_ID cid;
|
|
HANDLE threadHandle = NULL;
|
|
BK_THREAD_BASIC_INFORMATION tbi;
|
|
BK_NT_TIB tib;
|
|
NTSTATUS status;
|
|
ULONG_PTR limit;
|
|
ULONG_PTR base;
|
|
|
|
if (SourceProcess == NULL || StackLimit == NULL || StackBase == NULL || CallerThreadId == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*StackLimit = 0;
|
|
*StackBase = 0;
|
|
|
|
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
cid.UniqueProcess = CallerProcessId;
|
|
cid.UniqueThread = CallerThreadId;
|
|
status = ZwOpenThread(&threadHandle, THREAD_QUERY_LIMITED_INFORMATION, &oa, &cid);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
status = ZwOpenThread(&threadHandle, THREAD_QUERY_INFORMATION, &oa, &cid);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
RtlZeroMemory(&tbi, sizeof(tbi));
|
|
status = ZwQueryInformationThread(threadHandle, ThreadBasicInformation, &tbi, sizeof(tbi), NULL);
|
|
ZwClose(threadHandle);
|
|
if (!NT_SUCCESS(status) || tbi.TebBaseAddress == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if ((ULONG_PTR)tbi.TebBaseAddress >= (ULONG_PTR)MmSystemRangeStart)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory(&tib, sizeof(tib));
|
|
if (!BkchdlReadProcessBytes(SourceProcess, tbi.TebBaseAddress, &tib, sizeof(tib)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
limit = (ULONG_PTR)tib.StackLimit;
|
|
base = (ULONG_PTR)tib.StackBase;
|
|
if (limit == 0 || base == 0 || limit >= base)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (base - limit < 0x4000 || base - limit > (64ULL * 1024ULL * 1024ULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*StackLimit = limit;
|
|
*StackBase = base;
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN BkchdlFramesOutsideStackBounds(_In_ ULONG FrameCount, _In_reads_(FrameCount) PVOID *Frames,
|
|
_In_ ULONG_PTR StackLimit, _In_ ULONG_PTR StackBase)
|
|
{
|
|
ULONG i;
|
|
|
|
if (Frames == NULL || FrameCount == 0 || StackLimit == 0 || StackBase == 0 || StackLimit >= StackBase)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < FrameCount; ++i)
|
|
{
|
|
ULONG_PTR a = (ULONG_PTR)Frames[i];
|
|
if (a >= StackLimit && a < StackBase)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static PCSTR BkchdlClassToString(_In_ BK_HANDLE_CLASSIFICATION Class)
|
|
{
|
|
if (Class == BkchdlHandleLegitimateSyscall)
|
|
{
|
|
return "LEGITIMATE-SYSCALL";
|
|
}
|
|
if (Class == BkchdlHandleDirectSyscallSuspect)
|
|
{
|
|
return "DIRECT-SYSCALL-SUSPECT";
|
|
}
|
|
return "UNKNOWN-ORIGIN";
|
|
}
|
|
|
|
static BOOLEAN BkchdlHandleAccessIsHighRisk(_In_ ACCESS_MASK DesiredAccess, _In_ BOOLEAN IsThreadObject)
|
|
{
|
|
if (IsThreadObject)
|
|
{
|
|
return ((DesiredAccess & THREAD_SET_CONTEXT) != 0) || ((DesiredAccess & THREAD_SUSPEND_RESUME) != 0) ||
|
|
((DesiredAccess & THREAD_GET_CONTEXT) != 0) || ((DesiredAccess & THREAD_QUERY_INFORMATION) != 0) ||
|
|
((DesiredAccess & THREAD_ALL_ACCESS) == THREAD_ALL_ACCESS);
|
|
}
|
|
|
|
return ((DesiredAccess & PROCESS_VM_OPERATION) != 0) || ((DesiredAccess & PROCESS_VM_WRITE) != 0) ||
|
|
((DesiredAccess & PROCESS_CREATE_THREAD) != 0) || ((DesiredAccess & PROCESS_DUP_HANDLE) != 0) ||
|
|
((DesiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS);
|
|
}
|
|
|
|
static UINT32 BkchdlClassifyEnterpriseTargetName(_In_opt_z_ PCSTR ImageName)
|
|
{
|
|
UINT32 flags = 0;
|
|
|
|
if (ImageName == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (BkchdlAsciiEqualsInsensitive(ImageName, "lsass.exe"))
|
|
{
|
|
return BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS | BK_ENTERPRISE_FLAG_PRIVILEGED_TARGET |
|
|
BK_ENTERPRISE_FLAG_LSASS_TARGET | BK_ENTERPRISE_FLAG_KERBEROS_NTLM;
|
|
}
|
|
if (BkchdlAsciiEqualsInsensitive(ImageName, "lsaiso.exe"))
|
|
{
|
|
return BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS | BK_ENTERPRISE_FLAG_PRIVILEGED_TARGET |
|
|
BK_ENTERPRISE_FLAG_KERBEROS_NTLM;
|
|
}
|
|
if (BkchdlAsciiEqualsInsensitive(ImageName, "winlogon.exe"))
|
|
{
|
|
return BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS | BK_ENTERPRISE_FLAG_PRIVILEGED_TARGET |
|
|
BK_ENTERPRISE_FLAG_WINLOGON_TARGET;
|
|
}
|
|
|
|
if (BkchdlAsciiEqualsInsensitive(ImageName, "services.exe") ||
|
|
BkchdlAsciiEqualsInsensitive(ImageName, "wininit.exe") ||
|
|
BkchdlAsciiEqualsInsensitive(ImageName, "csrss.exe") || BkchdlAsciiEqualsInsensitive(ImageName, "smss.exe"))
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_PRIVILEGED_TARGET;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
static BOOLEAN BkchdlProcessAccessIsCredentialRelevant(_In_ ACCESS_MASK DesiredAccess)
|
|
{
|
|
return ((DesiredAccess & (PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD |
|
|
PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION)) !=
|
|
0) ||
|
|
((DesiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS);
|
|
}
|
|
|
|
static BOOLEAN BkchdlProcessAccessIsPrivilegeRelevant(_In_ ACCESS_MASK DesiredAccess)
|
|
{
|
|
return ((DesiredAccess & (PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
|
|
PROCESS_DUP_HANDLE | PROCESS_CREATE_PROCESS | PROCESS_SET_INFORMATION)) != 0) ||
|
|
((DesiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS);
|
|
}
|
|
|
|
static UINT32 BkchdlEnterpriseAccessFlags(_In_ ACCESS_MASK DesiredAccess, _In_ BOOLEAN IsThreadObject,
|
|
_In_ BOOLEAN IsDuplicateOperation)
|
|
{
|
|
UINT32 flags = IsThreadObject ? BK_ENTERPRISE_FLAG_THREAD_OBJECT : BK_ENTERPRISE_FLAG_PROCESS_OBJECT;
|
|
|
|
if (IsDuplicateOperation)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_DUPLICATE_HANDLE;
|
|
}
|
|
if ((DesiredAccess & PROCESS_VM_READ) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_VM_READ;
|
|
}
|
|
if ((DesiredAccess & PROCESS_VM_WRITE) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_VM_WRITE;
|
|
}
|
|
if ((DesiredAccess & PROCESS_VM_OPERATION) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_VM_OPERATION;
|
|
}
|
|
if ((DesiredAccess & PROCESS_CREATE_THREAD) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_CREATE_THREAD;
|
|
}
|
|
if ((DesiredAccess & (PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION | THREAD_QUERY_INFORMATION |
|
|
THREAD_QUERY_LIMITED_INFORMATION)) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_QUERY_ACCESS;
|
|
}
|
|
if ((DesiredAccess & (THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME)) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_THREAD_CONTEXT;
|
|
}
|
|
if ((DesiredAccess & (PROCESS_TERMINATE | PROCESS_SET_INFORMATION)) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_SET_OR_TERMINATE;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
static BOOLEAN BkchdlProcessEnterpriseCandidate(_In_ PEPROCESS TargetProcess, _In_ HANDLE CallerPid,
|
|
_In_ HANDLE TargetPid, _In_ ACCESS_MASK DesiredAccess)
|
|
{
|
|
UINT32 targetFlags;
|
|
PCHAR imageName;
|
|
|
|
if (TargetProcess == NULL || CallerPid == TargetPid)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
imageName = PsGetProcessImageFileName(TargetProcess);
|
|
targetFlags = BkchdlClassifyEnterpriseTargetName(imageName);
|
|
if ((targetFlags & BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS) != 0)
|
|
{
|
|
return BkchdlProcessAccessIsCredentialRelevant(DesiredAccess);
|
|
}
|
|
if ((targetFlags & BK_ENTERPRISE_FLAG_PRIVILEGED_TARGET) != 0)
|
|
{
|
|
return BkchdlProcessAccessIsPrivilegeRelevant(DesiredAccess);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static VOID BkchdlPublishEnterpriseHandleEvent(_In_ BK_HANDLE_CLASSIFICATION Class, _In_ HANDLE CallerPid,
|
|
_In_ HANDLE CallerTid, _In_ HANDLE TargetPid, _In_ HANDLE TargetTid,
|
|
_In_ UINT64 ObjectAddress, _In_ ACCESS_MASK DesiredAccess,
|
|
_In_ BOOLEAN IsThreadObject, _In_ BOOLEAN IsDuplicateOperation,
|
|
_In_ const BK_HANDLE_TELEMETRY *Telemetry)
|
|
{
|
|
PEPROCESS targetProcess = NULL;
|
|
PCHAR imageName;
|
|
UINT32 targetFlags;
|
|
UINT32 flags;
|
|
BK_ENTERPRISE_EVENT event;
|
|
UINT32 callerPid32 = (UINT32)(ULONG_PTR)CallerPid;
|
|
UINT32 targetPid32 = (UINT32)(ULONG_PTR)TargetPid;
|
|
|
|
if (CallerPid == TargetPid || callerPid32 == 0 || targetPid32 == 0)
|
|
{
|
|
return;
|
|
}
|
|
if (!BkctlHasPidInterest(callerPid32, targetPid32, BK_STREAM_ENTERPRISE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!NT_SUCCESS(PsLookupProcessByProcessId(TargetPid, &targetProcess)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
imageName = PsGetProcessImageFileName(targetProcess);
|
|
targetFlags = BkchdlClassifyEnterpriseTargetName(imageName);
|
|
if ((targetFlags & BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS) != 0)
|
|
{
|
|
if (!BkchdlProcessAccessIsCredentialRelevant(DesiredAccess))
|
|
{
|
|
ObDereferenceObject(targetProcess);
|
|
return;
|
|
}
|
|
}
|
|
else if ((targetFlags & BK_ENTERPRISE_FLAG_PRIVILEGED_TARGET) != 0)
|
|
{
|
|
if (!IsThreadObject && !BkchdlProcessAccessIsPrivilegeRelevant(DesiredAccess))
|
|
{
|
|
ObDereferenceObject(targetProcess);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ObDereferenceObject(targetProcess);
|
|
return;
|
|
}
|
|
|
|
flags = BK_ENTERPRISE_FLAG_HIGH_SIGNAL |
|
|
BkchdlEnterpriseAccessFlags(DesiredAccess, IsThreadObject, IsDuplicateOperation) | targetFlags;
|
|
if ((targetFlags & BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS) != 0)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_CRITICAL;
|
|
}
|
|
if (Class == BkchdlHandleDirectSyscallSuspect)
|
|
{
|
|
flags |= BK_ENTERPRISE_FLAG_DIRECT_SYSCALL_SUSPECT;
|
|
}
|
|
|
|
RtlZeroMemory(&event, sizeof(event));
|
|
event.ProcessId = (UINT64)(ULONG_PTR)CallerPid;
|
|
event.ThreadId = (UINT64)(ULONG_PTR)CallerTid;
|
|
event.TargetProcessId = (UINT64)(ULONG_PTR)TargetPid;
|
|
event.TargetThreadId = (UINT64)(ULONG_PTR)TargetTid;
|
|
event.ObjectAddress = ObjectAddress;
|
|
event.Aux0 = (Telemetry != NULL) ? (UINT64)(ULONG_PTR)Telemetry->OriginAddress : 0;
|
|
event.Aux1 = (Telemetry != NULL) ? Telemetry->CaptureFlags : 0;
|
|
event.Operation = ((targetFlags & BK_ENTERPRISE_FLAG_CREDENTIAL_PROCESS) != 0)
|
|
? BkEnterpriseOperationProcessCredentialAccess
|
|
: BkEnterpriseOperationProcessPrivilegedAccess;
|
|
event.SubOperation = (Class == BkchdlHandleDirectSyscallSuspect) ? BlackbirdHandleClassDirectSyscallSuspect
|
|
: BlackbirdHandleClassUnknown;
|
|
event.Flags = flags;
|
|
event.DesiredAccess = (UINT32)DesiredAccess;
|
|
BkctlPublishEnterpriseEvent(&event);
|
|
ObDereferenceObject(targetProcess);
|
|
}
|
|
|
|
static UINT32 BkchdlCountHandleAnomalySignals(_In_ BOOLEAN ExecProtect, _In_ BOOLEAN FromSyscallModule,
|
|
_In_ const BK_HANDLE_TELEMETRY *Telemetry)
|
|
{
|
|
UINT32 signals = 0;
|
|
|
|
if (Telemetry == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!Telemetry->StackValidated)
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (Telemetry->StackSpoofSuspect)
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (!Telemetry->ReturnAddressValid)
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (Telemetry->SyscallExportChecked && !Telemetry->SyscallExportMatch)
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (Telemetry->ModuleChainChecked && !Telemetry->ModuleChainSane)
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (Telemetry->UnwindMetadataChecked && !Telemetry->UnwindMetadataValid)
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (Telemetry->TebStackBoundsChecked && (!Telemetry->TebStackBoundsValid || !Telemetry->FramesOutsideTebStack))
|
|
{
|
|
signals += 1;
|
|
}
|
|
if (ExecProtect && !FromSyscallModule)
|
|
{
|
|
signals += 1;
|
|
}
|
|
|
|
return signals;
|
|
}
|
|
|
|
static VOID BkchdlLogHandleTelemetry(_In_ BK_HANDLE_CLASSIFICATION Class, _In_ HANDLE CallerPid, _In_ HANDLE CallerTid,
|
|
_In_ HANDLE TargetPid, _In_ HANDLE TargetTid, _In_ UINT64 ObjectAddress,
|
|
_In_ ACCESS_MASK DesiredAccess, _In_ BOOLEAN IsThreadObject,
|
|
_In_ BOOLEAN IsDuplicateOperation, _In_ BOOLEAN ExecProtect,
|
|
_In_ BOOLEAN FromNtdll, _In_ BOOLEAN FromExe, _In_ PBK_HANDLE_TELEMETRY Telemetry)
|
|
{
|
|
UINT32 flags = 0;
|
|
BOOLEAN memoryRelated;
|
|
BOOLEAN stackIntegrityAnomaly;
|
|
BOOLEAN highRiskAccess;
|
|
BOOLEAN fromSyscallModule;
|
|
BOOLEAN directSyscallEntryAnomaly;
|
|
UINT32 anomalySignals;
|
|
UINT32 classId;
|
|
UINT32 handleStream;
|
|
UNICODE_STRING originPathUs;
|
|
BK_HANDLE_EVENT handleEvent;
|
|
UINT32 i;
|
|
UINT32 safeFrameCount;
|
|
UINT32 safeFullFrameCount;
|
|
UINT32 safeStackBytes;
|
|
|
|
BketwLogHandleEvent(BkchdlClassToString(Class), CallerPid, TargetPid, DesiredAccess, Telemetry->OriginAddress,
|
|
Telemetry->OriginProtect, ExecProtect, FromNtdll, FromExe,
|
|
(Telemetry->OriginPath[0] != L'\0') ? Telemetry->OriginPath : NULL, Telemetry->FrameCount,
|
|
Telemetry->Frames, Telemetry->OpenProcessStatus, Telemetry->BasicInfoStatus,
|
|
Telemetry->SectionNameStatus, (UINT64)(ULONG_PTR)Telemetry->AllocationBase,
|
|
(UINT64)Telemetry->RegionSize, Telemetry->OriginProtect, Telemetry->RegionState,
|
|
Telemetry->RegionType, Telemetry->DeepSampleSize, Telemetry->DeepSample);
|
|
|
|
if (ExecProtect)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_EXEC_PROTECT;
|
|
}
|
|
if (FromNtdll)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_FROM_NTDLL;
|
|
}
|
|
if (FromExe)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_FROM_EXE;
|
|
}
|
|
|
|
memoryRelated = ((DesiredAccess & PROCESS_VM_OPERATION) != 0) || ((DesiredAccess & PROCESS_VM_READ) != 0) ||
|
|
((DesiredAccess & PROCESS_VM_WRITE) != 0) || ((DesiredAccess & PROCESS_CREATE_THREAD) != 0) ||
|
|
((DesiredAccess & PROCESS_DUP_HANDLE) != 0) ||
|
|
((DesiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS);
|
|
highRiskAccess = BkchdlHandleAccessIsHighRisk(DesiredAccess, IsThreadObject);
|
|
RtlInitUnicodeString(&originPathUs, Telemetry->OriginPath);
|
|
fromSyscallModule = BkchdlIsSyscallStubModulePath(&originPathUs);
|
|
anomalySignals = BkchdlCountHandleAnomalySignals(ExecProtect, fromSyscallModule, Telemetry);
|
|
directSyscallEntryAnomaly =
|
|
ExecProtect && (!fromSyscallModule || (Telemetry->SyscallExportChecked && !Telemetry->SyscallExportMatch));
|
|
if (memoryRelated)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_MEMORY_RELATED;
|
|
}
|
|
if (IsThreadObject)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_THREAD_OBJECT;
|
|
}
|
|
if (IsDuplicateOperation)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_DUPLICATE_OPERATION;
|
|
}
|
|
if (Telemetry->DeepPathCandidate)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_DEEP_PATH_CANDIDATE;
|
|
}
|
|
if (Telemetry->DeepPathCaptured)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_DEEP_PATH_CAPTURED;
|
|
}
|
|
if (Telemetry->DeepPathCacheHit)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_DEEP_PATH_CACHE_HIT;
|
|
}
|
|
if (Telemetry->ReturnAddressValid)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_RETURN_ADDRESS_VALID;
|
|
}
|
|
if (Telemetry->StackValidated)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_STACK_VALIDATED;
|
|
}
|
|
if (Telemetry->StackSpoofSuspect)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_STACK_SPOOF_SUSPECT;
|
|
}
|
|
if (Telemetry->SyscallExportChecked && Telemetry->SyscallExportMatch)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_SYSCALL_EXPORT_MATCH;
|
|
}
|
|
if (Telemetry->SyscallExportChecked && !Telemetry->SyscallExportMatch)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_SYSCALL_EXPORT_MISMATCH;
|
|
}
|
|
if (Telemetry->ModuleChainChecked && Telemetry->ModuleChainSane)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_MODULE_CHAIN_SANE;
|
|
}
|
|
if (Telemetry->UnwindMetadataChecked && Telemetry->UnwindMetadataValid)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_UNWIND_METADATA_VALID;
|
|
}
|
|
if (Telemetry->TebStackBoundsChecked && Telemetry->TebStackBoundsValid)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_TEB_STACK_BOUNDS_VALID;
|
|
}
|
|
if (Telemetry->TebStackBoundsChecked && Telemetry->FramesOutsideTebStack)
|
|
{
|
|
flags |= BK_HANDLE_FLAG_FRAMES_OUTSIDE_TEB_STACK;
|
|
}
|
|
|
|
classId = BlackbirdHandleClassUnknown;
|
|
if (Class == BkchdlHandleLegitimateSyscall)
|
|
{
|
|
classId = BlackbirdHandleClassLegitimateSyscall;
|
|
}
|
|
else if (Class == BkchdlHandleDirectSyscallSuspect)
|
|
{
|
|
classId = BlackbirdHandleClassDirectSyscallSuspect;
|
|
}
|
|
|
|
if (Class == BkchdlHandleDirectSyscallSuspect && highRiskAccess &&
|
|
(directSyscallEntryAnomaly || anomalySignals >= 2))
|
|
{
|
|
BketwLogDetectionEvent(
|
|
"DIRECT_SYSCALL_SUSPECT_HANDLE_OPERATION", 5, CallerPid, TargetPid, 0, (UINT32)DesiredAccess, 0,
|
|
L"high-risk handle operation reached the kernel from outside the expected ntdll syscall export");
|
|
}
|
|
|
|
stackIntegrityAnomaly =
|
|
(Telemetry->StackSpoofSuspect || !Telemetry->StackValidated || !Telemetry->ReturnAddressValid ||
|
|
(Telemetry->ModuleChainChecked && !Telemetry->ModuleChainSane) ||
|
|
(Telemetry->UnwindMetadataChecked && !Telemetry->UnwindMetadataValid) ||
|
|
(Telemetry->TebStackBoundsChecked && (!Telemetry->TebStackBoundsValid || !Telemetry->FramesOutsideTebStack)));
|
|
if (stackIntegrityAnomaly && (Class == BkchdlHandleDirectSyscallSuspect) && highRiskAccess && anomalySignals >= 3 &&
|
|
CallerPid != TargetPid)
|
|
{
|
|
BketwLogDetectionEvent("STACK_INTEGRITY_ANOMALY_ON_HANDLE_OP", 6, CallerPid, TargetPid, 0,
|
|
(UINT32)DesiredAccess, 0,
|
|
L"high-confidence stack integrity anomaly on high-risk handle operation");
|
|
}
|
|
|
|
/* Credential-access: process-memory handle to lsass.exe */
|
|
if (!IsThreadObject &&
|
|
(DesiredAccess & (PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD)) != 0 &&
|
|
CallerPid != TargetPid)
|
|
{
|
|
PEPROCESS targetProc = NULL;
|
|
NTSTATUS lookupSt = PsLookupProcessByProcessId(TargetPid, &targetProc);
|
|
if (NT_SUCCESS(lookupSt))
|
|
{
|
|
PCHAR imageName = PsGetProcessImageFileName(targetProc);
|
|
if (imageName != NULL)
|
|
{
|
|
/* Case-insensitive byte compare against "lsass.exe" */
|
|
static const CHAR kLsass[] = "lsass.exe";
|
|
BOOLEAN isLsass = TRUE;
|
|
ULONG ci;
|
|
for (ci = 0; ci < sizeof(kLsass) - 1u; ++ci)
|
|
{
|
|
CHAR c = imageName[ci];
|
|
if (c >= 'A' && c <= 'Z')
|
|
c = (CHAR)(c + 32);
|
|
if (c != kLsass[ci])
|
|
{
|
|
isLsass = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (isLsass && imageName[sizeof(kLsass) - 1u] == '\0')
|
|
{
|
|
ULONG sev = (Class == BkchdlHandleDirectSyscallSuspect) ? 8u : 7u;
|
|
BketwLogDetectionEvent("CREDENTIAL_ACCESS_LSASS_HANDLE", sev, CallerPid, TargetPid, 0,
|
|
(UINT32)DesiredAccess, 0,
|
|
L"cross-process memory handle to lsass.exe — credential access attempt");
|
|
}
|
|
|
|
/* Winlogon memory access is also high-value — holds logon tokens */
|
|
{
|
|
static const CHAR kWinlogon[] = "winlogon.exe";
|
|
BOOLEAN isWinlogon = TRUE;
|
|
for (ci = 0; ci < sizeof(kWinlogon) - 1u; ++ci)
|
|
{
|
|
CHAR c = imageName[ci];
|
|
if (c >= 'A' && c <= 'Z')
|
|
c = (CHAR)(c + 32);
|
|
if (c != kWinlogon[ci])
|
|
{
|
|
isWinlogon = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (isWinlogon && imageName[sizeof(kWinlogon) - 1u] == '\0' &&
|
|
(DesiredAccess & (PROCESS_VM_READ | PROCESS_VM_WRITE)) != 0)
|
|
{
|
|
BketwLogDetectionEvent("CREDENTIAL_ACCESS_WINLOGON_HANDLE", 6, CallerPid, TargetPid, 0,
|
|
(UINT32)DesiredAccess, 0,
|
|
L"cross-process memory handle to winlogon.exe");
|
|
}
|
|
}
|
|
}
|
|
ObDereferenceObject(targetProc);
|
|
}
|
|
}
|
|
|
|
/* Hardware breakpoint register detection.
|
|
* DR7 enable bits: L0/G0=0x3, L1/G1=0xC, L2/G2=0x30, L3/G3=0xC0 */
|
|
if (Telemetry->CaptureFlags & BK_HANDLE_CAPTURE_DEBUG_REGS_VALID)
|
|
{
|
|
UINT32 dr7EnableBits = (UINT32)(Telemetry->RegDr7 & 0xFFu);
|
|
UINT32 activeCount = 0;
|
|
if ((dr7EnableBits & 0x03u) && Telemetry->RegDr0 != 0)
|
|
activeCount++;
|
|
if ((dr7EnableBits & 0x0Cu) && Telemetry->RegDr1 != 0)
|
|
activeCount++;
|
|
if ((dr7EnableBits & 0x30u) && Telemetry->RegDr2 != 0)
|
|
activeCount++;
|
|
if ((dr7EnableBits & 0xC0u) && Telemetry->RegDr3 != 0)
|
|
activeCount++;
|
|
if (activeCount == 4)
|
|
{
|
|
BketwLogDetectionEvent(
|
|
"HW_BREAKPOINT_SATURATION", 6, CallerPid, TargetPid, activeCount, (UINT32)Telemetry->RegDr7, 0,
|
|
L"all four hardware breakpoint registers occupied — possible anti-debug or memory breakpoint abuse");
|
|
}
|
|
else if (activeCount >= 1)
|
|
{
|
|
BketwLogDetectionEvent("HARDWARE_BREAKPOINT_HOGGING", 5, CallerPid, TargetPid, activeCount,
|
|
(UINT32)Telemetry->RegDr7, 0,
|
|
L"hardware breakpoint register(s) occupied on process making sensitive handle call");
|
|
}
|
|
}
|
|
|
|
BkchdlPublishEnterpriseHandleEvent(Class, CallerPid, CallerTid, TargetPid, TargetTid, ObjectAddress, DesiredAccess,
|
|
IsThreadObject, IsDuplicateOperation, Telemetry);
|
|
|
|
handleStream = BK_STREAM_HANDLE;
|
|
if (memoryRelated)
|
|
{
|
|
handleStream |= BK_STREAM_MEMORY;
|
|
}
|
|
if (BkctlHasPidInterest(
|
|
(UINT32)(ULONG_PTR)CallerPid,
|
|
((UINT32)(ULONG_PTR)TargetPid != (UINT32)(ULONG_PTR)CallerPid) ? (UINT32)(ULONG_PTR)TargetPid : 0,
|
|
handleStream))
|
|
{
|
|
RtlZeroMemory(&handleEvent, sizeof(handleEvent));
|
|
handleEvent.CallerPid = (UINT64)(ULONG_PTR)CallerPid;
|
|
handleEvent.TargetPid = (UINT64)(ULONG_PTR)TargetPid;
|
|
handleEvent.DesiredAccess = (UINT32)DesiredAccess;
|
|
handleEvent.ClassId = classId;
|
|
handleEvent.OriginAddress = (UINT64)(ULONG_PTR)Telemetry->OriginAddress;
|
|
handleEvent.OriginProtect = Telemetry->OriginProtect;
|
|
handleEvent.Flags = flags;
|
|
handleEvent.StatusOpenProcess = (INT32)Telemetry->OpenProcessStatus;
|
|
handleEvent.StatusBasicInfo = (INT32)Telemetry->BasicInfoStatus;
|
|
handleEvent.StatusSectionName = (INT32)Telemetry->SectionNameStatus;
|
|
handleEvent.DeepAllocationBase = (UINT64)(ULONG_PTR)Telemetry->AllocationBase;
|
|
handleEvent.DeepRegionSize = (UINT64)Telemetry->RegionSize;
|
|
handleEvent.DeepRegionProtect = Telemetry->OriginProtect;
|
|
handleEvent.DeepRegionState = Telemetry->RegionState;
|
|
handleEvent.DeepRegionType = Telemetry->RegionType;
|
|
|
|
handleEvent.DeepSampleSize = Telemetry->DeepSampleSize;
|
|
if (handleEvent.DeepSampleSize > RTL_NUMBER_OF(handleEvent.DeepSample))
|
|
{
|
|
handleEvent.DeepSampleSize = RTL_NUMBER_OF(handleEvent.DeepSample);
|
|
}
|
|
if (handleEvent.DeepSampleSize != 0)
|
|
{
|
|
RtlCopyMemory(handleEvent.DeepSample, Telemetry->DeepSample, handleEvent.DeepSampleSize);
|
|
}
|
|
|
|
if (Telemetry->OriginPath[0] != L'\0')
|
|
{
|
|
(void)RtlStringCchCopyW(handleEvent.OriginPath, RTL_NUMBER_OF(handleEvent.OriginPath),
|
|
Telemetry->OriginPath);
|
|
}
|
|
|
|
safeFrameCount = (Telemetry->FrameCount > RTL_NUMBER_OF(handleEvent.Frames)) ? RTL_NUMBER_OF(handleEvent.Frames)
|
|
: Telemetry->FrameCount;
|
|
handleEvent.FrameCount = safeFrameCount;
|
|
for (i = 0; i < safeFrameCount; ++i)
|
|
{
|
|
handleEvent.Frames[i] = (UINT64)(ULONG_PTR)Telemetry->Frames[i];
|
|
}
|
|
|
|
safeFullFrameCount = (Telemetry->FullFrameCount > RTL_NUMBER_OF(handleEvent.FullFrames))
|
|
? RTL_NUMBER_OF(handleEvent.FullFrames)
|
|
: Telemetry->FullFrameCount;
|
|
handleEvent.FullFrameCount = safeFullFrameCount;
|
|
for (i = 0; i < safeFullFrameCount; ++i)
|
|
{
|
|
handleEvent.FullFrames[i] = (UINT64)(ULONG_PTR)Telemetry->FullFrames[i];
|
|
}
|
|
|
|
handleEvent.CaptureFlags = Telemetry->CaptureFlags;
|
|
handleEvent.RegRax = Telemetry->RegRax;
|
|
handleEvent.RegRbx = Telemetry->RegRbx;
|
|
handleEvent.RegRcx = Telemetry->RegRcx;
|
|
handleEvent.RegRdx = Telemetry->RegRdx;
|
|
handleEvent.RegRsi = Telemetry->RegRsi;
|
|
handleEvent.RegRdi = Telemetry->RegRdi;
|
|
handleEvent.RegRbp = Telemetry->RegRbp;
|
|
handleEvent.RegRsp = Telemetry->RegRsp;
|
|
handleEvent.RegR8 = Telemetry->RegR8;
|
|
handleEvent.RegR9 = Telemetry->RegR9;
|
|
handleEvent.RegR10 = Telemetry->RegR10;
|
|
handleEvent.RegR11 = Telemetry->RegR11;
|
|
handleEvent.RegR12 = Telemetry->RegR12;
|
|
handleEvent.RegR13 = Telemetry->RegR13;
|
|
handleEvent.RegR14 = Telemetry->RegR14;
|
|
handleEvent.RegR15 = Telemetry->RegR15;
|
|
handleEvent.RegRip = Telemetry->RegRip;
|
|
handleEvent.RegEFlags = Telemetry->RegEFlags;
|
|
handleEvent.RegDr0 = Telemetry->RegDr0;
|
|
handleEvent.RegDr1 = Telemetry->RegDr1;
|
|
handleEvent.RegDr2 = Telemetry->RegDr2;
|
|
handleEvent.RegDr3 = Telemetry->RegDr3;
|
|
handleEvent.RegDr6 = Telemetry->RegDr6;
|
|
handleEvent.RegDr7 = Telemetry->RegDr7;
|
|
handleEvent.StackSnapshotAddress = Telemetry->StackSnapshotAddress;
|
|
safeStackBytes = Telemetry->StackSnapshotSize;
|
|
if (safeStackBytes > RTL_NUMBER_OF(handleEvent.StackSnapshot))
|
|
{
|
|
safeStackBytes = RTL_NUMBER_OF(handleEvent.StackSnapshot);
|
|
}
|
|
handleEvent.StackSnapshotSize = safeStackBytes;
|
|
if (safeStackBytes != 0)
|
|
{
|
|
RtlCopyMemory(handleEvent.StackSnapshot, Telemetry->StackSnapshot, safeStackBytes);
|
|
}
|
|
|
|
BkctlPublishHandleEvent(&handleEvent);
|
|
}
|
|
}
|
|
|
|
static VOID BkchdlCaptureDeepPathData(_In_ HANDLE CallerProcessId, _In_ BOOLEAN ShouldCapture,
|
|
_In_ const MEMORY_BASIC_INFORMATION *Mbi, _In_opt_ const BK_HANDLE_WORK *Work,
|
|
_Inout_ PBK_HANDLE_TELEMETRY Telemetry)
|
|
{
|
|
NTSTATUS status;
|
|
PEPROCESS sourceProcess = NULL;
|
|
PEPROCESS localProcess;
|
|
SIZE_T bytesRead = 0;
|
|
SIZE_T bytesToRead;
|
|
ULONG_PTR regionBase;
|
|
ULONG_PTR sampleAddress;
|
|
ULONG_PTR regionOffset;
|
|
UINT32 sampleSize;
|
|
|
|
if (!ShouldCapture || Mbi == NULL || Telemetry == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Telemetry->DeepPathCandidate = TRUE;
|
|
Telemetry->AllocationBase = Mbi->AllocationBase;
|
|
Telemetry->RegionSize = Mbi->RegionSize;
|
|
Telemetry->RegionState = Mbi->State;
|
|
Telemetry->RegionType = Mbi->Type;
|
|
|
|
if (Work != NULL && Work->EarlyOriginSampleSize != 0 &&
|
|
Work->EarlyOriginAddress == (UINT64)(ULONG_PTR)Telemetry->OriginAddress)
|
|
{
|
|
Telemetry->DeepSampleSize = (Work->EarlyOriginSampleSize > RTL_NUMBER_OF(Telemetry->DeepSample))
|
|
? RTL_NUMBER_OF(Telemetry->DeepSample)
|
|
: Work->EarlyOriginSampleSize;
|
|
RtlCopyMemory(Telemetry->DeepSample, Work->EarlyOriginSample, Telemetry->DeepSampleSize);
|
|
Telemetry->DeepPathCaptured = TRUE;
|
|
Telemetry->DeepPathCacheHit = FALSE;
|
|
BkchdlDeepCacheStore(CallerProcessId, Mbi->BaseAddress, Telemetry->OriginAddress, Mbi->RegionSize, Mbi->Protect,
|
|
Mbi->State, Mbi->Type, Telemetry->DeepSample, Telemetry->DeepSampleSize);
|
|
return;
|
|
}
|
|
|
|
if (Mbi->BaseAddress == NULL || Mbi->RegionSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (BkchdlDeepCacheLookup(CallerProcessId, Mbi->BaseAddress,
|
|
(Telemetry->OriginAddress != NULL) ? Telemetry->OriginAddress : Mbi->BaseAddress,
|
|
Mbi->RegionSize, Mbi->Protect, Mbi->State, Mbi->Type, Telemetry->DeepSample, &sampleSize))
|
|
{
|
|
Telemetry->DeepPathCaptured = TRUE;
|
|
Telemetry->DeepPathCacheHit = TRUE;
|
|
Telemetry->DeepSampleSize = sampleSize;
|
|
return;
|
|
}
|
|
|
|
status = PsLookupProcessByProcessId(CallerProcessId, &sourceProcess);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return;
|
|
}
|
|
|
|
sampleAddress =
|
|
(Telemetry->OriginAddress != NULL) ? (ULONG_PTR)Telemetry->OriginAddress : (ULONG_PTR)Mbi->BaseAddress;
|
|
regionBase = (ULONG_PTR)Mbi->BaseAddress;
|
|
if (sampleAddress < regionBase || Mbi->RegionSize == 0)
|
|
{
|
|
ObDereferenceObject(sourceProcess);
|
|
return;
|
|
}
|
|
|
|
regionOffset = sampleAddress - regionBase;
|
|
if (regionOffset >= Mbi->RegionSize)
|
|
{
|
|
ObDereferenceObject(sourceProcess);
|
|
return;
|
|
}
|
|
|
|
bytesToRead = Mbi->RegionSize - regionOffset;
|
|
if (bytesToRead > sizeof(Telemetry->DeepSample))
|
|
{
|
|
bytesToRead = sizeof(Telemetry->DeepSample);
|
|
}
|
|
if (bytesToRead == 0)
|
|
{
|
|
ObDereferenceObject(sourceProcess);
|
|
return;
|
|
}
|
|
|
|
localProcess = PsGetCurrentProcess();
|
|
status = MmCopyVirtualMemory(sourceProcess, (const VOID *)sampleAddress, localProcess, Telemetry->DeepSample,
|
|
bytesToRead, KernelMode, &bytesRead);
|
|
ObDereferenceObject(sourceProcess);
|
|
if (!NT_SUCCESS(status) || bytesRead == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bytesRead > RTL_NUMBER_OF(Telemetry->DeepSample))
|
|
{
|
|
bytesRead = RTL_NUMBER_OF(Telemetry->DeepSample);
|
|
}
|
|
Telemetry->DeepSampleSize = (ULONG)bytesRead;
|
|
Telemetry->DeepPathCaptured = TRUE;
|
|
Telemetry->DeepPathCacheHit = FALSE;
|
|
BkchdlDeepCacheStore(CallerProcessId, Mbi->BaseAddress,
|
|
(Telemetry->OriginAddress != NULL) ? Telemetry->OriginAddress : Mbi->BaseAddress,
|
|
Mbi->RegionSize, Mbi->Protect, Mbi->State, Mbi->Type, Telemetry->DeepSample,
|
|
Telemetry->DeepSampleSize);
|
|
}
|
|
|
|
static VOID BkchdlClassifyUserOrigin(_In_ HANDLE CallerProcessId, _In_ HANDLE CallerThreadId,
|
|
_In_ BOOLEAN IsThreadObject, _In_ BOOLEAN IsDuplicateOperation,
|
|
_In_ ULONG FrameCount, _In_reads_(FrameCount) PVOID *Frames,
|
|
_In_opt_ const BK_HANDLE_WORK *Work, _Out_ PBK_HANDLE_TELEMETRY Telemetry)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
CLIENT_ID clientId;
|
|
HANDLE processHandle = NULL;
|
|
PEPROCESS sourceProcess = NULL;
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
UCHAR sectionNameRaw[1024];
|
|
PUNICODE_STRING sectionName;
|
|
UNICODE_STRING originPathUs;
|
|
BOOLEAN execProtect;
|
|
BOOLEAN fromNtdll;
|
|
BOOLEAN fromSyscallModule;
|
|
BOOLEAN deepPathGate;
|
|
PVOID ntdllBase = NULL;
|
|
PCSTR expectedExportName;
|
|
ULONG expectedSyscallNumber;
|
|
ULONG observedSyscallNumber;
|
|
BOOLEAN hasExpectedSyscall;
|
|
BOOLEAN hasObservedSyscall;
|
|
BOOLEAN originInsideExpectedExport;
|
|
ULONG_PTR stackLimit;
|
|
ULONG_PTR stackBase;
|
|
|
|
RtlZeroMemory(Telemetry, sizeof(*Telemetry));
|
|
Telemetry->OpenProcessStatus = STATUS_UNSUCCESSFUL;
|
|
Telemetry->BasicInfoStatus = STATUS_UNSUCCESSFUL;
|
|
Telemetry->SectionNameStatus = STATUS_UNSUCCESSFUL;
|
|
BkchdlApplyWorkCaptureToTelemetry(Work, Telemetry);
|
|
|
|
if (FrameCount == 0 || Frames == NULL || Frames[0] == NULL)
|
|
{
|
|
return;
|
|
}
|
|
Telemetry->OriginAddress = Frames[0];
|
|
Telemetry->FrameCount =
|
|
(FrameCount > RTL_NUMBER_OF(Telemetry->Frames)) ? RTL_NUMBER_OF(Telemetry->Frames) : FrameCount;
|
|
RtlCopyMemory(Telemetry->Frames, Frames, Telemetry->FrameCount * sizeof(PVOID));
|
|
|
|
InitializeObjectAttributes(&objectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
clientId.UniqueProcess = CallerProcessId;
|
|
clientId.UniqueThread = NULL;
|
|
|
|
status = ZwOpenProcess(&processHandle, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, &objectAttributes,
|
|
&clientId);
|
|
Telemetry->OpenProcessStatus = status;
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
BK_DBG_PRINT(DPFLTR_TRACE_LEVEL, "BK[DBG]: ZwOpenProcess failed callerPid=%p status=0x%08X.\n", CallerProcessId,
|
|
(ULONG)status);
|
|
return;
|
|
}
|
|
if (InterlockedCompareExchange(&g_HandleMonitorStopping, 0, 0) != 0)
|
|
{
|
|
if (sourceProcess != NULL)
|
|
{
|
|
ObDereferenceObject(sourceProcess);
|
|
}
|
|
ZwClose(processHandle);
|
|
return;
|
|
}
|
|
(void)PsLookupProcessByProcessId(CallerProcessId, &sourceProcess);
|
|
|
|
RtlZeroMemory(&mbi, sizeof(mbi));
|
|
status = ZwQueryVirtualMemory(processHandle, Telemetry->OriginAddress, BkchdlMemoryBasicInformation, &mbi,
|
|
sizeof(mbi), NULL);
|
|
Telemetry->BasicInfoStatus = status;
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
Telemetry->OriginProtect = mbi.Protect;
|
|
Telemetry->AllocationBase = mbi.AllocationBase;
|
|
Telemetry->RegionSize = mbi.RegionSize;
|
|
Telemetry->RegionState = mbi.State;
|
|
Telemetry->RegionType = mbi.Type;
|
|
}
|
|
else
|
|
{
|
|
BK_DBG_PRINT(DPFLTR_TRACE_LEVEL, "BK[DBG]: ZwQueryVirtualMemory(basic) failed callerPid=%p status=0x%08X.\n",
|
|
CallerProcessId, (ULONG)status);
|
|
}
|
|
if (InterlockedCompareExchange(&g_HandleMonitorStopping, 0, 0) != 0)
|
|
{
|
|
if (sourceProcess != NULL)
|
|
{
|
|
ObDereferenceObject(sourceProcess);
|
|
}
|
|
ZwClose(processHandle);
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(sectionNameRaw, sizeof(sectionNameRaw));
|
|
status = ZwQueryVirtualMemory(processHandle, Telemetry->OriginAddress, BkchdlMemorySectionName, sectionNameRaw,
|
|
sizeof(sectionNameRaw), NULL);
|
|
Telemetry->SectionNameStatus = status;
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
sectionName = (PUNICODE_STRING)sectionNameRaw;
|
|
BkstrSafeCopyUnicode(sectionName, Telemetry->OriginPath, RTL_NUMBER_OF(Telemetry->OriginPath));
|
|
}
|
|
else
|
|
{
|
|
BK_DBG_PRINT(DPFLTR_TRACE_LEVEL, "BK[DBG]: ZwQueryVirtualMemory(section) failed callerPid=%p status=0x%08X.\n",
|
|
CallerProcessId, (ULONG)status);
|
|
}
|
|
|
|
RtlInitUnicodeString(&originPathUs, Telemetry->OriginPath);
|
|
execProtect = BkprotIsExecutableProtection(Telemetry->OriginProtect);
|
|
fromNtdll = BkchdlIsNtdllPath(&originPathUs);
|
|
fromSyscallModule = BkchdlIsSyscallStubModulePath(&originPathUs);
|
|
// Capture sample bytes for executable origins, including ntdll/win32u stubs, so UI can disassemble real origin
|
|
// bytes.
|
|
deepPathGate = execProtect;
|
|
BkchdlCaptureDeepPathData(CallerProcessId, deepPathGate, &mbi, Work, Telemetry);
|
|
|
|
Telemetry->StackValidated = BkchdlValidateStackFrames(processHandle, Telemetry->FrameCount, Telemetry->Frames,
|
|
&Telemetry->StackSpoofSuspect);
|
|
if (sourceProcess != NULL && Telemetry->FrameCount > 1 && Telemetry->Frames[1] != NULL)
|
|
{
|
|
Telemetry->ReturnAddressValid = BkchdlValidateReturnAddress(sourceProcess, Telemetry->Frames[1]);
|
|
}
|
|
else
|
|
{
|
|
Telemetry->ReturnAddressValid = FALSE;
|
|
}
|
|
|
|
if (fromNtdll && Telemetry->AllocationBase != NULL)
|
|
{
|
|
ntdllBase = Telemetry->AllocationBase;
|
|
}
|
|
else
|
|
{
|
|
(void)BkchdlGetNtdllBaseFromFrames(processHandle, Telemetry->FrameCount, Telemetry->Frames, &ntdllBase);
|
|
}
|
|
|
|
expectedExportName = BkchdlGetExpectedSyscallExport(IsThreadObject, IsDuplicateOperation);
|
|
hasExpectedSyscall =
|
|
BkchdlResolveExpectedSyscallExport(sourceProcess, ntdllBase, expectedExportName, Telemetry->OriginAddress,
|
|
&expectedSyscallNumber, &originInsideExpectedExport);
|
|
hasObservedSyscall =
|
|
BkchdlExtractSyscallNumberNearAddress(sourceProcess, Telemetry->OriginAddress, &observedSyscallNumber);
|
|
Telemetry->SyscallExportChecked = hasExpectedSyscall;
|
|
Telemetry->SyscallExportMatch = fromNtdll && originInsideExpectedExport && hasExpectedSyscall &&
|
|
hasObservedSyscall && (expectedSyscallNumber == observedSyscallNumber);
|
|
|
|
Telemetry->ModuleChainChecked = TRUE;
|
|
Telemetry->ModuleChainSane =
|
|
BkchdlValidateModuleChainSanity(processHandle, Telemetry->FrameCount, Telemetry->Frames);
|
|
|
|
Telemetry->UnwindMetadataChecked = (sourceProcess != NULL);
|
|
Telemetry->UnwindMetadataValid =
|
|
Telemetry->UnwindMetadataChecked &&
|
|
BkchdlValidateUnwindMetadata(processHandle, sourceProcess, Telemetry->FrameCount, Telemetry->Frames);
|
|
|
|
stackLimit = 0;
|
|
stackBase = 0;
|
|
Telemetry->TebStackBoundsChecked = (sourceProcess != NULL && CallerThreadId != NULL);
|
|
Telemetry->TebStackBoundsValid =
|
|
Telemetry->TebStackBoundsChecked &&
|
|
BkchdlQueryTebStackBounds(CallerProcessId, CallerThreadId, sourceProcess, &stackLimit, &stackBase);
|
|
Telemetry->FramesOutsideTebStack =
|
|
Telemetry->TebStackBoundsValid &&
|
|
BkchdlFramesOutsideStackBounds(Telemetry->FrameCount, Telemetry->Frames, stackLimit, stackBase);
|
|
|
|
if (sourceProcess != NULL && (Telemetry->CaptureFlags & BK_HANDLE_CAPTURE_CONTEXT_VALID) != 0 &&
|
|
Telemetry->RegRsp != 0)
|
|
{
|
|
BkchdlCaptureStackSnapshot(sourceProcess, Telemetry->RegRsp, Telemetry);
|
|
}
|
|
|
|
if (sourceProcess != NULL)
|
|
{
|
|
ObDereferenceObject(sourceProcess);
|
|
}
|
|
ZwClose(processHandle);
|
|
}
|
|
|
|
static VOID BkchdlHandleWorkRoutine(_In_ PVOID Context)
|
|
{
|
|
PBK_HANDLE_WORK work = (PBK_HANDLE_WORK)Context;
|
|
BK_HANDLE_TELEMETRY telemetry;
|
|
UNICODE_STRING originPathUs;
|
|
BOOLEAN execProtect;
|
|
BOOLEAN fromNtdll;
|
|
BOOLEAN fromSyscallModule;
|
|
BOOLEAN fromExe;
|
|
BOOLEAN highRiskAccess;
|
|
BOOLEAN directSyscallEntryAnomaly;
|
|
UINT32 anomalySignals;
|
|
BK_HANDLE_CLASSIFICATION classification = BkchdlHandleUnknown;
|
|
|
|
PAGED_CODE();
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (InterlockedCompareExchange(&g_HandleMonitorStopping, 0, 0) != 0)
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (!BkctlIsArmedFast())
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
BkchdlClassifyUserOrigin(work->CallerPid, work->CallerTid, work->IsThreadObject, work->IsDuplicateOperation,
|
|
work->FrameCount, work->Frames, work, &telemetry);
|
|
|
|
RtlInitUnicodeString(&originPathUs, telemetry.OriginPath);
|
|
execProtect = BkprotIsExecutableProtection(telemetry.OriginProtect);
|
|
|
|
fromNtdll = BkchdlIsNtdllPath(&originPathUs);
|
|
fromSyscallModule = BkchdlIsSyscallStubModulePath(&originPathUs);
|
|
fromExe = BkstrUnicodeContainsInsensitive(&originPathUs, L".exe", 4);
|
|
highRiskAccess = BkchdlHandleAccessIsHighRisk(work->DesiredAccess, work->IsThreadObject);
|
|
anomalySignals = BkchdlCountHandleAnomalySignals(execProtect, fromSyscallModule, &telemetry);
|
|
directSyscallEntryAnomaly =
|
|
execProtect && (!fromSyscallModule || (telemetry.SyscallExportChecked && !telemetry.SyscallExportMatch));
|
|
if (fromSyscallModule && telemetry.StackValidated && !telemetry.StackSpoofSuspect && telemetry.ReturnAddressValid &&
|
|
telemetry.SyscallExportChecked && telemetry.SyscallExportMatch && telemetry.ModuleChainChecked &&
|
|
telemetry.ModuleChainSane && telemetry.UnwindMetadataChecked && telemetry.UnwindMetadataValid &&
|
|
telemetry.TebStackBoundsChecked && telemetry.TebStackBoundsValid && telemetry.FramesOutsideTebStack)
|
|
{
|
|
classification = BkchdlHandleLegitimateSyscall;
|
|
}
|
|
else if (highRiskAccess && execProtect && (directSyscallEntryAnomaly || anomalySignals >= 2))
|
|
{
|
|
classification = BkchdlHandleDirectSyscallSuspect;
|
|
}
|
|
else
|
|
{
|
|
classification = BkchdlHandleUnknown;
|
|
}
|
|
|
|
BkchdlLogHandleTelemetry(classification, work->CallerPid, work->CallerTid, work->TargetPid, work->TargetTid,
|
|
work->ObjectAddress, work->DesiredAccess, work->IsThreadObject, work->IsDuplicateOperation,
|
|
execProtect, fromNtdll, fromExe, &telemetry);
|
|
|
|
BK_DBG_PRINT(DPFLTR_INFO_LEVEL,
|
|
"BK[DBG]: handle event caller=%p target=%p access=0x%08X class=%s open=0x%08X "
|
|
"basic=0x%08X section=0x%08X frames=%lu.\n",
|
|
work->CallerPid, work->TargetPid, work->DesiredAccess, BkchdlClassToString(classification),
|
|
(ULONG)telemetry.OpenProcessStatus, (ULONG)telemetry.BasicInfoStatus,
|
|
(ULONG)telemetry.SectionNameStatus, work->FrameCount);
|
|
|
|
Exit:
|
|
BkchdlHandleReleaseWorkSlot();
|
|
ExFreePoolWithTag(work, 'hdtT');
|
|
}
|
|
|
|
static OB_PREOP_CALLBACK_STATUS BkchdlProcessPreOperation(_In_ PVOID RegistrationContext,
|
|
_Inout_ POB_PRE_OPERATION_INFORMATION OperationInformation)
|
|
{
|
|
ULONGLONG tempusStartQpc = BktmpEnter(BktmpSubsystemHandleMonitor);
|
|
ACCESS_MASK desiredAccess;
|
|
ACCESS_MASK originalDesiredAccess;
|
|
ACCESS_MASK sanitizedAccess;
|
|
HANDLE callerPid;
|
|
HANDLE callerTid;
|
|
HANDLE targetPid;
|
|
HANDLE targetTid = NULL;
|
|
PEPROCESS targetProcess;
|
|
PETHREAD targetThread;
|
|
PBK_HANDLE_WORK work;
|
|
PVOID userFrames[BK_MAX_FULL_EVENT_FRAMES] = {0};
|
|
ULONG frameCount;
|
|
ULONG copyCount;
|
|
ULONG fullCopyCount;
|
|
BOOLEAN hasVmWriteOrFull;
|
|
BOOLEAN hasThreadContextAccess;
|
|
BOOLEAN enterpriseCandidate;
|
|
UINT32 intentFlags;
|
|
UINT32 streamMask;
|
|
UINT32 callerPid32;
|
|
UINT32 targetPid32;
|
|
UINT32 secondaryPid32;
|
|
BOOLEAN shouldCaptureStack;
|
|
BOOLEAN isThreadObject = FALSE;
|
|
BOOLEAN isDuplicateOperation = FALSE;
|
|
BOOLEAN isProtectedTarget;
|
|
BOOLEAN trustedProtectedCaller;
|
|
LONG failureCounter;
|
|
|
|
UNREFERENCED_PARAMETER(RegistrationContext);
|
|
|
|
if (InterlockedCompareExchange(&g_HandleMonitorStopping, 0, 0) != 0)
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
if (OperationInformation == NULL || OperationInformation->KernelHandle)
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE)
|
|
{
|
|
desiredAccess = OperationInformation->Parameters->CreateHandleInformation.DesiredAccess;
|
|
}
|
|
else if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE)
|
|
{
|
|
desiredAccess = OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess;
|
|
isDuplicateOperation = TRUE;
|
|
}
|
|
else
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
originalDesiredAccess = desiredAccess;
|
|
sanitizedAccess = desiredAccess;
|
|
hasVmWriteOrFull = FALSE;
|
|
hasThreadContextAccess = FALSE;
|
|
enterpriseCandidate = FALSE;
|
|
if (OperationInformation->ObjectType == *PsProcessType)
|
|
{
|
|
targetProcess = (PEPROCESS)OperationInformation->Object;
|
|
targetPid = PsGetProcessId(targetProcess);
|
|
|
|
hasVmWriteOrFull = ((originalDesiredAccess & PROCESS_VM_OPERATION) != 0) ||
|
|
((originalDesiredAccess & PROCESS_VM_WRITE) != 0) ||
|
|
((originalDesiredAccess & PROCESS_CREATE_THREAD) != 0) ||
|
|
((originalDesiredAccess & PROCESS_DUP_HANDLE) != 0) ||
|
|
((originalDesiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS);
|
|
enterpriseCandidate =
|
|
BkchdlProcessEnterpriseCandidate(targetProcess, PsGetCurrentProcessId(), targetPid, originalDesiredAccess);
|
|
}
|
|
else if (OperationInformation->ObjectType == *PsThreadType)
|
|
{
|
|
isThreadObject = TRUE;
|
|
targetThread = (PETHREAD)OperationInformation->Object;
|
|
targetPid = PsGetThreadProcessId(targetThread);
|
|
targetTid = PsGetThreadId(targetThread);
|
|
|
|
hasThreadContextAccess = ((originalDesiredAccess & THREAD_SET_CONTEXT) != 0) ||
|
|
((originalDesiredAccess & THREAD_GET_CONTEXT) != 0) ||
|
|
((originalDesiredAccess & THREAD_SUSPEND_RESUME) != 0) ||
|
|
((originalDesiredAccess & THREAD_ALL_ACCESS) == THREAD_ALL_ACCESS);
|
|
enterpriseCandidate = (hasThreadContextAccess && PsGetCurrentProcessId() != targetPid);
|
|
}
|
|
else
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
callerPid = PsGetCurrentProcessId();
|
|
callerTid = PsGetCurrentThreadId();
|
|
callerPid32 = (UINT32)(ULONG_PTR)callerPid;
|
|
targetPid32 = (UINT32)(ULONG_PTR)targetPid;
|
|
isProtectedTarget = BkcprocIsProtectedPid(targetPid32);
|
|
trustedProtectedCaller = isProtectedTarget && BkcprocIsTrustedProtectedCaller(callerPid32, targetPid32);
|
|
if (isProtectedTarget && !trustedProtectedCaller)
|
|
{
|
|
sanitizedAccess &= isThreadObject ? BK_PROTECTED_THREAD_ALLOWED_ACCESS : BK_PROTECTED_PROCESS_ALLOWED_ACCESS;
|
|
if (sanitizedAccess != desiredAccess)
|
|
{
|
|
if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE)
|
|
{
|
|
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = sanitizedAccess;
|
|
}
|
|
else
|
|
{
|
|
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess = sanitizedAccess;
|
|
}
|
|
desiredAccess = sanitizedAccess;
|
|
}
|
|
}
|
|
|
|
if (!hasVmWriteOrFull && !hasThreadContextAccess && !enterpriseCandidate)
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
if (!BkctlIsArmedFast())
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
secondaryPid32 = (targetPid32 != callerPid32) ? targetPid32 : 0;
|
|
streamMask = (hasVmWriteOrFull || hasThreadContextAccess) ? BK_STREAM_HANDLE : 0;
|
|
if (hasVmWriteOrFull)
|
|
{
|
|
streamMask |= BK_STREAM_MEMORY;
|
|
}
|
|
if (enterpriseCandidate)
|
|
{
|
|
streamMask |= BK_STREAM_ENTERPRISE;
|
|
}
|
|
|
|
if (!BkctlHasPidInterest(callerPid32, secondaryPid32, streamMask))
|
|
{
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
intentFlags = 0;
|
|
if (hasVmWriteOrFull)
|
|
{
|
|
intentFlags |= BK_INTENT_PROCESS_MEMORY;
|
|
}
|
|
if (hasThreadContextAccess)
|
|
{
|
|
intentFlags |= BK_INTENT_THREAD_CONTEXT;
|
|
}
|
|
if (isDuplicateOperation && (intentFlags != 0))
|
|
{
|
|
intentFlags |= BK_INTENT_DUP_HANDLE;
|
|
}
|
|
|
|
if (intentFlags != 0)
|
|
{
|
|
BkcorRecordHandleIntent(callerPid, targetPid, originalDesiredAccess, intentFlags);
|
|
}
|
|
if (isThreadObject)
|
|
{
|
|
BkapcRecordThreadHandleIntent(callerPid, targetPid, originalDesiredAccess, isDuplicateOperation);
|
|
}
|
|
|
|
if (!BkchdlHandleTryAcquireWorkSlot())
|
|
{
|
|
failureCounter = InterlockedIncrement(&g_HandleCallbackDropLogCounter);
|
|
if (failureCounter == 1 || ((failureCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL,
|
|
"BK: handle callback drop caller=%p target=%p access=0x%08X total=%lu.\n", callerPid, targetPid,
|
|
originalDesiredAccess, (ULONG)failureCounter);
|
|
}
|
|
BK_DBG_PRINT(DPFLTR_WARNING_LEVEL,
|
|
"BK[DBG]: dropping handle preop caller=%p target=%p access=0x%08X (work slot unavailable).\n",
|
|
callerPid, targetPid, originalDesiredAccess);
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
work = (PBK_HANDLE_WORK)BkpoolAllocateCompat(POOL_FLAG_NON_PAGED, sizeof(*work), 'hdtT');
|
|
if (work == NULL)
|
|
{
|
|
failureCounter = InterlockedIncrement(&g_HandleAllocFailureCounter);
|
|
if (failureCounter == 1 || ((failureCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
|
|
"BK: handle callback alloc failure caller=%p target=%p access=0x%08X total=%lu.\n", callerPid,
|
|
targetPid, originalDesiredAccess, (ULONG)failureCounter);
|
|
}
|
|
BK_DBG_PRINT(DPFLTR_ERROR_LEVEL, "BK[DBG]: ExAllocatePool2 failed for handle work item.\n");
|
|
BkchdlHandleReleaseWorkSlot();
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
RtlZeroMemory(work, sizeof(*work));
|
|
work->CallerPid = callerPid;
|
|
work->CallerTid = callerTid;
|
|
work->TargetPid = targetPid;
|
|
work->TargetTid = targetTid;
|
|
work->ObjectAddress = (UINT64)(ULONG_PTR)OperationInformation->Object;
|
|
work->DesiredAccess = originalDesiredAccess;
|
|
work->IsThreadObject = isThreadObject;
|
|
work->IsDuplicateOperation = isDuplicateOperation;
|
|
|
|
frameCount = 0;
|
|
copyCount = 0;
|
|
fullCopyCount = 0;
|
|
BkchdlCaptureRegisterSnapshot(work);
|
|
shouldCaptureStack = isDuplicateOperation ||
|
|
((originalDesiredAccess & (PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD |
|
|
PROCESS_DUP_HANDLE | THREAD_SET_CONTEXT | THREAD_GET_CONTEXT |
|
|
THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION)) != 0) ||
|
|
((originalDesiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS) ||
|
|
((originalDesiredAccess & THREAD_ALL_ACCESS) == THREAD_ALL_ACCESS);
|
|
if (shouldCaptureStack)
|
|
{
|
|
__try
|
|
{
|
|
frameCount = RtlWalkFrameChain(userFrames, RTL_NUMBER_OF(userFrames), RTL_WALK_USER_MODE_STACK);
|
|
copyCount = (frameCount > RTL_NUMBER_OF(work->Frames)) ? RTL_NUMBER_OF(work->Frames) : frameCount;
|
|
fullCopyCount =
|
|
(frameCount > RTL_NUMBER_OF(work->FullFrames)) ? RTL_NUMBER_OF(work->FullFrames) : frameCount;
|
|
if (copyCount != 0)
|
|
{
|
|
RtlCopyMemory(work->Frames, userFrames, copyCount * sizeof(PVOID));
|
|
}
|
|
if (fullCopyCount != 0)
|
|
{
|
|
RtlCopyMemory(work->FullFrames, userFrames, fullCopyCount * sizeof(PVOID));
|
|
}
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
copyCount = 0;
|
|
fullCopyCount = 0;
|
|
RtlZeroMemory(work->Frames, sizeof(work->Frames));
|
|
RtlZeroMemory(work->FullFrames, sizeof(work->FullFrames));
|
|
InterlockedIncrement(&g_HandleStackCaptureFaults);
|
|
failureCounter = InterlockedIncrement(&g_HandleStackFaultLogCounter);
|
|
if (failureCounter == 1 || ((failureCounter & 0xFF) == 0))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL,
|
|
"BK: handle callback stack capture fault caller=%p target=%p access=0x%08X total=%lu.\n",
|
|
callerPid, targetPid, desiredAccess, (ULONG)failureCounter);
|
|
}
|
|
BK_DBG_PRINT(DPFLTR_WARNING_LEVEL, "BK[DBG]: RtlWalkFrameChain fault caller=%p target=%p access=0x%08X.\n",
|
|
callerPid, targetPid, desiredAccess);
|
|
}
|
|
}
|
|
|
|
work->FrameCount = copyCount;
|
|
work->FullFrameCount = fullCopyCount;
|
|
if (copyCount != 0 && work->Frames[0] != NULL)
|
|
{
|
|
BkchdlCaptureImmediateOriginSample(callerPid, work->Frames[0], work);
|
|
}
|
|
|
|
ExInitializeWorkItem(&work->WorkItem, BkchdlHandleWorkRoutine, work);
|
|
ExQueueWorkItem(&work->WorkItem, DelayedWorkQueue);
|
|
BK_DBG_PRINT(DPFLTR_TRACE_LEVEL, "BK[DBG]: queued handle work caller=%p target=%p access=0x%08X frames=%lu.\n",
|
|
callerPid, targetPid, desiredAccess, copyCount);
|
|
|
|
BktmpLeave(BktmpSubsystemHandleMonitor, tempusStartQpc);
|
|
return OB_PREOP_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
BkchdlInitialize(VOID)
|
|
{
|
|
NTSTATUS status;
|
|
LARGE_INTEGER freq;
|
|
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
return STATUS_INVALID_DEVICE_STATE;
|
|
}
|
|
if (g_HandleMonitorRegistered)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
KeInitializeEvent(&g_HandleAllWorkDone, NotificationEvent, TRUE);
|
|
KeInitializeSpinLock(&g_DeepCacheLock);
|
|
RtlZeroMemory(g_DeepCache, sizeof(g_DeepCache));
|
|
InterlockedExchange(&g_DeepCacheWriteIndex, -1);
|
|
KeQueryPerformanceCounter(&freq);
|
|
g_DeepCacheQpcFrequency = (freq.QuadPart > 0) ? (ULONGLONG)freq.QuadPart : 1;
|
|
InterlockedExchange(&g_HandleMonitorStopping, 0);
|
|
InterlockedExchange(&g_HandleOutstandingWork, 0);
|
|
InterlockedExchange(&g_HandleDroppedWork, 0);
|
|
InterlockedExchange(&g_HandleStackCaptureFaults, 0);
|
|
|
|
RtlZeroMemory(&g_OperationRegistration, sizeof(g_OperationRegistration));
|
|
g_OperationRegistration[0].ObjectType = PsProcessType;
|
|
g_OperationRegistration[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
|
g_OperationRegistration[0].PreOperation = BkchdlProcessPreOperation;
|
|
g_OperationRegistration[0].PostOperation = NULL;
|
|
|
|
g_OperationRegistration[1].ObjectType = PsThreadType;
|
|
g_OperationRegistration[1].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
|
g_OperationRegistration[1].PreOperation = BkchdlProcessPreOperation;
|
|
g_OperationRegistration[1].PostOperation = NULL;
|
|
|
|
RtlZeroMemory(&g_CallbackRegistration, sizeof(g_CallbackRegistration));
|
|
g_CallbackRegistration.Version = OB_FLT_REGISTRATION_VERSION;
|
|
g_CallbackRegistration.OperationRegistrationCount = RTL_NUMBER_OF(g_OperationRegistration);
|
|
g_CallbackRegistration.RegistrationContext = NULL;
|
|
g_CallbackRegistration.OperationRegistration = g_OperationRegistration;
|
|
RtlInitUnicodeString(&g_CallbackAltitude, g_CallbackAltitudeBuffer);
|
|
g_CallbackRegistration.Altitude = g_CallbackAltitude;
|
|
|
|
status = ObRegisterCallbacks(&g_CallbackRegistration, &g_ProcessObRegistrationHandle);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "BK: ObRegisterCallbacks failed (0x%08X).\n", status);
|
|
return status;
|
|
}
|
|
|
|
g_HandleMonitorRegistered = TRUE;
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "BK: process handle monitor initialized.\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID BkchdlUninitialize(VOID)
|
|
{
|
|
LARGE_INTEGER waitInterval;
|
|
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
return;
|
|
}
|
|
if (!g_HandleMonitorRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InterlockedExchange(&g_HandleMonitorStopping, 1);
|
|
if (g_ProcessObRegistrationHandle != NULL)
|
|
{
|
|
ObUnRegisterCallbacks(g_ProcessObRegistrationHandle);
|
|
g_ProcessObRegistrationHandle = NULL;
|
|
}
|
|
g_HandleMonitorRegistered = FALSE;
|
|
|
|
waitInterval.QuadPart = -(LONGLONG)1000 * 10000;
|
|
while (InterlockedCompareExchange(&g_HandleOutstandingWork, 0, 0) != 0)
|
|
{
|
|
NTSTATUS waitStatus;
|
|
|
|
waitStatus = KeWaitForSingleObject(&g_HandleAllWorkDone, Executive, KernelMode, FALSE, &waitInterval);
|
|
if (waitStatus == STATUS_TIMEOUT)
|
|
{
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "BK: handle monitor draining (outstanding=%ld).\n",
|
|
InterlockedCompareExchange(&g_HandleOutstandingWork, 0, 0));
|
|
}
|
|
}
|
|
|
|
InterlockedExchange(&g_HandleMonitorStopping, 0);
|
|
RtlZeroMemory(g_DeepCache, sizeof(g_DeepCache));
|
|
InterlockedExchange(&g_DeepCacheWriteIndex, -1);
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
|
|
"BK: process handle monitor uninitialized (dropped=%ld, stackCaptureFaults=%ld).\n",
|
|
InterlockedCompareExchange(&g_HandleDroppedWork, 0, 0),
|
|
InterlockedCompareExchange(&g_HandleStackCaptureFaults, 0, 0));
|
|
}
|
|
|
|
BOOLEAN
|
|
BkchdlSelfCheck(VOID)
|
|
{
|
|
if (!g_HandleMonitorRegistered)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (g_ProcessObRegistrationHandle == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (g_CallbackRegistration.OperationRegistrationCount != RTL_NUMBER_OF(g_OperationRegistration))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (g_CallbackRegistration.OperationRegistration != g_OperationRegistration)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (g_OperationRegistration[0].PreOperation != BkchdlProcessPreOperation ||
|
|
g_OperationRegistration[1].PreOperation != BkchdlProcessPreOperation)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (InterlockedCompareExchange(&g_HandleOutstandingWork, 0, 0) < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|