Files
2026-05-06 09:11:05 +10:00

576 lines
25 KiB
C++

#ifndef BK_CONTROLLER_PRIVATE_H
#define BK_CONTROLLER_PRIVATE_H
#include <windows.h>
#include <winsvc.h>
#include <sddl.h>
#include <strsafe.h>
#include <tdh.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <memory>
#include <array>
#include <vector>
#include <atomic>
#include "util/handles.h"
#include "..\\..\\sensor\\sensor_core.h"
#include "..\\..\\..\\abi\\blackbird_ipc.h"
#include "heuristics\\heuristics.h"
// ---------------------------------------------------------------------------
// Compile-time constants
// Using inline constexpr instead of #define gives type safety and scoping.
// String literals and computed masks that must stay as macros are kept below.
// ---------------------------------------------------------------------------
inline constexpr DWORD BK_CONTROLLER_MAX_CLIENTS = 256u;
inline constexpr DWORD BK_CONTROLLER_INVALID_SLOT = 0xFFFFFFFFu;
inline constexpr DWORD BK_CONTROLLER_CLIENT_MASK_DWORDS = (BK_CONTROLLER_MAX_CLIENTS + 31u) / 32u;
inline constexpr DWORD BK_CONTROLLER_MAX_CLIENT_SUBSCRIPTIONS = 4096u;
inline constexpr DWORD BK_CONTROLLER_MAX_CLIENT_QUEUE_DEPTH = 4096u;
inline constexpr DWORD BK_CONTROLLER_MAX_CLIENT_ETW_QUEUE_DEPTH = 8192u;
inline constexpr DWORD BK_CONTROLLER_SHARED_IOCTL_RING_CAPACITY = 262144u;
inline constexpr DWORD BK_CONTROLLER_SHARED_ETW_RING_CAPACITY = 65536u;
inline constexpr DWORD BK_CONTROLLER_DYNAMIC_SUBSCRIPTION_TTL_MS = 0u;
inline constexpr DWORD BK_CONTROLLER_DYNAMIC_SUBSCRIPTION_MAX_DEPTH = 0xFFFFFFFFu;
inline constexpr DWORD BK_CONTROLLER_HOOK_READY_TIMEOUT_MS = 15000u;
inline constexpr DWORD BK_CONTROLLER_SERVER_ACCEPT_THREADS = 3u;
inline constexpr DWORD BK_CONTROLLER_HOOK_ACCEPT_THREADS = 1u;
inline constexpr DWORD BK_CONTROLLER_HOLLOW_MAX_ENTRIES = 256u;
inline constexpr DWORD BK_CONTROLLER_HOLLOW_WINDOW_MS = 30000u;
inline constexpr DWORD BK_CONTROLLER_MANUAL_MAP_EMIT_COOLDOWN_MS = 4000u;
inline constexpr DWORD BK_CONTROLLER_MANUAL_MAP_PROBE_MIN_INTERVAL_MS = 1500u;
inline constexpr DWORD BK_CONTROLLER_SUBSCRIPTION_APPLY_SYNC_TIMEOUT_MS = 2500u;
inline constexpr UINT64 BK_CONTROLLER_HOLLOW_LARGE_ALLOC_BYTES = 0x8000ull;
inline constexpr UINT32 BK_NTAPI_EXEC_FLAG_CALLER_KERNEL = 0x00000001u;
inline constexpr UINT32 BK_NTAPI_EXEC_FLAG_CALLER_USER = 0x00000002u;
inline constexpr UINT32 BK_NTAPI_EXEC_FLAG_TARGET_CURRENT_PROCESS = 0x00000004u;
inline constexpr UINT32 BK_NTAPI_EXEC_FLAG_SECTION_IMAGE = 0x00000008u;
inline bool ControllerAsciiContainsInsensitive(_In_opt_z_ PCSTR haystack, _In_z_ PCSTR needle)
{
if (haystack == nullptr || needle == nullptr || needle[0] == '\0')
{
return false;
}
const size_t hayLen = strlen(haystack);
const size_t needleLen = strlen(needle);
if (hayLen < needleLen)
{
return false;
}
for (size_t i = 0; i <= (hayLen - needleLen); ++i)
{
if (_strnicmp(haystack + i, needle, needleLen) == 0)
{
return true;
}
}
return false;
}
inline bool ControllerAsciiEqualsInsensitive(_In_opt_z_ PCSTR left, _In_z_ PCSTR right)
{
return (left != nullptr && right != nullptr && _stricmp(left, right) == 0);
}
inline bool ControllerWideContainsInsensitive(_In_opt_z_ PCWSTR haystack, _In_z_ PCWSTR needle)
{
if (haystack == nullptr || needle == nullptr || needle[0] == L'\0')
{
return false;
}
size_t hayLen = wcslen(haystack);
size_t needleLen = wcslen(needle);
if (hayLen < needleLen)
{
return false;
}
for (size_t i = 0; i <= (hayLen - needleLen); ++i)
{
if (_wcsnicmp(haystack + i, needle, needleLen) == 0)
{
return true;
}
}
return false;
}
inline UINT32 ControllerComputeEtwDetectionTraits(_In_ const BKIPC_ETW_EVENT &Event)
{
UINT32 traits = 0;
const bool isDetection = (Event.DetectionName[0] != '\0') || Event.Family == BlackbirdIpcEtwFamilyDetection ||
Event.Family == BlackbirdIpcEtwFamilyThreatIntel || Event.Severity >= 4u;
const bool isProcessFamily = Event.Family == BlackbirdIpcEtwFamilyProcess;
const bool isImageFamily = Event.Family == BlackbirdIpcEtwFamilyImage;
const bool isThreadFamily = Event.Family == BlackbirdIpcEtwFamilyThread;
const bool isApcFamily = Event.Family == BlackbirdIpcEtwFamilyApc;
const bool isSocketFamily = Event.Family == BlackbirdIpcEtwFamilySocket;
if (isDetection)
{
traits |= BKIPC_ETW_TRAIT_DETECTION_CLASS;
}
if (isProcessFamily && (Event.Flags & BKIPC_ETW_FLAG_PROCESS_IS_CREATE) != 0)
{
traits |= BKIPC_ETW_TRAIT_PROCESS_LAUNCH | BKIPC_ETW_TRAIT_SCAN_IMAGE_PATH;
}
if (isImageFamily)
{
traits |= BKIPC_ETW_TRAIT_IMAGE_LOAD | BKIPC_ETW_TRAIT_SCAN_IMAGE_PATH;
}
if (isSocketFamily || ControllerAsciiContainsInsensitive(Event.DetectionName, "NETWORK_CONNECT") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "DOMAIN_RESOLUTION"))
{
traits |= BKIPC_ETW_TRAIT_NETWORK;
}
if ((isThreadFamily && (Event.Flags & BKIPC_ETW_FLAG_THREAD_REMOTE_CREATOR) != 0) || isApcFamily ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "REMOTE_THREAD") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "THREAD_HIJACK") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "APC"))
{
traits |= BKIPC_ETW_TRAIT_REMOTE_EXECUTION | BKIPC_ETW_TRAIT_SCAN_TARGET_PROCESS;
}
if (ControllerAsciiEqualsInsensitive(Event.Operation, "NtAllocateVirtualMemory") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "RWX_ALLOCATION") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "MEMORY_ACTIVITY"))
{
traits |= BKIPC_ETW_TRAIT_MEMORY_ALLOC_RW;
}
if (ControllerAsciiEqualsInsensitive(Event.Operation, "NtWriteVirtualMemory") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "WRITE_VM") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "PE_INJECTION_WRITE"))
{
traits |= BKIPC_ETW_TRAIT_MEMORY_WRITE_VM | BKIPC_ETW_TRAIT_SCAN_TARGET_PROCESS;
}
if (ControllerAsciiEqualsInsensitive(Event.Operation, "NtProtectVirtualMemory") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "PROTECT") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "RWX_ALLOCATION"))
{
traits |= BKIPC_ETW_TRAIT_MEMORY_PROTECT_RX;
}
if (ControllerAsciiContainsInsensitive(Event.DetectionName, "DIRECT_SYSCALL") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "ANOMALY_ON_HANDLE_OP") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "SUSPECT_HANDLE_OPERATION") ||
ControllerAsciiEqualsInsensitive(Event.ClassName, "DIRECT-SYSCALL-SUSPECT") ||
(Event.Flags & BKIPC_ETW_FLAG_SYSCALL_EXPORT_MISMATCH) != 0)
{
traits |= BKIPC_ETW_TRAIT_DIRECT_SYSCALL;
}
if (ControllerAsciiContainsInsensitive(Event.DetectionName, "HOOK_TAMPER") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "AMSI_PATCH_TAMPERED") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "ETW_PATCH_TAMPERED") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "IAT_TAMPER") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "EAT_TAMPER") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "NTDLL_IMAGE_TAMPER") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "SR71_HOOK_WRITE_BLOCKED") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "SR71_HOOK_PROTECT_BLOCKED"))
{
traits |= BKIPC_ETW_TRAIT_HOOK_TAMPER;
}
if (ControllerAsciiContainsInsensitive(Event.DetectionName, "CREDENTIAL_ACCESS"))
{
traits |= BKIPC_ETW_TRAIT_CREDENTIAL_ACCESS;
}
if (ControllerAsciiContainsInsensitive(Event.DetectionName, "PROCESS_IMAGE_TAMPER") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "GHOST") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "HERP") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "DOPPELGANG"))
{
traits |= BKIPC_ETW_TRAIT_IMAGE_TAMPER;
}
if (ControllerAsciiContainsInsensitive(Event.DetectionName, "LOLBIN") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "SCRIPT_HOST_ABUSE") ||
ControllerAsciiContainsInsensitive(Event.DetectionName, "POWERSHELL_"))
{
traits |= BKIPC_ETW_TRAIT_LOLBIN;
}
if (Event.TargetPid != 0 && Event.TargetPid != Event.ProcessId &&
(traits &
(BKIPC_ETW_TRAIT_REMOTE_EXECUTION | BKIPC_ETW_TRAIT_MEMORY_WRITE_VM | BKIPC_ETW_TRAIT_MEMORY_PROTECT_RX)) != 0)
{
traits |= BKIPC_ETW_TRAIT_SCAN_TARGET_PROCESS;
}
if ((isProcessFamily || isImageFamily || ControllerWideContainsInsensitive(Event.EventName, L"load") ||
ControllerWideContainsInsensitive(Event.EventName, L"create")) &&
Event.ImagePath[0] != L'\0')
{
traits |= BKIPC_ETW_TRAIT_SCAN_IMAGE_PATH;
}
return traits;
}
inline UINT32 ControllerHeurFlagsFromDetectionTraits(_In_ UINT32 traits)
{
UINT32 heurFlags = 0;
if ((traits & BKIPC_ETW_TRAIT_MEMORY_ALLOC_RW) != 0)
heurFlags |= BK_HEUR_FLAG_ALLOC_RW;
if ((traits & BKIPC_ETW_TRAIT_MEMORY_WRITE_VM) != 0)
heurFlags |= BK_HEUR_FLAG_WRITE_VM;
if ((traits & BKIPC_ETW_TRAIT_MEMORY_PROTECT_RX) != 0)
heurFlags |= BK_HEUR_FLAG_PROTECT_RX;
if ((traits & BKIPC_ETW_TRAIT_NETWORK) != 0)
heurFlags |= BK_HEUR_FLAG_NETWORK;
if ((traits & BKIPC_ETW_TRAIT_REMOTE_EXECUTION) != 0)
heurFlags |= BK_HEUR_FLAG_REMOTE_TH;
if ((traits & BKIPC_ETW_TRAIT_CREDENTIAL_ACCESS) != 0)
heurFlags |= BK_HEUR_FLAG_CRED_ACCS;
if ((traits & BKIPC_ETW_TRAIT_IMAGE_TAMPER) != 0)
heurFlags |= BK_HEUR_FLAG_IMG_TAMPER;
if ((traits & BKIPC_ETW_TRAIT_LOLBIN) != 0)
heurFlags |= BK_HEUR_FLAG_LOLBIN;
if ((traits & BKIPC_ETW_TRAIT_DETECTION_CLASS) != 0)
heurFlags |= BK_HEUR_FLAG_DETECTION;
return heurFlags;
}
// String literals and computed masks remain as macros (constexpr won't help here).
#define BK_CONTROLLER_SERVICE_NAMEW L"BlackbirdController"
#define BK_NET_SERVICE_NAMEW L"BlackbirdNetSvc"
#define BK_NET_SERVICE_IMAGE_NAMEW L"BlackbirdNetSvc.exe"
#define BK_SENSOR_CORE_IMAGE_NAMEW L"J58.dll"
#define BK_HOOK_IMAGE_NAMEW L"SR71.dll"
#define BK_DRIVER_IMAGE_NAMEW L"Blackbird.sys"
#define BK_CONTROLLER_ETW_SESSION_NAMEW L"BlackbirdControllerSession"
#define BK_CONTROLLER_VERSIONA "2.0.0"
#define BK_CONTROLLER_HOOK_CORE_REQUIRED_MASK \
(BKIPC_HOOK_READY_FLAG_IPC_CONNECTED | BKIPC_HOOK_READY_FLAG_NT | BKIPC_HOOK_READY_FLAG_KI | \
BKIPC_HOOK_READY_FLAG_MODULE)
#define BK_CONTROLLER_HOOK_LAUNCH_REQUIRED_MASK BK_CONTROLLER_HOOK_CORE_REQUIRED_MASK
#define BK_CONTROLLER_HOOK_READY_REQUIRED_MASK BK_CONTROLLER_HOOK_CORE_REQUIRED_MASK
#define BK_CONTROLLER_DRIVER_STREAM_MASK \
(BK_STREAM_HANDLE | BK_STREAM_MEMORY | BK_STREAM_THREAD | BK_STREAM_FILESYSTEM | BK_STREAM_REGISTRY | \
BK_STREAM_TIMING | BK_STREAM_ENTERPRISE)
// ---------------------------------------------------------------------------
// Client role — scoped enum replaces the C typedef enum.
// The underlying type matches DWORD so it is safe to store in DWORD fields
// without a cast, while still preventing implicit integer/enum conflation.
// ---------------------------------------------------------------------------
enum class BkctlrClientRole : DWORD
{
Unknown = 0,
Control = 1,
Hook = 2
};
// Compatibility aliases so the old enumerator names keep working at call sites.
inline constexpr DWORD BkctlrClientRoleUnknown = static_cast<DWORD>(BkctlrClientRole::Unknown);
inline constexpr DWORD BkctlrClientRoleControl = static_cast<DWORD>(BkctlrClientRole::Control);
inline constexpr DWORD BkctlrClientRoleHook = static_cast<DWORD>(BkctlrClientRole::Hook);
typedef struct _BK_CONTROLLER_SUBSCRIPTION
{
DWORD ProcessId;
DWORD StreamMask;
BOOL Dynamic;
DWORD SourceProcessId;
UINT32 Depth;
ULONGLONG LastSeenTick;
} BK_CONTROLLER_SUBSCRIPTION, *PBK_CONTROLLER_SUBSCRIPTION;
/* A memory range owned by the injected SR71.dll — excluded from heuristics
and annotated in the UI as BK instrumentation. */
#define BK_CONTROLLER_MAX_OWNED_RANGES 128u
typedef struct _BK_CONTROLLER_OWNED_RANGE
{
UINT64 BaseAddress;
UINT64 RegionSize;
UINT32 Flags; /* BK_INSTRUMENTATION_FLAG_* */
CHAR Tag[BK_MAX_INSTRUMENTATION_TAG];
} BK_CONTROLLER_OWNED_RANGE, *PBK_CONTROLLER_OWNED_RANGE;
typedef struct _BK_CONTROLLER_EVENT_NODE
{
struct _BK_CONTROLLER_EVENT_NODE *Next;
BK_EVENT_RECORD Record;
} BK_CONTROLLER_EVENT_NODE, *PBK_CONTROLLER_EVENT_NODE;
typedef struct _BK_CONTROLLER_ETW_EVENT_NODE
{
struct _BK_CONTROLLER_ETW_EVENT_NODE *Next;
BKIPC_ETW_EVENT Event;
} BK_CONTROLLER_ETW_EVENT_NODE, *PBK_CONTROLLER_ETW_EVENT_NODE;
typedef struct _BK_CONTROLLER_CLIENT
{
struct _BK_CONTROLLER_CLIENT *Next;
HANDLE Pipe;
DWORD ProcessId;
DWORD SessionId;
DWORD Role;
DWORD SlotIndex;
CRITICAL_SECTION Lock;
DWORD SubscriptionCount;
BK_CONTROLLER_SUBSCRIPTION Subscriptions[BK_CONTROLLER_MAX_CLIENT_SUBSCRIPTIONS];
PBK_CONTROLLER_EVENT_NODE QueueHead;
PBK_CONTROLLER_EVENT_NODE QueueTail;
DWORD QueueDepth;
DWORD DroppedEvents;
HANDLE IoctlQueueDataEvent;
/* Pre-allocated IOCTL node slab: eliminates calloc/free in the hot enqueue path.
* IoctlNodeSlab owns the allocation; IoctlNodeFreeHead is the free-list head. */
PBK_CONTROLLER_EVENT_NODE IoctlNodeSlab;
PBK_CONTROLLER_EVENT_NODE IoctlNodeFreeHead;
/* Pre-allocated ETW node slab: same pattern as the IOCTL slab. */
PBK_CONTROLLER_ETW_EVENT_NODE EtwNodeSlab;
PBK_CONTROLLER_ETW_EVENT_NODE EtwNodeFreeHead;
PBK_CONTROLLER_ETW_EVENT_NODE EtwQueueHead;
PBK_CONTROLLER_ETW_EVENT_NODE EtwQueueTail;
DWORD EtwQueueDepth;
DWORD EtwDroppedEvents;
HANDLE EtwQueueDataEvent;
BOOL SharedRingEnabled;
HANDLE IoctlSharedMapping;
HANDLE IoctlSharedDataEvent;
PBKIPC_SHARED_RING_HEADER IoctlSharedHeader;
PBYTE IoctlSharedRecords;
HANDLE EtwSharedMapping;
HANDLE EtwSharedDataEvent;
PBKIPC_SHARED_RING_HEADER EtwSharedHeader;
PBYTE EtwSharedRecords;
BOOL PendingLaunchArmed;
DWORD PendingLaunchPid;
DWORD PendingAnalysisSubjectKind;
ULONGLONG PendingLaunchArmedTick;
WCHAR PendingLaunchImagePath[BK_MAX_IMAGE_PATH_CHARS];
WCHAR PendingAnalysisSubjectPath[BK_MAX_IMAGE_PATH_CHARS];
ULONGLONG AnalysisSessionId;
DWORD AnalysisRootProcessId;
BOOL AnalysisLaunchOwned;
BOOL AnalysisActive;
ULONGLONG AnalysisStartedTick;
volatile LONG HookReadyMask;
ULONGLONG HookReadyTick;
volatile LONG DispatchRefCount;
volatile LONG Detached;
HANDLE DispatchIdleEvent;
volatile LONG WinsockInlineUpgradePending;
/* SR71-registered owned ranges — excluded from heuristics, annotated in UI */
DWORD OwnedRangeCount;
BK_CONTROLLER_OWNED_RANGE
OwnedRanges[BK_CONTROLLER_MAX_OWNED_RANGES]; /* set to 1 when IMAGE_TAMPER triggers inline hook upgrade */
} BK_CONTROLLER_CLIENT, *PBK_CONTROLLER_CLIENT;
inline BOOL ControllerIsBlackbirdOwnedAddress(_In_ const BK_CONTROLLER_CLIENT *Client, _In_ UINT64 Address)
{
if (Client == NULL || Address == 0 || Client->OwnedRangeCount == 0)
return FALSE;
for (DWORD i = 0; i < Client->OwnedRangeCount; ++i)
{
const BK_CONTROLLER_OWNED_RANGE *r = &Client->OwnedRanges[i];
if (r->BaseAddress == 0 || r->RegionSize == 0)
continue;
if (Address >= r->BaseAddress && Address < r->BaseAddress + r->RegionSize)
return TRUE;
}
return FALSE;
}
typedef struct _BK_CONTROLLER_PID_INDEX_ENTRY
{
DWORD ProcessId;
DWORD StreamMask;
DWORD ClientMask[BK_CONTROLLER_CLIENT_MASK_DWORDS];
DWORD ClientStreamMask[BK_CONTROLLER_MAX_CLIENTS];
} BK_CONTROLLER_PID_INDEX_ENTRY, *PBK_CONTROLLER_PID_INDEX_ENTRY;
typedef struct _BK_CONTROLLER_HOLLOW_ENTRY
{
BOOL Active;
DWORD ActorPid;
DWORD TargetPid;
ULONGLONG FirstSeenTick;
ULONGLONG LastSeenTick;
ULONGLONG LastAllocBase;
ULONGLONG LastAllocSize;
ULONG LastAllocProtect;
ULONGLONG LastWriteBase;
ULONGLONG LastWriteSize;
ULONGLONG LastWriteTick;
ULONGLONG LastProtectRxBase;
ULONGLONG LastProtectRxSize;
ULONG LastProtectRxProtect;
ULONGLONG LastProtectRxTick;
ULONGLONG LastThreadStartAddress;
ULONGLONG LastThreadStartTick;
ULONGLONG LastDllPathWriteTick;
ULONGLONG LastThreadCreateTarget;
ULONGLONG LastThreadCreateTick;
UINT32 LastThreadCreateKind;
UINT32 LastThreadCreateHasPe;
ULONGLONG LastThreadCreateRegionBase;
ULONGLONG LastThreadCreateRegionSize;
ULONGLONG LastApcRoutine;
ULONGLONG LastApcTick;
UINT32 LastApcKind;
UINT32 LastApcHasPe;
ULONGLONG LastApcRegionBase;
ULONGLONG LastApcRegionSize;
ULONGLONG LastSetContextRip;
ULONGLONG LastSetContextRsp;
ULONGLONG LastSetContextTick;
UINT32 LastSetContextKind;
UINT32 LastSetContextHasPe;
ULONGLONG LastSetContextRegionBase;
ULONGLONG LastSetContextRegionSize;
ULONGLONG LastSuspendTick;
ULONGLONG LastResumeTick;
ULONGLONG LastImageLoadTick;
ULONGLONG LastImageLoadBase;
ULONGLONG LastImageLoadSize;
WCHAR LastImageLoadPath[BKIPC_MAX_ETW_IMAGE_PATH];
UINT64 Marks;
ULONGLONG LastMediumEmitTick;
ULONGLONG LastStrongEmitTick;
ULONGLONG LastManualMapLikelyEmitTick;
ULONGLONG LastManualMapConfirmedEmitTick;
ULONGLONG LastManualMapHeaderlessEmitTick;
ULONGLONG LastRemoteThreadDllEmitTick;
ULONGLONG LastApcDllEmitTick;
ULONGLONG LastHijackDllEmitTick;
ULONGLONG LastRemoteThreadPrivateEmitTick;
ULONGLONG LastApcPrivateEmitTick;
ULONGLONG LastHijackPrivateEmitTick;
ULONGLONG LastManualMapProbeBase;
ULONGLONG LastManualMapProbeSize;
ULONGLONG LastManualMapProbeTick;
} BK_CONTROLLER_HOLLOW_ENTRY, *PBK_CONTROLLER_HOLLOW_ENTRY;
extern SERVICE_STATUS_HANDLE g_ServiceStatusHandle;
extern SERVICE_STATUS g_ServiceStatus;
extern HANDLE g_StopEvent;
extern HANDLE g_ServerThread;
extern HANDLE g_DriverPumpThread;
extern HANDLE g_EtwThread;
extern HANDLE g_DriverHandle;
extern BKSC_ETW_SESSION *g_EtwSession;
extern BOOL g_ThreatIntelEnabled;
extern DWORD g_ThreatIntelEnableError;
extern volatile LONG g_EtwDetectionEvents;
extern volatile LONG g_EtwTiEvents;
extern OwnedCriticalSection g_ClientListLock;
extern OwnedCriticalSection g_DriverLock;
extern OwnedCriticalSection g_DriverConfigLock;
extern volatile LONG g_DriverSubscriptionsDirty;
extern PBK_CONTROLLER_CLIENT g_ClientList;
extern PBK_CONTROLLER_CLIENT g_ClientSlots[BK_CONTROLLER_MAX_CLIENTS];
extern DWORD g_ClientCount;
extern DWORD g_ProgrammedPids[BK_MAX_PID_LIST];
extern DWORD g_ProgrammedPidCount;
extern BK_CONTROLLER_PID_INDEX_ENTRY g_PidIndex[BK_MAX_PID_LIST];
extern DWORD g_PidIndexCount;
extern SRWLOCK g_HollowLock;
extern BK_CONTROLLER_HOLLOW_ENTRY g_HollowEntries[BK_CONTROLLER_HOLLOW_MAX_ENTRIES];
// Log path written to: %ProgramData%\Blackbird\Node\logs\controller.log
// Rotated to controller.log.1 when file exceeds 4 MB.
VOID ControllerLogInit(VOID);
VOID ControllerLogClose(VOID);
VOID ControllerLog(_In_z_ _Printf_format_string_ PCSTR Format, ...);
VOID ControllerApplyProcessMitigations(VOID);
VOID ControllerUpdateServiceStatus(_In_ DWORD CurrentState, _In_ DWORD Win32ExitCode, _In_ DWORD WaitHint);
BOOL ControllerShouldStop(VOID);
VOID ControllerStopEtwSessionByNameBestEffort(_In_z_ PCWSTR SessionName, _In_z_ PCSTR Reason);
VOID ControllerCleanupStaleEtwSessions(VOID);
BOOL ControllerIsValidStreamMask(_In_ DWORD StreamMask);
VOID ControllerMarkDriverSubscriptionsDirty(VOID);
BOOL ControllerStartDriverSubscriptionWorker(VOID);
VOID ControllerStopDriverSubscriptionWorker(VOID);
BOOL ControllerRequestDriverSubscriptionApply(_In_ BOOL WaitForCompletion, _In_ DWORD TimeoutMs);
BOOL ControllerApplyDriverSubscriptionsIfDirty(VOID);
DWORD ControllerAllocateClientSlotLocked(VOID);
VOID ControllerReleaseClientSlotLocked(_In_ DWORD SlotIndex);
VOID ControllerRebuildPidIndexLocked(_Out_opt_ BOOL *DynamicPruned);
VOID ControllerRemoveSubscriptionAtLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ DWORD Index);
BOOL ControllerDropDynamicDescendantsLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ DWORD RootProcessId);
BOOL ControllerDropProcessSubscriptions(_In_ DWORD ProcessId, _In_z_ PCSTR Reason);
VOID ControllerExpandMonitoringGraph(_In_ DWORD SourceProcessId, _In_ DWORD TargetProcessId,
_In_ DWORD RelationStreamMask);
VOID ControllerClientDestroySharedRingsLocked(_Inout_ BK_CONTROLLER_CLIENT *Client);
VOID ControllerClientFreeQueueLocked(_Inout_ BK_CONTROLLER_CLIENT *Client);
VOID ControllerClientFreeEtwQueueLocked(_Inout_ BK_CONTROLLER_CLIENT *Client);
BOOL ControllerClientRetainForDispatchLocked(_Inout_ BK_CONTROLLER_CLIENT *Client);
VOID ControllerClientReleaseFromDispatch(_Inout_ BK_CONTROLLER_CLIENT *Client);
BOOL ControllerClientDequeueRecordLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _Out_ BK_EVENT_RECORD *Record);
BOOL ControllerClientEnqueueEtwEventLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BKIPC_ETW_EVENT *Event);
BOOL ControllerClientDequeueEtwEventLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _Out_ BKIPC_ETW_EVENT *Event);
BOOL ControllerApplyDriverSubscriptions(VOID);
VOID ControllerDispatchDriverRecord(_In_ const BK_EVENT_RECORD *Record);
BOOL ControllerEtwGetU64Property(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name, _Out_ ULONGLONG *Value);
BOOL ControllerEtwGetU32Property(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name, _Out_ ULONG *Value);
BOOL ControllerEtwGetI32Property(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name, _Out_ LONG *Value);
BOOL ControllerEtwGetU8Property(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name, _Out_ UCHAR *Value);
BOOL ControllerEtwGetBoolProperty(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name, _Out_ BOOL *Value);
BOOL ControllerEtwGetAnsiProperty(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name,
_Out_writes_z_(OutputChars) PSTR Output, _In_ size_t OutputChars);
BOOL ControllerEtwGetWideProperty(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name,
_Out_writes_z_(OutputChars) PWSTR Output, _In_ size_t OutputChars);
BOOL ControllerEtwCopyBinaryProperty(_In_ PEVENT_RECORD Record, _In_z_ PCWSTR Name,
_Out_writes_bytes_(Capacity) PBYTE Output, _In_ ULONG Capacity,
_Out_opt_ UINT32 *BytesCopied);
VOID ControllerDispatchEtwEvent(_In_ const BKIPC_ETW_EVENT *Event);
VOID ControllerProcessHollowingEtwRecord(_In_ PEVENT_RECORD Record, _In_opt_z_ PCWSTR EventName,
_In_ const BKIPC_ETW_EVENT *BrokerEvent);
VOID ControllerObserveUserHookHollowEvent(_In_ const BKIPC_ETW_EVENT *Event);
VOID ControllerResetHollowingState(VOID);
BOOL ControllerStartHollowingWorkers(VOID);
VOID ControllerStopHollowingWorkers(VOID);
BOOL ControllerSymbolServiceStart(VOID);
VOID ControllerSymbolServiceStop(VOID);
VOID ControllerSymbolServiceEnrichEvent(_Inout_ BKIPC_ETW_EVENT *Event);
BOOL ControllerSymbolServiceResolveHookAddress(_In_ DWORD ProcessId, _In_ UINT64 Address,
_Out_writes_z_(TextChars) PWSTR Text, _In_ size_t TextChars,
_Out_opt_ PWSTR Path, _In_ size_t PathChars);
VOID ControllerSymbolServicePrimeHookAddress(_In_ DWORD ProcessId, _In_ UINT64 Address);
BOOL ControllerCreatePipeSecurity(_In_ DWORD ClientRole, _Out_ PSECURITY_ATTRIBUTES SecurityAttributes,
_Outptr_ PSECURITY_DESCRIPTOR *SecurityDescriptor);
DWORD ControllerWaitForHookReady(_In_ DWORD ProcessId);
VOID ControllerDetachClient(_Inout_ BK_CONTROLLER_CLIENT *Client);
VOID ControllerTryMarkProtectedReady(_In_ HANDLE DriverHandle, _In_ BOOL LogFailure);
DWORD WINAPI ControllerClientThreadProc(_In_ LPVOID Context);
// ---------------------------------------------------------------------------
// ABI safety assertions
// Structs that cross the C/C++ or kernel/usermode boundary must remain
// trivially copyable POD so that the compiler cannot silently insert padding
// or vtable pointers.
// ---------------------------------------------------------------------------
static_assert(std::is_trivially_copyable_v<BK_CONTROLLER_SUBSCRIPTION>,
"BK_CONTROLLER_SUBSCRIPTION must remain trivially copyable");
static_assert(std::is_trivially_copyable_v<BK_CONTROLLER_CLIENT>,
"BK_CONTROLLER_CLIENT must remain trivially copyable");
static_assert(std::is_trivially_copyable_v<BK_CONTROLLER_HOLLOW_ENTRY>,
"BK_CONTROLLER_HOLLOW_ENTRY must remain trivially copyable");
static_assert(std::is_trivially_copyable_v<BK_CONTROLLER_PID_INDEX_ENTRY>,
"BK_CONTROLLER_PID_INDEX_ENTRY must remain trivially copyable");
// ---------------------------------------------------------------------------
// Service entry points — must keep C linkage to match SCM expectations.
// ---------------------------------------------------------------------------
extern "C"
{
VOID WINAPI ServiceMain(_In_ DWORD argc, _In_ LPWSTR *argv);
DWORD WINAPI ServiceCtrlHandlerEx(_In_ DWORD control, _In_ DWORD eventType, _In_opt_ LPVOID eventData,
_In_opt_ LPVOID context);
}
#endif