using BlackbirdInterface.Capture; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; namespace BlackbirdInterface { public partial class MainWindow { private BlackbirdBackendSession? _backendSession; private int _backendGeneration; private Action? _sessionIoctlHandler; private Action? _sessionEtwHandler; private Action? _sessionStatsHandler; private Action? _sessionStatusHandler; private CaptureProjectionEngine? _captureProjection; private readonly Dictionary> _etwHistoryByPid = new(); private readonly Dictionary> _heuristicsHistoryByPid = new(); private readonly Dictionary> _filesystemHistoryByPid = new(); private readonly Dictionary> _registryHistoryByPid = new(); private readonly Dictionary> _relationsHistoryByPid = new(); private readonly Dictionary> _apiGraphHistoryByPid = new(); private readonly Dictionary> _extendedHistoryByPid = new(); private readonly Dictionary _apiGraphRowsByKey = new(StringComparer.Ordinal); private readonly Dictionary _extendedRowsByKey = new(StringComparer.Ordinal); private readonly Dictionary _apiGraphReasonByKey = new(StringComparer.Ordinal); private readonly Dictionary _apiGraphActionByKey = new(StringComparer.Ordinal); private readonly Dictionary _apiGraphDecodedByKey = new(StringComparer.Ordinal); private readonly Dictionary _apiGraphFramesByKey = new(StringComparer.Ordinal); private readonly Dictionary _apiGraphSensorByKey = new(StringComparer.Ordinal); private readonly Dictionary _apiGraphTimelineLastEmitByKey = new(StringComparer.Ordinal); private readonly Dictionary _observedHookStackLastPersistByThread = new(StringComparer.Ordinal); private readonly Dictionary _threadStackFallbackLastCaptureByThread = new(StringComparer.Ordinal); private readonly ConcurrentDictionary _pendingThreadStackFallbackCaptures = new(StringComparer.Ordinal); private readonly Dictionary _apiMemorySignalsByPage = new(); private readonly Dictionary _targetExitReasonByPid = new(); private long _usermodeHookEventCount; private long _kernelHookEventCount; private bool _apiGraphSnapshotDirty; private bool _extendedViewSnapshotDirty; private BlackbirdPreflightReport? _lastPreflight; private string? _lastConnectivityIssueSignature; private readonly ConcurrentQueue _pendingIoctlEvents = new(); private readonly ConcurrentQueue _pendingEtwEvents = new(); private readonly ConcurrentQueue _pendingStatusLines = new(); private readonly ConcurrentQueue _pendingUiWork = new(); private readonly AutoResetEvent _backendTransformSignal = new(false); private CancellationTokenSource? _backendTransformCts; private Task? _backendTransformTask; private readonly ConcurrentDictionary _pendingMonitoredProcessRegistrations = new(); private long _pendingIoctlCount; private long _pendingEtwCount; private long _pendingStatusCount; private long _pendingUiWorkCount; private long _droppedIoctlForPressure; private long _droppedEtwForPressure; private long _droppedUiWorkForPressure; private int _backendUiFlushScheduled; private const int MaxBackendTransformItemsPerBatch = 1500; private const int MaxBackendUiItemsPerFlush = 220; private const int MaxBackendUiItemsPerFlushUnderPressure = 1400; private const int MaxBackendStatusLinesPerTransformBatch = 64; private const int MaxBackendStatusLinesPerUiFlush = 32; private const int FilesystemTimelineClusterFlushCount = 600; private const int MaxRecentImageFileAccesses = 2048; private const int MaxRecentImageMapStates = 2048; private const int MaxPendingIoctlEvents = 30000; private const int MaxPendingEtwEvents = 18000; private const int MaxPendingUiWorkItems = 20000; private const int IoctlPressureSoftLimit = (MaxPendingIoctlEvents * 7) / 10; private const int IoctlPressureCriticalLimit = (MaxPendingIoctlEvents * 9) / 10; private const int EtwPressureSoftLimit = (MaxPendingEtwEvents * 7) / 10; private const int EtwPressureCriticalLimit = (MaxPendingEtwEvents * 9) / 10; private const double MemoryHighEntropyBits = 7.15; private const double MemoryEntropyFlipDeltaBits = 0.55; private const uint MemoryEntropyMinSampleBytes = 32; private const uint CorrelationIntentMask = 0x00000007u; private const uint HandleFlagExecProtect = 0x00000001u; private const uint HandleFlagFromNtdll = 0x00000002u; private const uint HandleFlagFromExe = 0x00000004u; private const uint HandleFlagThreadObject = 0x00000010u; private const uint HandleFlagDuplicateOperation = 0x00000020u; private const uint HandleFlagStackValidated = 0x00000400u; private const uint HandleFlagStackSpoofSuspect = 0x00000800u; private const uint HandleFlagSyscallExportMatch = 0x00001000u; private const uint HandleFlagSyscallExportMismatch = 0x00002000u; private const uint HandleFlagTebStackBoundsValid = 0x00010000u; private const uint HandleFlagFramesOutsideTebStack = 0x00020000u; private const uint HighSignalHandleMask = HandleFlagStackSpoofSuspect | HandleFlagSyscallExportMismatch; private const uint ThreadHighSignalMask = 0x00000004u | 0x00000008u | 0x00000010u | 0x00000020u | 0x00000040u | 0x00000080u | 0x00000100u; private static readonly TimeSpan BackendUiFlushBudget = TimeSpan.FromMilliseconds(5); private static readonly TimeSpan BackendUiFlushBudgetUnderPressure = TimeSpan.FromMilliseconds(18); private static readonly TimeSpan FilesystemTimelineClusterWindow = TimeSpan.FromMilliseconds(15000); private static readonly TimeSpan FilesystemTimelineClusterIdleFlush = TimeSpan.FromMilliseconds(1500); private static readonly TimeSpan ApiTimelineEmissionWindow = TimeSpan.FromSeconds(5); private static readonly TimeSpan ImageMapFileCorrelationWindow = TimeSpan.FromSeconds(4); private static readonly TimeSpan ImageMapRepeatWindow = TimeSpan.FromSeconds(45); private static readonly(string Token, string Label)[] VirtualizationArtifactPatterns = { ("\\VBoxGuest", "VirtualBox guest driver"), ("\\VBoxMouse", "VirtualBox mouse driver"), ("\\VBoxMini", "VirtualBox mini redirector"), ("\\VBoxSF", "VirtualBox shared folders"), ("\\VBoxVideo", "VirtualBox video driver"), ("VirtualBox Guest Additions", "VirtualBox guest additions"), ("Oracle\\VirtualBox", "VirtualBox registry key"), ("VBOX__", "VirtualBox ACPI firmware marker"), ("VEN_80EE", "VirtualBox PCI vendor"), ("DiskVBOX", "VirtualBox disk vendor"), ("SystemBiosVersion\\VBOX", "VirtualBox BIOS marker"), ("\\vmhgfs", "VMware shared folders"), ("\\vmmouse", "VMware mouse driver"), ("\\vmci", "VMware VMCI driver"), ("\\vmx_svga", "VMware SVGA driver"), ("VMware Tools", "VMware tools"), ("VMware, Inc.", "VMware registry key"), ("VEN_15AD", "VMware PCI vendor"), ("DiskVMware", "VMware disk vendor"), ("VMW0003", "VMware ACPI marker"), ("qemu-ga", "QEMU guest agent"), ("\\qemufwcfg", "QEMU firmware config driver"), ("QEMU HARDDISK", "QEMU disk vendor"), ("Red Hat VirtIO", "QEMU/VirtIO vendor"), ("\\viostor", "VirtIO storage driver"), ("\\viofs", "VirtIO filesystem driver"), ("\\netkvm", "VirtIO network driver"), ("\\balloon", "VirtIO balloon driver"), ("VEN_1AF4", "VirtIO PCI vendor"), ("\\xen", "Xen driver or registry key"), ("XenTools", "Xen tools"), ("VEN_5853", "Xen PCI vendor"), ("\\prl_", "Parallels driver"), ("Parallels Tools", "Parallels tools"), ("VEN_1AB8", "Parallels PCI vendor"), ("Hyper-V", "Hyper-V registry key"), ("\\VMBus", "Hyper-V VMBus driver"), ("VID_1414", "Microsoft Hyper-V vendor"), ("SystemManufacturer\\Microsoft Corporation", "Microsoft virtual machine manufacturer") }; private string? _lastEtwTimelineSignature; private DateTime _lastEtwTimelineTimestampUtc; private readonly Dictionary _recentHandleEvidenceByPair = new(); private DateTime _lastHandleEvidencePruneUtc = DateTime.MinValue; private readonly Dictionary _filesystemClusterOperationCounts = new(StringComparer.Ordinal); private readonly Dictionary _filesystemClusterSamplesByOperation = new(StringComparer.Ordinal); private readonly Dictionary _crossProcWriteCountByPair = new(StringComparer.Ordinal); private readonly Dictionary _crossProcRwxAllocCountByPair = new(StringComparer.Ordinal); private readonly Dictionary _antiAnalysisCountByEvidence = new(StringComparer.OrdinalIgnoreCase); private readonly object _imageMapCorrelationLock = new(); private readonly List _recentImageFileAccesses = new(); private readonly Dictionary _recentImageMapByPidPath = new(StringComparer.OrdinalIgnoreCase); private DateTime _lastImageMapCorrelationPruneUtc = DateTime.MinValue; private readonly Dictionary _observedProcessStartUtcByPid = new(); private readonly Dictionary _observedProcessStartKeyByPid = new(); private readonly Dictionary _observedInitialThreadIdByPid = new(); private readonly Dictionary _regionLifecycleByIdentity = new(StringComparer.Ordinal); private readonly Dictionary _functionTableBaseByPointer = new(StringComparer.Ordinal); private volatile int _filterRootPid; private readonly ConcurrentDictionary _filterTrackedPids = new(); private volatile int _focusedPid; internal ObservableCollection MonitoredProcesses { get; } = new(); private readonly HashSet _monitoredProcessSet = new(); private int _filesystemClusterTotal; private DateTime _filesystemClusterWindowStartUtc = DateTime.MinValue; private DateTime _filesystemClusterLastSeenUtc = DateTime.MinValue; public void SetFocusedPid(int pid) { _focusedPid = pid; BroadcastFocusedPid(pid); RefreshProcessSelectorBar(); } private void BroadcastFocusedPid(int pid) { EtwPaneHost.FocusedPid = pid; EventsPaneHost.FocusedPid = pid; HeuristicsPaneHost.FocusedPid = pid; FilesystemPaneHost.FocusedPid = pid; ProcessRelationsPaneHost.FocusedPid = pid; } private void RefreshProcessSelectorBar() { ProcessSelectorBarPanel.Children.Clear(); /* "All" chip */ var allBtn = new System.Windows.Controls.Button { Content = "All", Margin = new System.Windows.Thickness(2, 2, 2, 2), Padding = new System.Windows.Thickness(8, 2, 8, 2), FontSize = 11, Tag = 0 }; allBtn.Click += (_, __) => SetFocusedPid(0); ProcessSelectorBarPanel.Children.Add(allBtn); foreach (MonitoredProcessEntry entry in MonitoredProcesses) { var btn = new System.Windows.Controls.Button { Content = entry.Label, Margin = new System.Windows.Thickness(2, 2, 2, 2), Padding = new System.Windows.Thickness(8, 2, 8, 2), FontSize = 11, Tag = (int)entry.Pid, FontWeight = (_focusedPid == (int)entry.Pid) ? System.Windows.FontWeights.Bold : System.Windows.FontWeights.Normal }; var capturedPid = (int)entry.Pid; btn.Click += (_, __) => SetFocusedPid(capturedPid); ProcessSelectorBarPanel.Children.Add(btn); } ProcessSelectorBarBorder.Visibility = MonitoredProcesses.Count > 1 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; } private void TryRegisterMonitoredProcess(uint pid) { if (pid == 0 || _monitoredProcessSet.Contains(pid)) return; _monitoredProcessSet.Add(pid); string imageName = string.Empty; try { imageName = ProcessIdentityResolver.Resolve(pid) ?? string.Empty; } catch { } MonitoredProcesses.Add( new MonitoredProcessEntry { Pid = pid, ImageName = imageName, FirstSeenUtc = DateTime.UtcNow }); RefreshProcessSelectorBar(); } private void RefreshSubsystemSegmentationDiagnostics() { bool kernelHooksEnabled = _currentSession?.KernelHooksEnabled ?? _kernelHooksArmed; bool usermodeHooksEnabled = _currentSession?.UseUsermodeHooks ?? false; bool signatureIntelEnabled = _currentSession?.SignatureIntelEnabled ?? _signatureIntelEnabled; bool memoryScanEnabled = _currentSession?.SignatureIntelMemoryScanEnabled ?? _signatureIntelMemoryScanEnabled; bool pageScanEnabled = _currentSession?.SignatureIntelPageScanEnabled ?? _signatureIntelPageScanEnabled; if (!kernelHooksEnabled) { DiagnosticsState.SetValue("Kernel Hooks", "Disabled by operator"); } else { string? kernelHooks = DiagnosticsState.GetValue("Kernel Hooks"); if (string.IsNullOrWhiteSpace(kernelHooks) || kernelHooks.Contains("Disabled", StringComparison.OrdinalIgnoreCase) || kernelHooks.Contains("Inactive", StringComparison.OrdinalIgnoreCase) || kernelHooks.Contains("Awaiting", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("Kernel Hooks", "Awaiting telemetry"); } } if (!usermodeHooksEnabled) { DiagnosticsState.SetValue("Usermode Hooks", "Disabled by operator"); DiagnosticsState.SetValue("Hook Integrity", "Disabled (no usermode hooks)"); DiagnosticsState.SetValue("AMSI Integrity", "Disabled (no usermode hooks)"); } else { string? usermode = DiagnosticsState.GetValue("Usermode Hooks"); if (string.IsNullOrWhiteSpace(usermode) || usermode.Contains("Inactive", StringComparison.OrdinalIgnoreCase) || usermode.Contains("Disabled", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("Usermode Hooks", "Awaiting hook-ready"); } if (string.Equals(DiagnosticsState.GetValue("Hook Integrity"), "Disabled (no usermode hooks)", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("Hook Integrity", "Unknown"); } if (string.Equals(DiagnosticsState.GetValue("AMSI Integrity"), "Disabled (no usermode hooks)", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("AMSI Integrity", "Unknown"); } } DiagnosticsState.SetValue( "Signature Intel", signatureIntelEnabled ? $"Enabled memory={(memoryScanEnabled ? "on" : "off")} page={(pageScanEnabled ? "on" : "off")} hash=on" : "Disabled by operator"); } private void InitializeBackendUi() { EtwPaneHost.ClearAll(); HeuristicsPaneHost.ClearAll(); FilesystemPaneHost.ClearAll(); RegistryPaneHost.ClearAll(); ProcessRelationsPaneHost.ClearAll(); _apiGraphRowsByKey.Clear(); _captureProjection = null; _extendedRowsByKey.Clear(); _apiGraphReasonByKey.Clear(); _apiGraphActionByKey.Clear(); _apiGraphDecodedByKey.Clear(); _apiGraphFramesByKey.Clear(); _apiGraphSensorByKey.Clear(); _apiGraphTimelineLastEmitByKey.Clear(); _observedHookStackLastPersistByThread.Clear(); _threadStackFallbackLastCaptureByThread.Clear(); _pendingThreadStackFallbackCaptures.Clear(); _apiMemorySignalsByPage.Clear(); _crossProcWriteCountByPair.Clear(); _crossProcRwxAllocCountByPair.Clear(); _antiAnalysisCountByEvidence.Clear(); ResetImageMapCorrelationCaches(); _observedProcessStartUtcByPid.Clear(); _targetExitReasonByPid.Clear(); _extendedViewRows.Clear(); _extendedComRows.Clear(); _extendedEtwRows.Clear(); _extendedJobRows.Clear(); _extendedYaraRows.Clear(); _extendedViewSnapshotDirty = false; _extendedViewRefreshTimer.Stop(); if (ExtendedViewSummaryBlock != null) { ExtendedViewSummaryBlock.Text = "No extended activity yet"; } _observedProcessStartKeyByPid.Clear(); _observedInitialThreadIdByPid.Clear(); _regionLifecycleByIdentity.Clear(); _functionTableBaseByPointer.Clear(); PublishApiGraphSnapshot(); ResetFilesystemTimelineCluster(); ProcessRelationsPaneHost.SetRootPid(0); RefreshExplorerDataBadges(); DiagnosticsState.SetValue("UI", "Initialized"); DiagnosticsState.SetValue("Operator Connection Established", "Disabled in analyst interface"); DiagnosticsState.SetValue("Hook Integrity", "Unknown"); DiagnosticsState.SetValue("AMSI Integrity", "Unknown"); DiagnosticsState.SetValue("ETW Integrity", "Unknown"); DiagnosticsState.SetValue("ETW Status", "Ready"); DiagnosticsState.SetValue("API Graph", "patterns=0/0 visible=False"); RefreshSubsystemSegmentationDiagnostics(); } private void StartBackendForPid(int pid, bool useUsermodeHooks, bool stopExistingSession = true, bool fastStopExistingSession = false) { BlackbirdBackendSession? preparedSession = null; if (stopExistingSession) { StopBackendSession(fastTeardown: fastStopExistingSession); OutputCapture.Clear(); } else { ClearPendingBackendUiQueues(); } if (pid <= 0) { DisposePreparedLaunchBackendSession(); return; } if (_preparedLaunchBackendSession != null) { if (_preparedLaunchBackendPid == pid && useUsermodeHooks) { preparedSession = _preparedLaunchBackendSession; _preparedLaunchBackendSession = null; _preparedLaunchBackendPid = 0; } else { DisposePreparedLaunchBackendSession(); } } _lastEtwTimelineSignature = null; _lastEtwTimelineTimestampUtc = DateTime.MinValue; ResetFilesystemTimelineCluster(); ResetImageMapCorrelationCaches(); int generation = ++_backendGeneration; try { _filterRootPid = pid; _captureProjection = new CaptureProjectionEngine(pid, $"ui-{pid}", "BlackbirdInterface"); _filterTrackedPids.Clear(); _filterTrackedPids.TryAdd((uint)pid, 0); _targetExitReasonByPid.Remove((uint)pid); _observedProcessStartUtcByPid[(uint)pid] = DateTime.UtcNow; MonitoredProcesses.Clear(); _monitoredProcessSet.Clear(); _focusedPid = 0; BroadcastFocusedPid(0); TryRegisterMonitoredProcess((uint)pid); if (_currentSession != null) { _currentSession.UseUsermodeHooks = useUsermodeHooks; _currentSession.KernelHooksEnabled = _kernelHooksArmed; _currentSession.SignatureIntelEnabled = _signatureIntelEnabled; _currentSession.SignatureIntelMemoryScanEnabled = _signatureIntelMemoryScanEnabled; _currentSession.SignatureIntelPageScanEnabled = _signatureIntelPageScanEnabled; } RefreshSubsystemSegmentationDiagnostics(); QueueSignatureIntelForRootPid(pid); var session = preparedSession ?? BlackbirdBackendSession.Start(pid, BlackbirdNative.StreamAll, useUsermodeHooks); _backendSession = session; _sessionIoctlHandler = record => { if (generation != _backendGeneration) return; if (!ShouldTrackRawIoctlRecord(record)) { return; } long pending = Interlocked.Read(ref _pendingIoctlCount); if (!ShouldAdmitIoctlRecord(record, pending)) { Interlocked.Increment(ref _droppedIoctlForPressure); return; } _pendingIoctlEvents.Enqueue(record); Interlocked.Increment(ref _pendingIoctlCount); _backendTransformSignal.Set(); }; _sessionEtwHandler = etw => { if (generation != _backendGeneration) return; if (!ShouldTrackRawEtwEvent(etw)) { return; } long pending = Interlocked.Read(ref _pendingEtwCount); if (!ShouldAdmitEtwEvent(etw, pending)) { Interlocked.Increment(ref _droppedEtwForPressure); return; } _pendingEtwEvents.Enqueue(etw); Interlocked.Increment(ref _pendingEtwCount); _backendTransformSignal.Set(); }; _sessionStatsHandler = stats => { if (Dispatcher.HasShutdownStarted) return; Dispatcher.BeginInvoke(new Action( () => { if (generation != _backendGeneration) return; DiagnosticsState.SetValue( "Session Stats", $"subs={stats.SubscriptionCount} depth={stats.QueueDepth} dropped={stats.DroppedEvents}"); })); }; _sessionStatusHandler = text => { if (generation != _backendGeneration) return; _captureProjection?.ObserveStatus(text); _pendingStatusLines.Enqueue($"[session pid={pid}] {text}"); Interlocked.Increment(ref _pendingStatusCount); _backendTransformSignal.Set(); }; session.IoctlEvent += _sessionIoctlHandler; session.EtwEvent += _sessionEtwHandler; session.Stats += _sessionStatsHandler; session.Status += _sessionStatusHandler; StartBackendTransformLoop(generation); StatusBlock.Text = $"Status: Session active for PID {pid}"; OutputCapture.AppendLine($"Session started for PID {pid}"); DiagnosticsState.SetValue("Session", $"Active PID {pid}"); ProcessRelationsPaneHost.SetRootPid(pid); } catch (Exception ex) { if (preparedSession != null) { try { preparedSession.Dispose(); } catch { } } string displayError = FormatSessionStartError(ex); _backendSession = null; StatusBlock.Text = $"Status: Session start failed ({displayError})"; OutputCapture.AppendLine($"Session start failed for PID {pid}: {displayError}"); DiagnosticsState.SetValue("Session", $"Start failed PID {pid}"); DiagnosticsState.SetValue("Connectivity", $"Session start failed: {displayError}"); SetBackendConnectivity(false); } } private static string FormatSessionStartError(Exception ex) { if (ex is EntryPointNotFoundException) { return "IPC runtime DLL is outdated (missing export). Rebuild/deploy J58.dll and controller."; } if (ex is System.ComponentModel.Win32Exception wx && (wx.NativeErrorCode == 127 || wx.NativeErrorCode == BlackbirdNative.ErrorInvalidFunction)) { return "IPC runtime DLL/controller mismatch (missing function). Rebuild/deploy latest J58.dll and controller."; } if (ex is System.ComponentModel.Win32Exception brokerWx && (brokerWx.NativeErrorCode == 109 || brokerWx.NativeErrorCode == 233)) { return "Broker handshake failed because the loaded J58.dll does not match the running BlackbirdController build. Rebuild/redeploy both together."; } return ex.Message; } private void DetachSessionHandlers(BlackbirdBackendSession session) { if (_sessionIoctlHandler != null) { session.IoctlEvent -= _sessionIoctlHandler; _sessionIoctlHandler = null; } if (_sessionEtwHandler != null) { session.EtwEvent -= _sessionEtwHandler; _sessionEtwHandler = null; } if (_sessionStatsHandler != null) { session.Stats -= _sessionStatsHandler; _sessionStatsHandler = null; } if (_sessionStatusHandler != null) { session.Status -= _sessionStatusHandler; _sessionStatusHandler = null; } } private void StopBackendSession(bool preserveApiGraphSnapshot = false, bool fastTeardown = false) { _liveAnalysisSession = null; if (_backendSession == null) { StopBackendTransformLoop(); DisposeLiveCaptureStore(); ClearPendingBackendUiQueues(); return; } try { DetachSessionHandlers(_backendSession); _backendSession.Dispose(); } catch { } _backendSession = null; _captureProjection?.WaitForPendingStackCaptures( fastTeardown ? TimeSpan.FromMilliseconds(50) : TimeSpan.FromSeconds(1)); _captureProjection = null; _filterRootPid = 0; _filterTrackedPids.Clear(); _pendingMonitoredProcessRegistrations.Clear(); StopBackendTransformLoop(); DisposeLiveCaptureStore(); ClearPendingBackendUiQueues(); _lastEtwTimelineSignature = null; _lastEtwTimelineTimestampUtc = DateTime.MinValue; Interlocked.Exchange(ref _droppedIoctlForPressure, 0); Interlocked.Exchange(ref _droppedEtwForPressure, 0); Interlocked.Exchange(ref _droppedUiWorkForPressure, 0); ResetFilesystemTimelineCluster(); ResetImageMapCorrelationCaches(); if (!preserveApiGraphSnapshot) { _apiGraphRowsByKey.Clear(); _extendedRowsByKey.Clear(); _extendedViewRows.Clear(); _extendedComRows.Clear(); _extendedEtwRows.Clear(); _extendedJobRows.Clear(); _extendedYaraRows.Clear(); _apiGraphReasonByKey.Clear(); _apiGraphActionByKey.Clear(); _apiGraphDecodedByKey.Clear(); _apiGraphFramesByKey.Clear(); _apiGraphSensorByKey.Clear(); _apiGraphTimelineLastEmitByKey.Clear(); _observedHookStackLastPersistByThread.Clear(); _threadStackFallbackLastCaptureByThread.Clear(); _pendingThreadStackFallbackCaptures.Clear(); _apiMemorySignalsByPage.Clear(); _crossProcWriteCountByPair.Clear(); _crossProcRwxAllocCountByPair.Clear(); _antiAnalysisCountByEvidence.Clear(); ResetImageMapCorrelationCaches(); _observedProcessStartUtcByPid.Clear(); _targetExitReasonByPid.Clear(); _extendedViewSnapshotDirty = false; _extendedViewRefreshTimer.Stop(); if (ExtendedViewSummaryBlock != null) { ExtendedViewSummaryBlock.Text = "No extended activity yet"; } _observedProcessStartKeyByPid.Clear(); _observedInitialThreadIdByPid.Clear(); _regionLifecycleByIdentity.Clear(); _functionTableBaseByPointer.Clear(); PublishApiGraphSnapshot(); } DiagnosticsState.SetValue("Session", "Stopped"); DiagnosticsState.SetValue("Kernel Hooks", "Inactive"); DiagnosticsState.SetValue("Usermode Hooks", "Inactive"); DiagnosticsState.SetValue("Operator Connection Established", "Disabled in analyst interface"); DiagnosticsState.SetValue("Hook Integrity", "Unknown"); DiagnosticsState.SetValue("AMSI Integrity", "Unknown"); DiagnosticsState.SetValue("ETW Integrity", "Unknown"); DiagnosticsState.SetValue("Signature Intel", "Inactive"); } private void ScheduleApiGraphSnapshot() { _apiGraphSnapshotDirty = true; } private void ApiGraphRefreshTimer_Tick(object? sender, EventArgs e) { if (!_apiGraphSnapshotDirty) { return; } _apiGraphSnapshotDirty = false; PublishApiGraphSnapshot(); } private void StartBackendTransformLoop(int generation) { StopBackendTransformLoop(); var cts = new CancellationTokenSource(); _backendTransformCts = cts; _backendTransformTask = Task.Run(() => BackendTransformLoop(generation, cts.Token)); } private void StopBackendTransformLoop() { CancellationTokenSource? cts = _backendTransformCts; Task? task = _backendTransformTask; _backendTransformCts = null; _backendTransformTask = null; if (cts == null) { return; } try { cts.Cancel(); _backendTransformSignal.Set(); task?.Wait(300); } catch { } finally { cts.Dispose(); } } private void BackendTransformLoop(int generation, CancellationToken ct) { while (!ct.IsCancellationRequested && generation == _backendGeneration) { bool producedUiWork = false; int transformed = 0; int etwBudget = Math.Min(MaxBackendTransformItemsPerBatch / 2, Math.Max(128, MaxBackendTransformItemsPerBatch)); while (transformed < etwBudget && _pendingEtwEvents.TryDequeue(out var etw)) { Interlocked.Decrement(ref _pendingEtwCount); BrokerEtwEventView view = BrokerEtwEventMapper.FromNative(etw); _captureProjection?.ObserveEtw(view); ProcessIdentityResolver.Prime(view.ActorPid); ProcessIdentityResolver.Prime(view.TargetPid); if (view.Family == BlackbirdNative.IpcEtwFamilyProcess && (view.Flags & BlackbirdNative.IpcEtwFlagProcessIsCreate) != 0) { uint etwCreator = view.CreatorPid != 0 ? view.CreatorPid : view.ActorPid; uint etwChild = view.ProcessPid != 0 ? view.ProcessPid : view.TargetPid; if (etwCreator != 0 && etwChild != 0 && etwCreator != etwChild && _filterTrackedPids.ContainsKey(etwCreator)) { _filterTrackedPids.TryAdd(etwChild, 0); QueueMonitoredProcessRegistration(generation, etwChild); } } if (view.ActorPid != 0 && _filterTrackedPids.ContainsKey(view.ActorPid)) { QueueMonitoredProcessRegistration(generation, view.ActorPid); } producedUiWork |= TryEnqueueUiWork(BackendUiWorkItem.FromEtw(view)); transformed += 1; } while (transformed < MaxBackendTransformItemsPerBatch && _pendingIoctlEvents.TryDequeue(out var ioctl)) { Interlocked.Decrement(ref _pendingIoctlCount); DateTime nowUtc = DateTime.UtcNow; _captureProjection?.ObserveIoctl(ioctl, nowUtc); bool acceptFilesystem = ShouldAcceptFilesystemRecord(ioctl); if (acceptFilesystem) { RememberRecentImageFileAccess(ioctl, nowUtc); } IReadOnlyList filesystemClusterEvents = acceptFilesystem ? AccumulateFilesystemTimelineCluster(ioctl, nowUtc) : Array.Empty(); if (filesystemClusterEvents.Count > 0) { for (int i = 0; i < filesystemClusterEvents.Count; i += 1) { if (TryEnqueueUiWork( BackendUiWorkItem.FromIoctl(filesystemClusterEvents[i], null, null, null, null))) { producedUiWork = true; } else { break; } } } if (ioctl.Type == BlackbirdNative.EventTypeThread && ioctl.ProcessPid != 0 && ioctl.CreatorPid != 0 && ioctl.ProcessPid != ioctl.CreatorPid && _filterTrackedPids.ContainsKey(ioctl.CreatorPid)) { _filterTrackedPids.TryAdd(ioctl.ProcessPid, 0); } TelemetryEvent? telemetry = MapIoctlRecord(ioctl); ProcessRelationView? relation = MapIoctlRelation(ioctl); HeuristicEventView? heuristic = MapIoctlHeuristic(ioctl); IReadOnlyList signatureIntelIoctlFindings = ShouldEvaluateSignatureIntelForIoctl(ioctl, acceptFilesystem) ? EvaluateSignatureIntelForIoctl(ioctl) : Array.Empty(); ThreadLifecycleEventSample? threadLifecycle = MapIoctlThreadLifecycle(ioctl); IoctlParsedEvent? filesystem = acceptFilesystem ? MapIoctlFilesystem(ioctl) : null; IoctlParsedEvent? registry = MapIoctlRegistry(ioctl); if (relation != null) { ProcessIdentityResolver.Prime(relation.SourcePid); ProcessIdentityResolver.Prime(relation.TargetPid); } if (heuristic != null) { ProcessIdentityResolver.Prime(heuristic.ActorPid); ProcessIdentityResolver.Prime(heuristic.TargetPid); } if (signatureIntelIoctlFindings.Count > 0) { for (int i = 0; i < signatureIntelIoctlFindings.Count; i += 1) { ProcessIdentityResolver.Prime(signatureIntelIoctlFindings[i].ActorPid); ProcessIdentityResolver.Prime(signatureIntelIoctlFindings[i].TargetPid); } } if (filesystem != null) { ProcessIdentityResolver.Prime(filesystem.FileProcessPid); } if (registry != null) { ProcessIdentityResolver.Prime(registry.RegistryProcessPid); } if (ioctl.Type == BlackbirdNative.EventTypeEnterprise) { ProcessIdentityResolver.Prime(ioctl.EnterpriseProcessPid); ProcessIdentityResolver.Prime(ioctl.EnterpriseTargetProcessPid); } if (acceptFilesystem && (ShouldPersistIoctlRecord(ioctl, telemetry, relation, heuristic, filesystem) || signatureIntelIoctlFindings.Count > 0)) { AppendIoctlToCaptureStore(nowUtc, ioctl); } if (telemetry != null || relation != null || heuristic != null || threadLifecycle != null || filesystem != null || registry != null) { producedUiWork |= TryEnqueueUiWork(BackendUiWorkItem.FromIoctl( telemetry, relation, heuristic, threadLifecycle, filesystem, registry)); } if (signatureIntelIoctlFindings.Count > 0) { for (int i = 0; i < signatureIntelIoctlFindings.Count; i += 1) { producedUiWork |= TryEnqueueUiWork( BackendUiWorkItem.FromIoctl(null, null, signatureIntelIoctlFindings[i], null, null)); } } transformed += 1; } int statusLines = 0; while (statusLines < MaxBackendStatusLinesPerTransformBatch && _pendingStatusLines.TryDequeue(out var statusLine)) { Interlocked.Decrement(ref _pendingStatusCount); producedUiWork |= TryEnqueueUiWork(BackendUiWorkItem.FromStatus(statusLine)); statusLines += 1; } IReadOnlyList idleFilesystemClusters = FlushFilesystemTimelineClusterIfNeeded(DateTime.UtcNow, force: false); if (idleFilesystemClusters.Count > 0) { for (int i = 0; i < idleFilesystemClusters.Count; i += 1) { if (TryEnqueueUiWork( BackendUiWorkItem.FromIoctl(idleFilesystemClusters[i], null, null, null, null))) { producedUiWork = true; } else { break; } } } if (producedUiWork) { ScheduleBackendUiFlush(generation); continue; } _backendTransformSignal.WaitOne(120); } } private void ScheduleBackendUiFlush(int generation) { if (Interlocked.Exchange(ref _backendUiFlushScheduled, 1) != 0) { return; } Dispatcher.BeginInvoke(new Action(() => FlushBackendUi(generation)), DispatcherPriority.Background); } private void FlushBackendUi(int generation) { Interlocked.Exchange(ref _backendUiFlushScheduled, 0); if (generation != _backendGeneration) { ClearPendingBackendUiQueues(); return; } var stopwatch = Stopwatch.StartNew(); int processed = 0; bool underPressure = Interlocked.Read(ref _pendingUiWorkCount) > (MaxPendingUiWorkItems / 2) || Interlocked.Read(ref _pendingIoctlCount) > (MaxPendingIoctlEvents / 2) || Interlocked.Read(ref _pendingEtwCount) > (MaxPendingEtwEvents / 2); int maxItemsThisFlush = underPressure ? MaxBackendUiItemsPerFlushUnderPressure : MaxBackendUiItemsPerFlush; TimeSpan budget = underPressure ? BackendUiFlushBudgetUnderPressure : BackendUiFlushBudget; var telemetryBatch = new List(96); var relationBatch = new List(32); var heuristicBatch = new List(32); var threadLifecycleBatch = new List(32); var filesystemBatch = new List(32); var registryBatch = new List(32); var etwBatch = new List(96); var statusBatch = new List(32); while (processed < maxItemsThisFlush && stopwatch.Elapsed < budget && _pendingUiWork.TryDequeue(out var uiWork)) { Interlocked.Decrement(ref _pendingUiWorkCount); if (uiWork.Kind == BackendUiWorkKind.Status) { if (statusBatch.Count < MaxBackendStatusLinesPerUiFlush && !string.IsNullOrWhiteSpace(uiWork.StatusLine)) { statusBatch.Add(uiWork.StatusLine); } } else if (uiWork.Kind == BackendUiWorkKind.Ioctl) { if (uiWork.Telemetry != null) { telemetryBatch.Add(uiWork.Telemetry); } if (uiWork.Relation != null) { relationBatch.Add(uiWork.Relation); } if (uiWork.Heuristic != null) { heuristicBatch.Add(uiWork.Heuristic); } if (uiWork.ThreadLifecycle != null) { threadLifecycleBatch.Add(uiWork.ThreadLifecycle); } if (uiWork.Filesystem != null) { filesystemBatch.Add(uiWork.Filesystem); } if (uiWork.Registry != null) { registryBatch.Add(uiWork.Registry); } } else if (uiWork.Kind == BackendUiWorkKind.Etw && uiWork.EtwView != null) { etwBatch.Add(uiWork.EtwView); } processed += 1; } for (int i = 0; i < statusBatch.Count; i += 1) { OutputCapture.AppendLine(statusBatch[i]); } if (telemetryBatch.Count > 0) { AppendEvents(telemetryBatch); } if (relationBatch.Count > 0) { ProcessRelationsPaneHost.PushRelations(relationBatch); _explorer.FirstOrDefault(x => x.Name == "Process Relations") ?.PushPreviewValue(ProcessRelationsPaneHost.TotalRawCount); SetExplorerHasData("Process Relations", ProcessRelationsPaneHost.ItemCount > 0); } if (heuristicBatch.Count > 0) { HeuristicsPaneHost.PushHeuristics(heuristicBatch); bool extendedChanged = false; for (int i = 0; i < heuristicBatch.Count; i += 1) { extendedChanged |= ObserveSignatureIntelActivity(heuristicBatch[i]); } if (extendedChanged) { ScheduleExtendedActivitySnapshot(); } _explorer.FirstOrDefault(x => x.Name == "Heuristics") ?.PushPreviewValue(HeuristicsPaneHost.TotalRawCount); SetExplorerHasData("Heuristics", HeuristicsPaneHost.ItemCount > 0); DiagnosticsState.Increment("Heuristics", heuristicBatch.Count); } if (threadLifecycleBatch.Count > 0) { PerformancePaneHost.PushThreadLifecycles(threadLifecycleBatch); if (_currentSession != null) { for (int i = 0; i < threadLifecycleBatch.Count; i += 1) { _currentSession.ThreadLifecycleHistory.Add(CloneThreadLifecycleEvent(threadLifecycleBatch[i])); } if (_currentSession.ThreadLifecycleHistory.Count > 12_000) { _currentSession.ThreadLifecycleHistory.RemoveRange( 0, _currentSession.ThreadLifecycleHistory.Count - 12_000); } } } if (filesystemBatch.Count > 0) { FilesystemPaneHost.PushFileEvents(filesystemBatch); _explorer.FirstOrDefault(x => x.Name == "Filesystem") ?.PushPreviewValue(FilesystemPaneHost.TotalRawCount); SetExplorerHasData("Filesystem", FilesystemPaneHost.ItemCount > 0); } if (registryBatch.Count > 0) { RegistryPaneHost.PushRegistryEvents(registryBatch); _explorer.FirstOrDefault(x => x.Name == "Registry")?.PushPreviewValue(RegistryPaneHost.TotalRawCount); SetExplorerHasData("Registry", RegistryPaneHost.ItemCount > 0); } SpillCurrentSessionWorkingSetIfNeeded(); if (etwBatch.Count > 0) { HandleBrokerEtwViews(etwBatch); } DiagnosticsState.SetValue( "UI Flush", $"items={processed} ms={stopwatch.Elapsed.TotalMilliseconds:0.0} pending={Interlocked.Read(ref _pendingUiWorkCount)}"); if (HasPendingBackendUiData()) { ScheduleBackendUiFlush(generation); } } private bool HasPendingBackendUiData() { return Interlocked.Read(ref _pendingIoctlCount) > 0 || Interlocked.Read(ref _pendingEtwCount) > 0 || Interlocked.Read(ref _pendingStatusCount) > 0 || Interlocked.Read(ref _pendingUiWorkCount) > 0; } private void ClearPendingBackendUiQueues() { while (_pendingIoctlEvents.TryDequeue(out _)) { } while (_pendingEtwEvents.TryDequeue(out _)) { } while (_pendingStatusLines.TryDequeue(out _)) { } while (_pendingUiWork.TryDequeue(out _)) { } Interlocked.Exchange(ref _pendingIoctlCount, 0); Interlocked.Exchange(ref _pendingEtwCount, 0); Interlocked.Exchange(ref _pendingStatusCount, 0); Interlocked.Exchange(ref _pendingUiWorkCount, 0); Interlocked.Exchange(ref _backendUiFlushScheduled, 0); ResetFilesystemTimelineCluster(); } private bool TryEnqueueUiWork(BackendUiWorkItem item) { if (Interlocked.Read(ref _pendingUiWorkCount) >= MaxPendingUiWorkItems) { Interlocked.Increment(ref _droppedUiWorkForPressure); return false; } _pendingUiWork.Enqueue(item); Interlocked.Increment(ref _pendingUiWorkCount); return true; } private void QueueMonitoredProcessRegistration(int generation, uint pid) { if (pid == 0 || !_pendingMonitoredProcessRegistrations.TryAdd(pid, 0) || Dispatcher.HasShutdownStarted) { return; } Dispatcher.BeginInvoke(new Action(() => { _pendingMonitoredProcessRegistrations.TryRemove(pid, out _); if (generation == _backendGeneration) { TryRegisterMonitoredProcess(pid); } }), DispatcherPriority.Background); } private void AppendIoctlToCaptureStore(DateTime timestampUtc, IoctlParsedEvent record) { BlackbirdCaptureLiveStore? store = _liveCaptureStore; if (store == null) { return; } try { store.AppendIoctl(timestampUtc, record); } catch (Exception ex) { DiagnosticsState.SetValue("Capture Store", $"IOCTL append failed: {ex.Message}"); } } private void AppendEtwToCaptureStore(BrokerEtwEventView view) { BlackbirdCaptureLiveStore? store = _liveCaptureStore; if (store == null) { return; } try { store.AppendEtw(view); } catch (Exception ex) { DiagnosticsState.SetValue("Capture Store", $"ETW append failed: {ex.Message}"); } } private static ulong BuildRelationKey(uint actorPid, uint targetPid) => ((ulong)actorPid << 32) | targetPid; private void RememberHandleEvidence(IoctlParsedEvent record) { if (record.Type != BlackbirdNative.EventTypeHandle || record.CallerPid == 0 || record.TargetPid == 0) { return; } uint highSignalMask = 0x00000800 | 0x00002000; bool highRiskAccess = (record.DesiredAccess & (0x0002u | 0x0008u | 0x0010u | 0x0020u | 0x0800u)) != 0; if (!highRiskAccess) { bool processAll = (record.DesiredAccess & 0x001F0FFFu) == 0x001F0FFFu; bool threadAll = (record.DesiredAccess & 0x001F03FFu) == 0x001F03FFu; highRiskAccess = processAll || threadAll; } bool suspicious = record.HandleClass == 2 || ((record.HandleFlags & highSignalMask) != 0); if (!highRiskAccess && !suspicious) { return; } _recentHandleEvidenceByPair[BuildRelationKey(record.CallerPid, record.TargetPid)] = record; _recentHandleEvidenceByPair[BuildRelationKey(record.TargetPid, record.CallerPid)] = record; DateTime now = DateTime.UtcNow; if ((now - _lastHandleEvidencePruneUtc).TotalSeconds < 20) { return; } _lastHandleEvidencePruneUtc = now; if (_recentHandleEvidenceByPair.Count > 4096) { _recentHandleEvidenceByPair.Clear(); } } private bool TryGetHandleEvidence(uint actorPid, uint targetPid, out IoctlParsedEvent evidence) { if (_recentHandleEvidenceByPair.TryGetValue(BuildRelationKey(actorPid, targetPid), out IoctlParsedEvent? found)) { evidence = found; return true; } evidence = new IoctlParsedEvent(); return false; } private static bool IsBlackbirdOwnEvent(BrokerEtwEventView view) => (view.DetectionTraits & BlackbirdNative.IpcEtwTraitBlackbirdOwn) != 0 || view.DetectionName.Equals("BK_INSTRUMENTATION", StringComparison.OrdinalIgnoreCase); private static bool ShouldKeepEtwEvent(BrokerEtwEventView view) { /* Always surface BK-instrumentation events — but they are annotated and displayed distinctly so analysts aren't confused. */ if (IsBlackbirdOwnEvent(view)) return true; if (view.Family == BlackbirdNative.IpcEtwFamilyUserHook) { return true; } if (view.Family == BlackbirdNative.IpcEtwFamilySocket || EventDetailFormatting.IsKernelNetworkEtwSource(view)) { return true; } if (IsDirectSyscallDetection(view)) { return true; } if (!string.IsNullOrWhiteSpace(view.DetectionName)) { return true; } if (view.Severity >= 4) { return true; } if (EventDetailFormatting.IsThreatIntelEtwSource(view)) { return view.Task == 1 || view.Task == 2 || view.Task == 7; } if (view.EventName.Equals("ThreadTelemetry", StringComparison.OrdinalIgnoreCase) || view.EventName.Equals("ApcTelemetry", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } private static bool ShouldPromoteHeuristic(BrokerEtwEventView view) { if (IsDirectSyscallDetection(view)) { return view.Severity >= 2 || IsDirectSyscallHandleTelemetry(view); } if (string.IsNullOrWhiteSpace(view.DetectionName)) { return false; } string det = view.DetectionName; if (IsHookTamperDetection(view)) { return true; } if (det.Contains("AMSI_PATCH_TAMPERED", StringComparison.OrdinalIgnoreCase) || det.Contains("ETW_PATCH_TAMPERED", StringComparison.OrdinalIgnoreCase)) { return true; } if (det.Equals("USERMODE_COM_INIT", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_COM_SECURITY_INIT", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_COM_INSTANCE_CREATE", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_WMI_ACTIVITY", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_ETW_SESSION_CONTROL", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_ETW_SUBSCRIPTION", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_ETW_PROVIDER_REGISTER", StringComparison.OrdinalIgnoreCase) || det.Equals("USERMODE_ETW_PROVIDER_UNREGISTER", StringComparison.OrdinalIgnoreCase)) { return true; } if (det.Contains("ANTI_DEBUG", StringComparison.OrdinalIgnoreCase) || det.Contains("ANTI_VM", StringComparison.OrdinalIgnoreCase) || det.Contains("ANTI_VIRTUAL", StringComparison.OrdinalIgnoreCase)) { return view.Severity >= 4; } if (det.Contains("HOLLOW", StringComparison.OrdinalIgnoreCase) || det.Contains("INJECTION", StringComparison.OrdinalIgnoreCase) || det.Contains("THREAD_HIJACK", StringComparison.OrdinalIgnoreCase) || det.Contains("REMOTE_APC", StringComparison.OrdinalIgnoreCase)) { return view.Severity >= 5; } if (det.Contains("REMOTE_THREAD_WITH_RECENT_HANDLE_INTENT", StringComparison.OrdinalIgnoreCase) || det.Contains("THREAD_ACTIVITY_WITH_THREAD_CONTEXT_INTENT", StringComparison.OrdinalIgnoreCase) || det.Contains("REMOTE_THREAD_OUTSIDE_MAIN_IMAGE", StringComparison.OrdinalIgnoreCase)) { return false; } return view.Severity >= 6; } private HeuristicEventView? EvaluateCrossProcessMemoryHeuristic(BrokerEtwEventView view) { string apiName = !string.IsNullOrWhiteSpace(view.EventName) ? view.EventName : (!string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : string.Empty); if (string.IsNullOrEmpty(apiName)) { return null; } uint actor = view.ActorPid != 0 ? view.ActorPid : view.ProcessPid; uint target = view.TargetPid != 0 ? view.TargetPid : actor; if (actor == 0 || actor == target) { return null; } string pairKey = $"{actor}|{target}"; if (apiName.Equals("NtWriteVirtualMemory", StringComparison.OrdinalIgnoreCase)) { _crossProcWriteCountByPair.TryGetValue(pairKey, out int count); count += 1; _crossProcWriteCountByPair[pairKey] = count; if (count != 5 && count != 25 && count != 100) { return null; } uint severity = count >= 100 ? 8u : count >= 25 ? 7u : 6u; return new HeuristicEventView { TimestampUtc = view.TimestampUtc, LastSeenUtc = view.TimestampUtc, Severity = severity, DetectionName = "CROSS_PROCESS_WRITE_PATTERN", ActorPid = actor, TargetPid = target, Source = "BK/MemoryPattern", EventName = "NtWriteVirtualMemory", Reason = $"reason=cross-process write accumulation; count={count}; actor={actor}; target={target}", Evidence = $"NtWriteVirtualMemory observed {count}x from pid {actor} into pid {target}", RepeatCount = 1 }; } if (apiName.Equals("NtAllocateVirtualMemory", StringComparison.OrdinalIgnoreCase)) { Dictionary fields = ParseReasonFields(view.Reason ?? string.Empty); uint protect = (uint)FirstU64(fields, "protect", "c3", "a5"); bool isRwx = protect != 0 && (protect & 0x40u) != 0; if (!isRwx) { return null; } _crossProcRwxAllocCountByPair.TryGetValue(pairKey, out int count); count += 1; _crossProcRwxAllocCountByPair[pairKey] = count; if (count != 3 && count != 12 && count != 48) { return null; } uint severity = count >= 48 ? 9u : count >= 12 ? 8u : 7u; return new HeuristicEventView { TimestampUtc = view.TimestampUtc, LastSeenUtc = view.TimestampUtc, Severity = severity, DetectionName = "CROSS_PROCESS_RWX_ALLOC_PATTERN", ActorPid = actor, TargetPid = target, Source = "BK/MemoryPattern", EventName = "NtAllocateVirtualMemory", Reason = $"reason=repeated cross-process RWX allocation; count={count}; actor={actor}; target={target}", Evidence = $"NtAllocateVirtualMemory(PAGE_EXECUTE_READWRITE) observed {count}x from pid {actor} into pid {target}", RepeatCount = 1 }; } return null; } private HeuristicEventView? EvaluateMemoryLifecycleHeuristic(MemoryRegionAttributionSample sample) { if (sample.BlackbirdOwned || sample.TargetPid == 0) { return null; } bool protectEvent = sample.ProtectFlipCount != 0 && sample.EventKind.Equals("ProtectChange", StringComparison.OrdinalIgnoreCase); bool guardNoAccessSignal = sample.GuardNoAccessFlipCount != 0; bool writableExecutableSignal = sample.WritableExecutableFlipCount != 0; bool firstHighSignalProtect = sample.ProtectFlipCount == 1 && (guardNoAccessSignal || writableExecutableSignal || sample.CrossProcess); bool repeatedProtect = protectEvent && (IsMemoryPatternMilestone(sample.ProtectFlipCount) || sample.RapidProtectFlipCount == 1 || IsMemoryPatternMilestone(sample.RapidProtectFlipCount)); if (protectEvent && (firstHighSignalProtect || repeatedProtect)) { uint severity = ComputeMemoryLifecycleSeverity(sample, protectSignal: true, entropySignal: false); string detectionName = guardNoAccessSignal ? "MEMORY_GUARD_NOACCESS_PROTECTION_FLIP" : writableExecutableSignal ? "MEMORY_WRITABLE_EXECUTABLE_PROTECTION_FLIP" : "MEMORY_PROTECTION_FLIP_PATTERN"; string transition = string.IsNullOrWhiteSpace(sample.ProtectionTransition) ? "unknown" : sample.ProtectionTransition; string entropyText = sample.EntropyBits >= 0 ? sample.EntropyBits.ToString("F2", CultureInfo.InvariantCulture) : "n/a"; string maxEntropyText = sample.MaxEntropyBits >= 0 ? sample.MaxEntropyBits.ToString("F2", CultureInfo.InvariantCulture) : "n/a"; return new HeuristicEventView { TimestampUtc = sample.TimestampUtc == default ? DateTime.UtcNow : sample.TimestampUtc, LastSeenUtc = sample.TimestampUtc == default ? DateTime.UtcNow : sample.TimestampUtc, Severity = severity, DetectionName = detectionName, ActorPid = sample.ActorPid != 0 ? sample.ActorPid : sample.TargetPid, TargetPid = sample.TargetPid, Source = "BK/MemoryLifecycle", EventName = string.IsNullOrWhiteSpace(sample.ApiName) ? sample.EventKind : sample.ApiName, Reason = $"reason=memory protection flip pattern; region=0x{sample.BaseAddress:X}; size=0x{sample.RegionSize:X}; " + $"transition={transition}; flips={sample.ProtectFlipCount}; rapid={sample.RapidProtectFlipCount}; " + $"execFlips={sample.ExecutableFlipCount}; guardNoAccessFlips={sample.GuardNoAccessFlipCount}; " + $"wxFlips={sample.WritableExecutableFlipCount}; writes={sample.WriteCount}; entropy={entropyText}; maxEntropy={maxEntropyText}", Evidence = $"Region 0x{sample.BaseAddress:X} changed protection {transition}; " + $"protection flips={sample.ProtectFlipCount}, rapid flips={sample.RapidProtectFlipCount}, " + $"exec/guard/WX flips={sample.ExecutableFlipCount}/{sample.GuardNoAccessFlipCount}/{sample.WritableExecutableFlipCount}, " + $"writes={sample.WriteCount}, entropy={entropyText} bits/byte" }; } bool entropyEvent = sample.EventKind.Equals("MemoryWrite", StringComparison.OrdinalIgnoreCase) && HasUsableEntropySample(sample); bool highEntropy = entropyEvent && sample.EntropyBits >= MemoryHighEntropyBits; bool repeatedEntropyShift = entropyEvent && (IsMemoryPatternMilestone(sample.EntropyFlipCount) || sample.RapidEntropyFlipCount == 1 || IsMemoryPatternMilestone(sample.RapidEntropyFlipCount)); bool highEntropyMilestone = highEntropy && IsHighEntropyWriteMilestone(sample.HighEntropyWriteCount); if (entropyEvent && (highEntropyMilestone || repeatedEntropyShift)) { uint severity = ComputeMemoryLifecycleSeverity(sample, protectSignal: false, entropySignal: true); string entropyText = sample.EntropyBits.ToString("F2", CultureInfo.InvariantCulture); string maxEntropyText = sample.MaxEntropyBits >= 0 ? sample.MaxEntropyBits.ToString("F2", CultureInfo.InvariantCulture) : entropyText; string detectionName = highEntropy ? "MEMORY_HIGH_ENTROPY_WRITE_PATTERN" : "MEMORY_ENTROPY_SHIFT_PATTERN"; return new HeuristicEventView { TimestampUtc = sample.TimestampUtc == default ? DateTime.UtcNow : sample.TimestampUtc, LastSeenUtc = sample.TimestampUtc == default ? DateTime.UtcNow : sample.TimestampUtc, Severity = severity, DetectionName = detectionName, ActorPid = sample.ActorPid != 0 ? sample.ActorPid : sample.TargetPid, TargetPid = sample.TargetPid, Source = "BK/MemoryLifecycle", EventName = string.IsNullOrWhiteSpace(sample.ApiName) ? sample.EventKind : sample.ApiName, Reason = $"reason=memory entropy pattern; region=0x{sample.BaseAddress:X}; size=0x{sample.RegionSize:X}; " + $"entropy={entropyText}; maxEntropy={maxEntropyText}; entropyFlips={sample.EntropyFlipCount}; " + $"rapidEntropyFlips={sample.RapidEntropyFlipCount}; highEntropyWrites={sample.HighEntropyWriteCount}; " + $"sampleBytes={sample.SampleBytes}; protectFlips={sample.ProtectFlipCount}", Evidence = $"Region 0x{sample.BaseAddress:X} write sample entropy={entropyText} bits/byte " + $"(max={maxEntropyText}, entropy flips={sample.EntropyFlipCount}, high entropy writes={sample.HighEntropyWriteCount}, " + $"sample bytes={sample.SampleBytes})" }; } return null; } private HeuristicEventView? EvaluateAntiAnalysisHeuristic(BrokerEtwEventView view) { if (IsBlackbirdOwnEvent(view)) { return null; } string apiName = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : (!string.IsNullOrWhiteSpace(view.EventName) ? view.EventName : string.Empty); if (string.IsNullOrWhiteSpace(apiName)) { return null; } Dictionary fields = BuildHookFieldMap(view); uint actor = view.ActorPid != 0 ? view.ActorPid : view.ProcessPid; uint target = view.TargetPid != 0 ? view.TargetPid : actor; if (actor == 0) { return null; } if (apiName.Equals("NtQueryInformationProcess", StringComparison.OrdinalIgnoreCase)) { uint infoClass = (uint)FirstU64(fields, "processInformationClass", "ProcessInformationClass", "infoClass", "class", "a1", "c1"); string className = infoClass switch { 7 => "ProcessDebugPort", 30 => "ProcessDebugObjectHandle", 31 => "ProcessDebugFlags", _ => string.Empty }; if (string.IsNullOrWhiteSpace(className)) { return null; } string evidence = $"{apiName} {className} actor={actor} target={target}"; return BuildAntiAnalysisFinding( view.TimestampUtc, actor, target, "ANTI_DEBUG_PROCESS_QUERY", 6, "UserHook/AntiAnalysis", apiName, $"queried anti-debug process information class {className} (0x{infoClass:X})", evidence); } if (apiName.Equals("NtQuerySystemInformation", StringComparison.OrdinalIgnoreCase) || apiName.Equals("NtQuerySystemInformationEx", StringComparison.OrdinalIgnoreCase)) { uint infoClass = (uint)FirstU64(fields, "systemInformationClass", "SystemInformationClass", "infoClass", "class", "a0", "c0"); if (infoClass == 35) { string evidence = $"{apiName} SystemKernelDebuggerInformation actor={actor}"; return BuildAntiAnalysisFinding( view.TimestampUtc, actor, target, "ANTI_DEBUG_KERNEL_DEBUGGER_QUERY", 6, "UserHook/AntiAnalysis", apiName, "queried kernel debugger state with SystemKernelDebuggerInformation", evidence); } if (infoClass == 76) { string evidence = $"{apiName} SystemFirmwareTableInformation actor={actor}"; return BuildAntiAnalysisFinding( view.TimestampUtc, actor, target, "ANTI_VM_FIRMWARE_TABLE_QUERY", 5, "UserHook/AntiAnalysis", apiName, "queried firmware tables commonly used for hypervisor/vendor checks", evidence); } } return null; } private HeuristicEventView? EvaluateImageSectionMapHeuristic(BrokerEtwEventView view) { if (IsBlackbirdOwnEvent(view)) { return null; } string apiName = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : (!string.IsNullOrWhiteSpace(view.EventName) ? view.EventName : string.Empty); if (!IsImageSectionMapApi(apiName)) { return null; } uint actor = view.ActorPid != 0 ? view.ActorPid : view.ProcessPid != 0 ? view.ProcessPid : view.EventProcessId; if (actor == 0) { return null; } uint tid = view.EventThreadId != 0 ? view.EventThreadId : view.ThreadId; DateTime observedUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; Dictionary fields = BuildHookFieldMap(view); string mappedPath = ExtractMappedImagePath(view, fields); RecentImageFileAccess ? linkedAccess; RecentImageMapState mapState; lock (_imageMapCorrelationLock) { PruneImageMapCorrelationCachesLocked(observedUtc); linkedAccess = FindRecentImageFileAccessLocked(actor, tid, mappedPath, observedUtc); if (string.IsNullOrWhiteSpace(mappedPath) && linkedAccess != null) { mappedPath = linkedAccess.Path; } mappedPath = NormalizeCorrelationPath(mappedPath); if (!IsImagePathForMapping(mappedPath)) { return null; } mapState = RememberImageMapLocked(actor, mappedPath, apiName, observedUtc, linkedAccess); } string fileName = ModuleNameFromPath(mappedPath); bool isNtdll = fileName.Equals("ntdll.dll", StringComparison.OrdinalIgnoreCase); bool hasFilesystemLink = linkedAccess != null; bool emitMilestone = mapState.Count == 2 || mapState.Count == 4 || mapState.Count == 8 || (mapState.Count > 0 && mapState.Count % 16 == 0); if (!emitMilestone) { return null; } if (!isNtdll && !hasFilesystemLink && mapState.Count < 4) { return null; } uint severity = isNtdll ? 5u : hasFilesystemLink ? 4u : 3u; string fileEvidence = hasFilesystemLink ? $"linkedFs={DescribeFileOperation(linkedAccess!.Operation)} pid={linkedAccess.Pid} tid={linkedAccess.Tid} path={linkedAccess.Path}" : "linkedFs="; string targetText = view.TargetPid == 0 || view.TargetPid == actor ? "self" : view.TargetPid.ToString(CultureInfo.InvariantCulture); string reason = isNtdll ? "repeated mapping of ntdll.dll in one process; normal loader startup maps it once, so later repeat maps are worth review" : "repeated image-backed section mapping correlated with recent filesystem image access"; return new HeuristicEventView { TimestampUtc = observedUtc, LastSeenUtc = observedUtc, Severity = severity, DetectionName = isNtdll ? "REPEATED_NTDLL_IMAGE_MAPPING" : "REPEATED_IMAGE_SECTION_MAPPING", ActorPid = actor, TargetPid = view.TargetPid == 0 ? actor : view.TargetPid, Source = "BK/ImageMapCorrelation", EventName = apiName, Reason = $"reason={reason}; count={mapState.Count}; target={targetText}", Evidence = $"api={apiName} path={mappedPath} firstSeen={mapState.FirstSeenUtc:O} lastSeen={mapState.LastSeenUtc:O} {fileEvidence}", RepeatCount = 1 }; } private void RememberRecentImageFileAccess(IoctlParsedEvent record, DateTime observedUtc) { if (record.Type != BlackbirdNative.EventTypeFileSystem || record.FileProcessPid == 0 || (record.FileOperation != BlackbirdNative.FileOperationCreate && record.FileOperation != BlackbirdNative.FileOperationRead)) { return; } string path = NormalizeCorrelationPath(record.FilePath); if (!IsImagePathForMapping(path)) { return; } var access = new RecentImageFileAccess { TimestampUtc = observedUtc, Pid = record.FileProcessPid, Tid = record.FileThreadId, Operation = record.FileOperation, Path = path, FileName = ModuleNameFromPath(path) }; lock (_imageMapCorrelationLock) { PruneImageMapCorrelationCachesLocked(observedUtc); _recentImageFileAccesses.Add(access); if (_recentImageFileAccesses.Count > MaxRecentImageFileAccesses) { _recentImageFileAccesses.RemoveRange(0, _recentImageFileAccesses.Count - MaxRecentImageFileAccesses); } } } private void ResetImageMapCorrelationCaches() { lock (_imageMapCorrelationLock) { _recentImageFileAccesses.Clear(); _recentImageMapByPidPath.Clear(); _lastImageMapCorrelationPruneUtc = DateTime.MinValue; } } private void PruneImageMapCorrelationCachesLocked(DateTime nowUtc) { if (_lastImageMapCorrelationPruneUtc != DateTime.MinValue && nowUtc < _lastImageMapCorrelationPruneUtc.AddSeconds(2)) { return; } DateTime fileCutoff = nowUtc - ImageMapFileCorrelationWindow - TimeSpan.FromSeconds(2); _recentImageFileAccesses.RemoveAll(x => x.TimestampUtc < fileCutoff); DateTime mapCutoff = nowUtc - ImageMapRepeatWindow; foreach (string key in _recentImageMapByPidPath.Where(x => x.Value.LastSeenUtc < mapCutoff) .Select(x => x.Key) .ToList()) { _recentImageMapByPidPath.Remove(key); } if (_recentImageMapByPidPath.Count > MaxRecentImageMapStates) { foreach (string key in _recentImageMapByPidPath.OrderBy(x => x.Value.LastSeenUtc) .Take(_recentImageMapByPidPath.Count - MaxRecentImageMapStates) .Select(x => x.Key) .ToList()) { _recentImageMapByPidPath.Remove(key); } } _lastImageMapCorrelationPruneUtc = nowUtc; } private RecentImageFileAccess? FindRecentImageFileAccessLocked(uint pid, uint tid, string mappedPath, DateTime observedUtc) { string normalizedPath = NormalizeCorrelationPath(mappedPath); string fileName = ModuleNameFromPath(normalizedPath); bool hasPath = !string.IsNullOrWhiteSpace(normalizedPath); bool hasFileName = !string.IsNullOrWhiteSpace(fileName) && !fileName.Equals("unknown", StringComparison.OrdinalIgnoreCase); return _recentImageFileAccesses .Where(x => x.Pid == pid && IsWithinCorrelationWindow(x.TimestampUtc, observedUtc, ImageMapFileCorrelationWindow) && (!hasPath || x.Path.Equals(normalizedPath, StringComparison.OrdinalIgnoreCase) || (hasFileName && x.FileName.Equals(fileName, StringComparison.OrdinalIgnoreCase)))) .OrderByDescending(x => x.Tid == tid && tid != 0) .ThenByDescending(x => x.Path.Equals(normalizedPath, StringComparison.OrdinalIgnoreCase)) .ThenBy(x => Math.Abs((x.TimestampUtc - observedUtc).TotalMilliseconds)) .FirstOrDefault(); } private RecentImageMapState RememberImageMapLocked(uint pid, string mappedPath, string apiName, DateTime observedUtc, RecentImageFileAccess? linkedAccess) { string key = $"{pid}|{mappedPath}"; if (!_recentImageMapByPidPath.TryGetValue(key, out RecentImageMapState? state) || observedUtc > state.LastSeenUtc.Add(ImageMapRepeatWindow)) { state = new RecentImageMapState { FirstSeenUtc = observedUtc, LastSeenUtc = observedUtc, Path = mappedPath, LastApi = apiName, LastLinkedFileAccess = linkedAccess, Count = 0 }; _recentImageMapByPidPath[key] = state; } state.Count += 1; state.LastSeenUtc = observedUtc; state.LastApi = apiName; if (linkedAccess != null) { state.LastLinkedFileAccess = linkedAccess; } return state.Clone(); } private static bool IsWithinCorrelationWindow(DateTime leftUtc, DateTime rightUtc, TimeSpan window) => Math.Abs((leftUtc - rightUtc).TotalMilliseconds) <= window.TotalMilliseconds; private static bool IsImageSectionMapApi(string apiName) { string api = (apiName ?? string.Empty).Trim(); return api.Equals("NtMapViewOfSection", StringComparison.OrdinalIgnoreCase) || api.Equals("ZwMapViewOfSection", StringComparison.OrdinalIgnoreCase) || api.Equals("NtCreateSection", StringComparison.OrdinalIgnoreCase) || api.Equals("ZwCreateSection", StringComparison.OrdinalIgnoreCase) || api.Equals("CreateFileMappingA", StringComparison.OrdinalIgnoreCase) || api.Equals("CreateFileMappingW", StringComparison.OrdinalIgnoreCase) || api.Equals("CreateFileMapping", StringComparison.OrdinalIgnoreCase) || api.Equals("MapViewOfFile", StringComparison.OrdinalIgnoreCase) || api.Equals("MapViewOfFileEx", StringComparison.OrdinalIgnoreCase); } private static string ExtractMappedImagePath(BrokerEtwEventView view, IReadOnlyDictionary fields) { foreach (string candidate in new[] { view.ImagePath, ReadTrimmedField(fields, "sectionPath"), ReadTrimmedField(fields, "filePath"), ReadTrimmedField(fields, "imagePath"), ReadTrimmedField(fields, "modulePath"), ReadTrimmedField(fields, "mappedPath"), ReadTrimmedField(fields, "path"), ReadTrimmedField(fields, "objectName"), ReadTrimmedField(fields, "name") }) { string normalized = NormalizeCorrelationPath(candidate); if (IsImagePathForMapping(normalized)) { return normalized; } } return string.Empty; } private static string NormalizeCorrelationPath(string? path) { string value = (path ?? string.Empty).Trim().Trim('"'); if (value.Length == 0) { return string.Empty; } value = value.Replace('/', '\\'); if (value.StartsWith("\\??\\", StringComparison.OrdinalIgnoreCase)) { value = value[4..]; } if (value.StartsWith("file:", StringComparison.OrdinalIgnoreCase)) { value = value[5..].TrimStart('\\'); } return value.Trim(); } private static bool IsImagePathForMapping(string? path) { string name = ModuleNameFromPath(path ?? string.Empty); return name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".ocx", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".sys", StringComparison.OrdinalIgnoreCase); } private HeuristicEventView? BuildAntiAnalysisFinding(DateTime timestampUtc, uint actor, uint target, string detection, uint severity, string source, string eventName, string reason, string evidence) { string key = $"{actor}|{target}|{detection}|{evidence}"; _antiAnalysisCountByEvidence.TryGetValue(key, out int count); count += 1; _antiAnalysisCountByEvidence[key] = count; if (count != 1 && count != 5 && count != 25 && count != 100) { return null; } return new HeuristicEventView { TimestampUtc = timestampUtc, LastSeenUtc = timestampUtc, Severity = severity, DetectionName = detection, ActorPid = actor, TargetPid = target == 0 ? actor : target, Source = source, EventName = eventName, Reason = $"reason={reason}; hits={count}", Evidence = evidence, RepeatCount = 1 }; } private static bool HasDetectionTrait(BrokerEtwEventView view, uint trait) => (view.DetectionTraits & trait) != 0; private static bool IsDirectSyscallDetection(BrokerEtwEventView view) { if (HasDetectionTrait(view, BlackbirdNative.IpcEtwTraitDirectSyscall)) { return true; } if (IsDirectSyscallHandleTelemetry(view)) { return true; } string detection = view.DetectionName; if (string.IsNullOrWhiteSpace(detection)) { return false; } return detection.Contains("DIRECT_SYSCALL", StringComparison.OrdinalIgnoreCase) || detection.Contains("DIRECT-SYSCALL", StringComparison.OrdinalIgnoreCase) || detection.Contains("SUSPECT_HANDLE_OPERATION", StringComparison.OrdinalIgnoreCase) || detection.Contains("ANOMALY_ON_HANDLE_OP", StringComparison.OrdinalIgnoreCase); } private static bool IsDirectSyscallHandleTelemetry(BrokerEtwEventView view) { if (view.Family != BlackbirdNative.IpcEtwFamilyHandle) { return false; } if (view.ClassName.Equals("DIRECT-SYSCALL-SUSPECT", StringComparison.OrdinalIgnoreCase)) { return true; } bool exportMismatch = (view.Flags & HandleFlagSyscallExportMismatch) != 0; bool stackSpoof = (view.Flags & HandleFlagStackSpoofSuspect) != 0; bool execOutsideNtdll = (view.Flags & HandleFlagExecProtect) != 0 && (view.Flags & HandleFlagFromNtdll) == 0; return exportMismatch || stackSpoof || execOutsideNtdll; } private static bool IsHookTamperDetection(BrokerEtwEventView view) { if (HasDetectionTrait(view, BlackbirdNative.IpcEtwTraitHookTamper)) { return true; } string detection = view.DetectionName; if (string.IsNullOrWhiteSpace(detection)) { return false; } return detection.Contains("USERMODE_HOOK_TAMPERED", StringComparison.OrdinalIgnoreCase) || detection.Contains("AMSI_PATCH_TAMPERED", StringComparison.OrdinalIgnoreCase) || detection.Contains("ETW_PATCH_TAMPERED", StringComparison.OrdinalIgnoreCase) || detection.Contains("IAT_TAMPER", StringComparison.OrdinalIgnoreCase) || detection.Contains("EAT_TAMPER", StringComparison.OrdinalIgnoreCase) || detection.Contains("NTDLL_IMAGE_TAMPER", StringComparison.OrdinalIgnoreCase) || detection.Contains("SR71_HOOK_WRITE_BLOCKED", StringComparison.OrdinalIgnoreCase) || detection.Contains("SR71_HOOK_PROTECT_BLOCKED", StringComparison.OrdinalIgnoreCase) || detection.Contains("HOOK_TAMPER", StringComparison.OrdinalIgnoreCase); } private static string BuildHandleEvidenceText(IoctlParsedEvent record) { string accessDecoded = EventDetailFormatting.DescribeHandleAccess(record.DesiredAccess); string flagsDecoded = EventDetailFormatting.DescribeHandleFlags(record.HandleFlags); string sampleHex = EventDetailFormatting.FormatSampleHex(record.DeepSample, (int)record.DeepSampleSize); string disasm = EventDetailFormatting.InferSampleDisassembly(record.DeepSample, (int)record.DeepSampleSize); string stackSnapshotHex = EventDetailFormatting.FormatSampleHex(record.StackSnapshot, (int)record.StackSnapshotSize); string stack0 = record.Frames.Length > 0 ? FormatIoctlCodeAddress(record, record.Frames[0]) : "n/a"; string stack1 = record.Frames.Length > 1 ? FormatIoctlCodeAddress(record, record.Frames[1]) : "n/a"; string moduleName = EventDetailFormatting.ModuleNameFromPath(record.OriginPath); string originAddress = FormatIoctlCodeAddress(record, record.OriginAddress); string allocationBase = FormatIoctlCodeAddress(record, record.DeepAllocationBase); string fullFrames = BuildFrameList(record, record.FullFrames, record.FullFrameCount); string captureFlags = DescribeCaptureFlags(record.CaptureFlags); string directSyscallName = EventDetailFormatting.ResolveDirectSyscallApi(record.DesiredAccess, record.HandleFlags); string directSyscallLabel = EventDetailFormatting.BuildDirectSyscallLabel( record.DesiredAccess, record.HandleFlags, record.DeepSample, (int)record.DeepSampleSize); bool hasContext = (record.CaptureFlags & 0x00000001u) != 0; bool hasDebugRegs = (record.CaptureFlags & 0x00000002u) != 0; bool hasFullFrames = (record.CaptureFlags & 0x00000004u) != 0; bool hasStackSnapshot = (record.CaptureFlags & 0x00000008u) != 0; string frameSegment = hasFullFrames ? $"fullFrameCount={record.FullFrameCount} fullFrames={fullFrames} " : "fullFrameCount=0 fullFrames= "; string registerSegment = hasContext ? $"rax=0x{record.RegRax:X} rbx=0x{record.RegRbx:X} rcx=0x{record.RegRcx:X} rdx=0x{record.RegRdx:X} " + $"rsi=0x{record.RegRsi:X} rdi=0x{record.RegRdi:X} rbp=0x{record.RegRbp:X} rsp=0x{record.RegRsp:X} " + $"r8=0x{record.RegR8:X} r9=0x{record.RegR9:X} r10=0x{record.RegR10:X} r11=0x{record.RegR11:X} " + $"r12=0x{record.RegR12:X} r13=0x{record.RegR13:X} r14=0x{record.RegR14:X} r15=0x{record.RegR15:X} " + $"rip=0x{record.RegRip:X} eflags=0x{record.RegEFlags:X} " : string.Empty; string debugSegment = hasDebugRegs ? $"dr0=0x{record.RegDr0:X} dr1=0x{record.RegDr1:X} dr2=0x{record.RegDr2:X} dr3=0x{record.RegDr3:X} dr6=0x{record.RegDr6:X} dr7=0x{record.RegDr7:X} " : string.Empty; string stackSegment = hasStackSnapshot ? $"stackSnapshotAddress=0x{record.StackSnapshotAddress:X} stackSnapshotSize={record.StackSnapshotSize} stackSnapshot={stackSnapshotHex} " : "stackSnapshotAddress=0x0 stackSnapshotSize=0 stackSnapshot= "; return $"ioctlEvidence class={record.HandleClass} syscallName={directSyscallName} syscallLabel={directSyscallLabel.Replace(' ', '_')} access=0x{record.DesiredAccess:X8} ({accessDecoded}) flags=0x{record.HandleFlags:X8} ({flagsDecoded}) " + $"origin={originAddress} protect=0x{record.OriginProtect:X8} module={moduleName} " + $"allocationBase={allocationBase} regionSize=0x{record.DeepRegionSize:X} regionProtect=0x{record.DeepRegionProtect:X8} ({EventDetailFormatting.DescribeMemoryProtection(record.DeepRegionProtect)}) " + $"regionState=0x{record.DeepRegionState:X8} ({EventDetailFormatting.DescribeMemoryState(record.DeepRegionState)}) regionType=0x{record.DeepRegionType:X8} ({EventDetailFormatting.DescribeMemoryType(record.DeepRegionType)}) " + $"path={record.OriginPath} stack0={stack0} stack1={stack1} " + $"captureFlags=0x{record.CaptureFlags:X8} ({captureFlags}) " + frameSegment + registerSegment + debugSegment + stackSegment + $"deepSampleSize={record.DeepSampleSize} deepSample={sampleHex} sampleDisasmHint={disasm}"; } private static string DescribeCaptureFlags(uint captureFlags) { var labels = new List(); if ((captureFlags & 0x00000001u) != 0) { labels.Add("CONTEXT"); } if ((captureFlags & 0x00000002u) != 0) { labels.Add("DEBUG_REGS"); } if ((captureFlags & 0x00000004u) != 0) { labels.Add("FULL_FRAMES"); } if ((captureFlags & 0x00000008u) != 0) { labels.Add("STACK_SNAPSHOT"); } return labels.Count == 0 ? "NONE" : string.Join("|", labels); } private static string BuildFrameList(IoctlParsedEvent record, ulong[]? frames, uint frameCount) { if (frames == null || frames.Length == 0 || frameCount == 0) { return ""; } int safeCount = Math.Min(frames.Length, (int)frameCount); var list = new List(safeCount); for (int i = 0; i < safeCount; i += 1) { ulong frame = frames[i]; if (frame == 0) { continue; } list.Add(FormatIoctlCodeAddress(record, frame)); } return list.Count == 0 ? "" : string.Join(",", list); } private static string FormatIoctlCodeAddress(IoctlParsedEvent record, ulong address) { if (address == 0) { return "n/a"; } string moduleName = EventDetailFormatting.ModuleNameFromPath(record.OriginPath); ulong allocationBase = record.DeepAllocationBase; ulong regionSize = record.DeepRegionSize; bool inRange = regionSize == 0 || (regionSize <= ulong.MaxValue - allocationBase && address < allocationBase + regionSize); if (!moduleName.Equals("unknown", StringComparison.OrdinalIgnoreCase) && allocationBase != 0 && address >= allocationBase && inRange) { string section = (record.DeepRegionType & 0x01000000u) != 0 ? ".image" : ".region"; return $"{moduleName}{section}+0x{address - allocationBase:X}"; } return $"unresolved+0x{address:X}"; } private static bool IsHighRiskIoctlAccess(uint desiredAccess, bool isThreadObject) { if (isThreadObject) { bool threadAll = (desiredAccess & 0x001F03FFu) == 0x001F03FFu; return threadAll || (desiredAccess & (0x0002u | 0x0008u | 0x0010u)) != 0; } bool processAll = (desiredAccess & 0x001F0FFFu) == 0x001F0FFFu; return processAll || (desiredAccess & (0x0002u | 0x0008u | 0x0010u | 0x0020u)) != 0; } private static bool ShouldKeepDirectSyscallHeuristicFromEvidence(IoctlParsedEvent record) { if (record.HandleClass == 2) { return true; } bool exportMismatch = (record.HandleFlags & HandleFlagSyscallExportMismatch) != 0; bool stackSpoof = (record.HandleFlags & HandleFlagStackSpoofSuspect) != 0; bool execOutsideNtdll = (record.HandleFlags & HandleFlagExecProtect) != 0 && (record.HandleFlags & HandleFlagFromNtdll) == 0; bool framesOutsideTeb = (record.HandleFlags & HandleFlagFramesOutsideTebStack) != 0; return exportMismatch || stackSpoof || execOutsideNtdll || framesOutsideTeb; } private static bool IsGdiSubsystemModule(string? originPath) { if (string.IsNullOrWhiteSpace(originPath)) { return false; } string module = EventDetailFormatting.ModuleNameFromPath(originPath).ToLowerInvariant(); return module is "win32u.dll" or "gdi32.dll" or "gdi32full.dll" or "user32.dll" or "user32full.dll"; } private static string DescribeFileOperation(uint operation) { return operation switch { BlackbirdNative.FileOperationCreate => "CREATE", BlackbirdNative.FileOperationRead => "READ", BlackbirdNative.FileOperationWrite => "WRITE", BlackbirdNative.FileOperationClose => "CLOSE", BlackbirdNative.FileOperationCleanup => "CLEANUP", BlackbirdNative.FileOperationSetInformation => "SET_INFORMATION", BlackbirdNative.FileOperationQueryInformation => "QUERY_INFORMATION", BlackbirdNative.FileOperationDirectoryControl => "DIRECTORY_CONTROL", BlackbirdNative.FileOperationFsControl => "FS_CONTROL", _ => "UNKNOWN" }; } private static string BuildFileSummaryPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return ""; } const int maxChars = 84; if (path.Length <= maxChars) { return path; } return "..." + path[^maxChars..]; } private static string BuildFilesystemClusterKey(IoctlParsedEvent record) { string path = string.IsNullOrWhiteSpace(record.FilePath) ? "" : record.FilePath; return $"{record.FileOperation}|{record.FileProcessPid}|{path}"; } private void ResetFilesystemTimelineCluster() { _filesystemClusterOperationCounts.Clear(); _filesystemClusterSamplesByOperation.Clear(); _filesystemClusterTotal = 0; _filesystemClusterWindowStartUtc = DateTime.MinValue; _filesystemClusterLastSeenUtc = DateTime.MinValue; } private IReadOnlyList FlushFilesystemTimelineClusterIfNeeded(DateTime nowUtc, bool force) { if (_filesystemClusterTotal <= 0) { return Array.Empty(); } if (!force && (nowUtc - _filesystemClusterLastSeenUtc) < FilesystemTimelineClusterIdleFlush) { return Array.Empty(); } double windowMs = Math.Max(1, (_filesystemClusterLastSeenUtc - _filesystemClusterWindowStartUtc).TotalMilliseconds); var emitted = new List(_filesystemClusterOperationCounts.Count); foreach (var entry in _filesystemClusterOperationCounts.OrderByDescending(x => x.Value).ThenBy(x => x.Key)) { if (entry.Value <= 0) { continue; } int count = entry.Value; (uint Pid, uint Tid, string Path, uint Operation) sample = _filesystemClusterSamplesByOperation.TryGetValue(entry.Key, out var found) ? found : (0u, 0u, "", 0u); string operationName = DescribeFileOperation(sample.Operation); string samplePath = string.IsNullOrWhiteSpace(sample.Path) ? "" : sample.Path; string summaryPath = BuildFileSummaryPath(samplePath); emitted.Add(new TelemetryEvent { TimestampUtc = _filesystemClusterLastSeenUtc, PID = unchecked((int)sample.Pid), TID = unchecked((int)sample.Tid), Group = "Filesystem", SubType = operationName, Summary = $"{operationName} x{count} pid={sample.Pid} path={summaryPath}", Details = $"windowStart={_filesystemClusterWindowStartUtc:O} windowEnd={_filesystemClusterLastSeenUtc:O} windowMs={windowMs:0} " + $"operation={operationName} count={count} clusterTotal={_filesystemClusterTotal} samplePid={sample.Pid} sampleTid={sample.Tid} samplePath={samplePath}" }); } ResetFilesystemTimelineCluster(); return emitted; } private IReadOnlyList AccumulateFilesystemTimelineCluster(IoctlParsedEvent record, DateTime nowUtc) { if (record.Type != BlackbirdNative.EventTypeFileSystem) { return Array.Empty(); } var emitted = new List(); if (_filesystemClusterTotal > 0 && (nowUtc - _filesystemClusterWindowStartUtc) >= FilesystemTimelineClusterWindow) { emitted.AddRange(FlushFilesystemTimelineClusterIfNeeded(nowUtc, force: true)); } if (_filesystemClusterTotal == 0) { _filesystemClusterWindowStartUtc = nowUtc; } string clusterKey = BuildFilesystemClusterKey(record); _filesystemClusterTotal += 1; _filesystemClusterLastSeenUtc = nowUtc; _filesystemClusterOperationCounts.TryGetValue(clusterKey, out int count); _filesystemClusterOperationCounts[clusterKey] = count + 1; if (!_filesystemClusterSamplesByOperation.ContainsKey(clusterKey)) { _filesystemClusterSamplesByOperation[clusterKey] = (record.FileProcessPid, record.FileThreadId, string.IsNullOrWhiteSpace(record.FilePath) ? "" : record.FilePath, record.FileOperation); } if (_filesystemClusterTotal >= FilesystemTimelineClusterFlushCount) { emitted.AddRange(FlushFilesystemTimelineClusterIfNeeded(nowUtc, force: true)); } return emitted.Count == 0 ? Array.Empty() : emitted; } private TelemetryEvent? MapIoctlRecord(IoctlParsedEvent record) { DateTime now = DateTime.UtcNow; if (record.Type == BlackbirdNative.EventTypeHandle) { RememberHandleEvidence(record); uint caller = record.CallerPid; uint target = record.TargetPid; if (caller != 0 && !_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(caller)) { return null; } string originModuleForFilter = EventDetailFormatting.ModuleNameFromPath(record.OriginPath); if (EventDetailFormatting.IsSr71Module(originModuleForFilter)) { return null; } string className = record.HandleClass switch { 1 => "LEGITIMATE-SYSCALL", 2 => "DIRECT-SYSCALL-SUSPECT", _ => "UNKNOWN" }; string syscallLabel = EventDetailFormatting.BuildDirectSyscallLabel( record.DesiredAccess, record.HandleFlags, record.DeepSample, (int)record.DeepSampleSize); return new TelemetryEvent { TimestampUtc = now, PID = unchecked((int)caller), TID = unchecked((int)record.ThreadId), Group = "Kernel-IOCTL", SubType = "Handle", Summary = $"{className} {syscallLabel} caller={caller} target={target} access=0x{record.DesiredAccess:X8}", Details = BuildHandleEvidenceText(record) }; } if (record.Type == BlackbirdNative.EventTypeThread) { uint process = record.ProcessPid; uint creator = record.CreatorPid; if (creator != 0 && process != 0 && creator != process && !_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(creator)) { return null; } string eventKind = DetermineThreadLifecycleKind(record.ThreadFlags, record.StartAddress, record.ImageSize); string threadFlags = EventDetailFormatting.DescribeThreadFlags(record.ThreadFlags); return new TelemetryEvent { TimestampUtc = now, PID = unchecked((int)process), TID = unchecked((int)record.ThreadId), Group = "Kernel-IOCTL", SubType = $"Thread{eventKind}", Summary = $"{eventKind} creator={creator} process={process} flags=0x{record.ThreadFlags:X8}", Details = $"seq={record.Sequence} start=0x{record.StartAddress:X} imageBase=0x{record.ImageBase:X} imageSize=0x{record.ImageSize:X} decodedFlags={threadFlags}" }; } if (record.Type == BlackbirdNative.EventTypeFileSystem) { return null; } if (record.Type == BlackbirdNative.EventTypeEnterprise) { uint actor = record.EnterpriseProcessPid; uint target = record.EnterpriseTargetProcessPid; if (actor != 0 && !_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(actor)) { return null; } string operation = DescribeEnterpriseOperation(record.EnterpriseOperation); string flags = DescribeEnterpriseFlags(record.EnterpriseFlags); return new TelemetryEvent { TimestampUtc = now, PID = unchecked((int)actor), TID = unchecked((int)record.EnterpriseThreadId), Group = "Kernel-IOCTL", SubType = "Enterprise", Summary = $"{operation} actor={actor} target={target} flags=0x{record.EnterpriseFlags:X8}", Details = $"seq={record.Sequence} op={operation} subOp={record.EnterpriseSubOperation} actor={actor} tid={record.EnterpriseThreadId} " + $"target={target} targetTid={record.EnterpriseTargetThreadId} desired=0x{record.EnterpriseDesiredAccess:X8} " + $"granted=0x{record.EnterpriseGrantedAccess:X8} status=0x{record.EnterpriseStatus:X8} " + $"object=0x{record.EnterpriseObjectAddress:X} aux0=0x{record.EnterpriseAux0:X} aux1=0x{record.EnterpriseAux1:X} " + $"protocol={record.EnterpriseProtocol} localPort={record.EnterpriseLocalPort} remotePort={record.EnterpriseRemotePort} flags={flags}" }; } return null; } private bool ShouldAcceptFilesystemRecord(IoctlParsedEvent record) { if (record.Type != BlackbirdNative.EventTypeFileSystem) { return true; } uint pid = record.FileProcessPid; if (pid == 0 || (!_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(pid))) { return false; } return !IsBlackbirdInternalFilesystemPath(record.FilePath); } private bool ShouldEvaluateSignatureIntelForIoctl(IoctlParsedEvent record, bool acceptFilesystem) { if (record.Type == BlackbirdNative.EventTypeFileSystem) { return acceptFilesystem; } if (record.Type == BlackbirdNative.EventTypeRegistry) { return record.RegistryProcessPid != 0 && IsTrackedPid(record.RegistryProcessPid); } if (record.Type == BlackbirdNative.EventTypeEnterprise) { return record.EnterpriseProcessPid != 0 && IsTrackedPid(record.EnterpriseProcessPid); } if (record.Type == BlackbirdNative.EventTypeHandle) { if (record.CallerPid != 0 && !IsTrackedPid(record.CallerPid)) { return false; } string originModule = EventDetailFormatting.ModuleNameFromPath(record.OriginPath); return !EventDetailFormatting.IsSr71Module(originModule); } if (record.Type == BlackbirdNative.EventTypeThread) { if (record.CreatorPid != 0 && record.ProcessPid != 0 && record.CreatorPid != record.ProcessPid) { return IsTrackedPid(record.CreatorPid); } return IsTrackedPid(record.ProcessPid); } return true; } private static bool IsBlackbirdInternalFilesystemPath(string? path) { if (string.IsNullOrWhiteSpace(path)) { return false; } string normalized = path.Replace('/', '\\').Trim(); return normalized.Contains("\\ProgramData\\Blackbird", StringComparison.OrdinalIgnoreCase) || normalized.Contains("\\Device\\HarddiskVolume3\\ProgramData\\Blackbird", StringComparison.OrdinalIgnoreCase) || normalized.EndsWith("\\controller.log", StringComparison.OrdinalIgnoreCase) || normalized.EndsWith("\\controller.log.1", StringComparison.OrdinalIgnoreCase); } private static IoctlParsedEvent? MapIoctlFilesystem(IoctlParsedEvent record) { return record.Type == BlackbirdNative.EventTypeFileSystem ? record : null; } private static IoctlParsedEvent? MapIoctlRegistry(IoctlParsedEvent record) { return record.Type == BlackbirdNative.EventTypeRegistry ? record : null; } private static ThreadLifecycleEventSample? MapIoctlThreadLifecycle(IoctlParsedEvent record) { if (record.Type != BlackbirdNative.EventTypeThread) { return null; } DateTime now = DateTime.UtcNow; string eventKind = DetermineThreadLifecycleKind(record.ThreadFlags, record.StartAddress, record.ImageSize); string decodedFlags = EventDetailFormatting.DescribeThreadFlags(record.ThreadFlags); return new ThreadLifecycleEventSample { TimestampUtc = now, ProcessPid = record.ProcessPid, ThreadId = record.ThreadId, CreatorPid = record.CreatorPid, Flags = record.ThreadFlags, StartAddress = record.StartAddress, ImageBase = record.ImageBase, ImageSize = record.ImageSize, EventKind = eventKind, Notes = $"flags={decodedFlags}" }; } private static string DetermineThreadLifecycleKind(uint threadFlags, ulong startAddress, ulong imageSize) { if ((threadFlags & 0x00000200u) != 0) { return "Exit"; } if ((threadFlags & 0x00000001u) == 0 && startAddress == 0 && imageSize == 0) { return "Exit"; } if ((threadFlags & 0x00000001u) != 0) { return "Start"; } return "Update"; } private ProcessRelationView? MapIoctlRelation(IoctlParsedEvent record) { DateTime now = DateTime.UtcNow; if (record.Type == BlackbirdNative.EventTypeHandle) { uint source = record.CallerPid; uint target = record.TargetPid; if (source == 0 || target == 0 || source == target) { return null; } if (!_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(source)) { return null; } string accessDecoded = EventDetailFormatting.DescribeHandleAccess(record.DesiredAccess); string flagsDecoded = EventDetailFormatting.DescribeHandleFlags(record.HandleFlags); string originModule = EventDetailFormatting.ModuleNameFromPath(record.OriginPath); bool isSr71Handle = EventDetailFormatting.IsSr71Module(originModule); if (isSr71Handle) { return null; } string detailSignature = $"handle|{source}|{target}|{record.DesiredAccess:X8}|{record.HandleFlags:X8}|{originModule}"; string detailText = $"sourcePid={source} targetPid={target} relationType=HandleOpen access=0x{record.DesiredAccess:X8} ({accessDecoded}) " + $"flags=0x{record.HandleFlags:X8} ({flagsDecoded}) originModule={originModule} " + $"handleOwner={(isSr71Handle ? "SR71" : "ActorProcess")}"; return new ProcessRelationView { FirstSeenUtc = now, LastSeenUtc = now, SourcePid = source, TargetPid = target, RelationType = "HandleOpen", LastAccessMask = record.DesiredAccess, LastFlags = record.HandleFlags, OriginSource = "Kernel-IOCTL", OriginModule = originModule, DetailSignature = detailSignature, DetailText = detailText, RepeatCount = 1 }; } if (record.Type == BlackbirdNative.EventTypeThread) { uint source = record.CreatorPid; uint target = record.ProcessPid; if (source == 0 || target == 0 || source == target) { return null; } if (!_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(source)) { return null; } return new ProcessRelationView { FirstSeenUtc = now, LastSeenUtc = now, SourcePid = source, TargetPid = target, RelationType = "ThreadCreate", LastAccessMask = 0, LastFlags = record.ThreadFlags, OriginSource = "Kernel-IOCTL", OriginModule = EventDetailFormatting.ModuleNameFromPath(record.OriginPath), RepeatCount = 1 }; } if (record.Type == BlackbirdNative.EventTypeEnterprise) { uint source = record.EnterpriseProcessPid; uint target = record.EnterpriseTargetProcessPid; if (source == 0 || target == 0 || source == target) { return null; } if (!_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(source)) { return null; } string operation = DescribeEnterpriseOperation(record.EnterpriseOperation); return new ProcessRelationView { FirstSeenUtc = now, LastSeenUtc = now, SourcePid = source, TargetPid = target, RelationType = "EnterpriseAccess", LastAccessMask = record.EnterpriseDesiredAccess, LastFlags = record.EnterpriseFlags, OriginSource = "Kernel-IOCTL", OriginModule = operation, DetailSignature = $"enterprise|{source}|{target}|{record.EnterpriseOperation}|{record.EnterpriseFlags:X8}", DetailText = $"sourcePid={source} targetPid={target} relationType=EnterpriseAccess operation={operation} " + $"access=0x{record.EnterpriseDesiredAccess:X8} flags=0x{record.EnterpriseFlags:X8}", RepeatCount = 1 }; } return null; } private static ProcessRelationView? MapEtwRelation(BrokerEtwEventView view) { if (view.Family != BlackbirdNative.IpcEtwFamilyProcess || (view.Flags & BlackbirdNative.IpcEtwFlagProcessIsCreate) == 0) { return null; } uint source = view.CreatorPid != 0 ? view.CreatorPid : (view.ParentPid != 0 ? view.ParentPid : view.ActorPid); uint target = view.ProcessPid != 0 ? view.ProcessPid : view.TargetPid; if (source == 0 || target == 0 || source == target) { return null; } uint createStatus = unchecked((uint)view.CreateStatus); string detailSignature = $"create|{source}|{target}|0x{view.ProcessStartKey:X}|0x{createStatus:X8}"; string detailText = $"sourcePid={source} targetPid={target} relationType=ProcessCreate creatorPid={view.CreatorPid} " + $"parentPid={view.ParentPid} createStatus=0x{createStatus:X8} startKey=0x{view.ProcessStartKey:X} " + $"imagePath={view.ImagePath}"; return new ProcessRelationView { FirstSeenUtc = view.TimestampUtc, LastSeenUtc = view.TimestampUtc, SourcePid = source, TargetPid = target, RelationType = "ProcessCreate", LastAccessMask = 0, LastFlags = view.Flags, OriginSource = view.Source, OriginModule = EventDetailFormatting.ModuleNameFromPath(view.ImagePath), DetailSignature = detailSignature, DetailText = detailText, RepeatCount = 1 }; } private HeuristicEventView? MapIoctlHeuristic(IoctlParsedEvent record) { HeuristicEventView? antiAnalysis = EvaluateAntiAnalysisIoctlHeuristic(record); if (antiAnalysis != null) { return antiAnalysis; } if (record.Type == BlackbirdNative.EventTypeEnterprise) { uint actor = record.EnterpriseProcessPid; uint target = record.EnterpriseTargetProcessPid; if (actor == 0 || (!_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(actor))) { return null; } string operation = DescribeEnterpriseOperation(record.EnterpriseOperation); string flags = DescribeEnterpriseFlags(record.EnterpriseFlags); uint enterpriseSeverity = (record.EnterpriseFlags & BlackbirdNative.EnterpriseFlagCritical) != 0 ? 8u : 6u; return new HeuristicEventView { TimestampUtc = DateTime.UtcNow, LastSeenUtc = DateTime.UtcNow, Severity = enterpriseSeverity, DetectionName = $"ENTERPRISE_{operation}", ActorPid = actor, TargetPid = target, Source = "Kernel-IOCTL/Enterprise", EventName = "EnterpriseTelemetry", CorrelationFlags = record.EnterpriseFlags, CorrelationAccessMask = record.EnterpriseDesiredAccess, CorrelationAgeMs = 0, Reason = $"enterprise operation={operation}; flags={flags}; desired=0x{record.EnterpriseDesiredAccess:X8}; " + $"status=0x{record.EnterpriseStatus:X8}", Evidence = $"actor={actor} tid={record.EnterpriseThreadId} target={target} targetTid={record.EnterpriseTargetThreadId} " + $"object=0x{record.EnterpriseObjectAddress:X} aux0=0x{record.EnterpriseAux0:X} aux1=0x{record.EnterpriseAux1:X}", RepeatCount = 1 }; } if (record.Type != BlackbirdNative.EventTypeHandle || record.CallerPid == 0 || record.TargetPid == 0) { return null; } if (record.HandleClass != 2) { return null; } DateTime now = DateTime.UtcNow; bool isThreadObject = (record.HandleFlags & HandleFlagThreadObject) != 0; bool highRiskAccess = IsHighRiskIoctlAccess(record.DesiredAccess, isThreadObject); bool exportMismatch = (record.HandleFlags & HandleFlagSyscallExportMismatch) != 0; bool stackSpoof = (record.HandleFlags & HandleFlagStackSpoofSuspect) != 0; bool stackValidated = (record.HandleFlags & HandleFlagStackValidated) != 0; bool tebBoundsValid = (record.HandleFlags & HandleFlagTebStackBoundsValid) != 0; bool execOutsideNtdll = (record.HandleFlags & HandleFlagExecProtect) != 0 && (record.HandleFlags & HandleFlagFromNtdll) == 0; uint severity = exportMismatch ? 6u : highRiskAccess ? 5u : (stackSpoof || execOutsideNtdll) ? 4u : 3u; string handleFlagsDecoded = EventDetailFormatting.DescribeHandleFlags(record.HandleFlags); string corrAccessDecoded = EventDetailFormatting.DescribeHandleAccess(record.DesiredAccess); string syscallName = EventDetailFormatting.ResolveDirectSyscallApi(record.DesiredAccess, record.HandleFlags); string syscallSummary = EventDetailFormatting.BuildDirectSyscallSummary( record.CallerPid.ToString(CultureInfo.InvariantCulture), record.TargetPid.ToString(CultureInfo.InvariantCulture), record.DesiredAccess, record.HandleFlags, record.DeepSample, (int)record.DeepSampleSize, record.OriginPath); return new HeuristicEventView { TimestampUtc = now, LastSeenUtc = now, Severity = severity, DetectionName = $"DIRECT_SYSCALL_SUSPECT_HANDLE_OPERATION [{syscallName}]", ActorPid = record.CallerPid, TargetPid = record.TargetPid, Source = "Kernel-IOCTL", EventName = "HandleTelemetry", CorrelationFlags = 0, CorrelationAccessMask = record.DesiredAccess, CorrelationAgeMs = 0, Reason = $"{syscallSummary}; ioctlClass={record.HandleClass}; directClass=true; highRiskAccess={highRiskAccess}; " + $"exportMismatch={exportMismatch}; stackSpoof={stackSpoof}; stackValidated={stackValidated}; " + $"tebBoundsValid={tebBoundsValid}; execOutsideNtdll={execOutsideNtdll}; " + $"handleFlags={handleFlagsDecoded}; access={corrAccessDecoded}", Evidence = BuildHandleEvidenceText(record), RepeatCount = 1 }; } private HeuristicEventView? EvaluateAntiAnalysisIoctlHeuristic(IoctlParsedEvent record) { DateTime now = DateTime.UtcNow; if (record.Type == BlackbirdNative.EventTypeFileSystem) { if (!ShouldAcceptFilesystemRecord(record)) { return null; } uint pid = record.FileProcessPid; string path = record.FilePath ?? string.Empty; if (!TryMatchVirtualizationArtifact(path, out string artifact)) { return null; } string evidence = $"filesystem operation=0x{record.FileOperation:X} pid={pid} tid={record.FileThreadId} artifact={artifact} path={path}"; return BuildAntiAnalysisFinding(now, pid, pid, "ANTI_VM_FILESYSTEM_ARTIFACT_PROBE", 5, "Kernel-IOCTL/AntiAnalysis", "FileSystemTelemetry", $"probed virtualization filesystem artifact {artifact}", evidence); } if (record.Type == BlackbirdNative.EventTypeRegistry) { uint pid = record.RegistryProcessPid; if (pid == 0 || (!_filterTrackedPids.IsEmpty && !_filterTrackedPids.ContainsKey(pid))) { return null; } if (!IsRegistryProbeOperation(record.RegistryOperation)) { return null; } string registryPath = BuildRegistryProbePath(record); if (!TryMatchVirtualizationArtifact(registryPath, out string artifact)) { return null; } string operation = DescribeRegistryProbeOperation(record.RegistryOperation); string evidence = $"registry operation={operation} pid={pid} tid={record.RegistryThreadId} artifact={artifact} path={registryPath}"; return BuildAntiAnalysisFinding(now, pid, pid, "ANTI_VM_REGISTRY_ARTIFACT_PROBE", 5, "Kernel-IOCTL/AntiAnalysis", "RegistryTelemetry", $"queried virtualization registry artifact {artifact}", evidence); } return null; } private static string BuildRegistryProbePath(IoctlParsedEvent record) { string key = record.RegistryKeyPath ?? string.Empty; string value = record.RegistryValueName ?? string.Empty; if (string.IsNullOrWhiteSpace(value)) { return key; } return string.IsNullOrWhiteSpace(key) ? value : $"{key}\\{value}"; } private static string DescribeEnterpriseOperation(uint operation) { return operation switch { BlackbirdNative.EnterpriseOperationProcessCredentialAccess => "PROCESS_CREDENTIAL_ACCESS", BlackbirdNative.EnterpriseOperationProcessPrivilegedAccess => "PROCESS_PRIVILEGED_ACCESS", BlackbirdNative.EnterpriseOperationTokenAccess => "TOKEN_ACCESS", BlackbirdNative.EnterpriseOperationRegistryCredentialHiveAccess => "REGISTRY_CREDENTIAL_HIVE", BlackbirdNative.EnterpriseOperationRegistryLsaPolicyAccess => "REGISTRY_LSA_POLICY", BlackbirdNative.EnterpriseOperationRegistryKerberosNtlmAccess => "REGISTRY_KERBEROS_NTLM", BlackbirdNative.EnterpriseOperationRegistryServiceConfigAccess => "REGISTRY_SERVICE_CONFIG", BlackbirdNative.EnterpriseOperationRegistryLpePersistenceAccess => "REGISTRY_LPE_PERSISTENCE", BlackbirdNative.EnterpriseOperationFileCredentialStoreAccess => "FILE_CREDENTIAL_STORE", BlackbirdNative.EnterpriseOperationFileDirectoryCredentialAccess => "FILE_CREDENTIAL_DIRECTORY", BlackbirdNative.EnterpriseOperationFileDriverArtifactAccess => "FILE_DRIVER_ARTIFACT", BlackbirdNative.EnterpriseOperationNetworkAdProtocolConnect => "NETWORK_AD_PROTOCOL", _ => $"UNKNOWN_{operation}" }; } private static string DescribeEnterpriseFlags(uint flags) { var parts = new List(8); if ((flags & BlackbirdNative.EnterpriseFlagCritical) != 0) parts.Add("Critical"); if ((flags & BlackbirdNative.EnterpriseFlagQuery) != 0) parts.Add("Query"); if ((flags & BlackbirdNative.EnterpriseFlagWrite) != 0) parts.Add("Write"); if ((flags & BlackbirdNative.EnterpriseFlagCreate) != 0) parts.Add("Create"); if ((flags & BlackbirdNative.EnterpriseFlagDelete) != 0) parts.Add("Delete"); if ((flags & BlackbirdNative.EnterpriseFlagDuplicateHandle) != 0) parts.Add("DuplicateHandle"); if ((flags & BlackbirdNative.EnterpriseFlagProcessObject) != 0) parts.Add("ProcessObject"); if ((flags & BlackbirdNative.EnterpriseFlagThreadObject) != 0) parts.Add("ThreadObject"); if ((flags & BlackbirdNative.EnterpriseFlagVmRead) != 0) parts.Add("VmRead"); if ((flags & BlackbirdNative.EnterpriseFlagVmWrite) != 0) parts.Add("VmWrite"); if ((flags & BlackbirdNative.EnterpriseFlagVmOperation) != 0) parts.Add("VmOperation"); if ((flags & BlackbirdNative.EnterpriseFlagCreateThread) != 0) parts.Add("CreateThread"); if ((flags & BlackbirdNative.EnterpriseFlagCredentialProcess) != 0) parts.Add("CredentialProcess"); if ((flags & BlackbirdNative.EnterpriseFlagPrivilegedTarget) != 0) parts.Add("PrivilegedTarget"); if ((flags & BlackbirdNative.EnterpriseFlagLsassTarget) != 0) parts.Add("LsassTarget"); if ((flags & BlackbirdNative.EnterpriseFlagWinlogonTarget) != 0) parts.Add("WinlogonTarget"); if ((flags & BlackbirdNative.EnterpriseFlagServiceConfig) != 0) parts.Add("ServiceConfig"); if ((flags & BlackbirdNative.EnterpriseFlagSecurityHive) != 0) parts.Add("SecurityHive"); if ((flags & BlackbirdNative.EnterpriseFlagLsaPolicy) != 0) parts.Add("LsaPolicy"); if ((flags & BlackbirdNative.EnterpriseFlagKerberosNtlm) != 0) parts.Add("KerberosNtlm"); if ((flags & BlackbirdNative.EnterpriseFlagCredentialFile) != 0) parts.Add("CredentialFile"); if ((flags & BlackbirdNative.EnterpriseFlagDriverArtifact) != 0) parts.Add("DriverArtifact"); if ((flags & BlackbirdNative.EnterpriseFlagAdNetwork) != 0) parts.Add("AdNetwork"); if ((flags & BlackbirdNative.EnterpriseFlagDirectSyscallSuspect) != 0) parts.Add("DirectSyscallSuspect"); if ((flags & BlackbirdNative.EnterpriseFlagQueryAccess) != 0) parts.Add("QueryAccess"); if ((flags & BlackbirdNative.EnterpriseFlagThreadContext) != 0) parts.Add("ThreadContext"); if ((flags & BlackbirdNative.EnterpriseFlagSetOrTerminate) != 0) parts.Add("SetOrTerminate"); return parts.Count == 0 ? "" : string.Join("|", parts); } private static bool IsRegistryProbeOperation(uint operation) { return operation is BlackbirdNative.RegistryOperationQueryValue or BlackbirdNative .RegistryOperationQueryKey or BlackbirdNative.RegistryOperationEnumerateKey or BlackbirdNative.RegistryOperationEnumerateValue or BlackbirdNative.RegistryOperationOpenKey; } private static string DescribeRegistryProbeOperation(uint operation) { return operation switch { BlackbirdNative.RegistryOperationQueryValue => "QUERY_VALUE", BlackbirdNative.RegistryOperationQueryKey => "QUERY_KEY", BlackbirdNative.RegistryOperationEnumerateKey => "ENUMERATE_KEY", BlackbirdNative.RegistryOperationEnumerateValue => "ENUMERATE_VALUE", BlackbirdNative.RegistryOperationOpenKey => "OPEN_KEY", _ => $"0x{operation:X}" }; } private static bool TryMatchVirtualizationArtifact(string? text, out string artifact) { artifact = string.Empty; if (string.IsNullOrWhiteSpace(text)) { return false; } string normalized = text.Replace('/', '\\').Trim(); for (int i = 0; i < VirtualizationArtifactPatterns.Length; i += 1) { if (normalized.Contains(VirtualizationArtifactPatterns[i].Token, StringComparison.OrdinalIgnoreCase)) { artifact = VirtualizationArtifactPatterns[i].Label; return true; } } return false; } private void RememberObservedProcessStart(BrokerEtwEventView view) { DateTime observedUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; ulong startKey = view.ProcessStartKey; if (view.ProcessPid != 0 && !_observedProcessStartUtcByPid.ContainsKey(view.ProcessPid)) { _observedProcessStartUtcByPid[view.ProcessPid] = observedUtc; } if (view.EventProcessId != 0 && !_observedProcessStartUtcByPid.ContainsKey(view.EventProcessId)) { _observedProcessStartUtcByPid[view.EventProcessId] = observedUtc; } if (startKey != 0) { if (view.ProcessPid != 0) { _observedProcessStartKeyByPid[view.ProcessPid] = startKey; } if (view.TargetPid != 0) { _observedProcessStartKeyByPid[view.TargetPid] = startKey; } if (view.EventProcessId != 0) { _observedProcessStartKeyByPid[view.EventProcessId] = startKey; } } if (view.Family == BlackbirdNative.IpcEtwFamilyProcess && (view.Flags & BlackbirdNative.IpcEtwFlagProcessIsCreate) != 0) { uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.TargetPid; if (pid != 0) { _observedProcessStartUtcByPid[pid] = observedUtc; if (view.EventThreadId != 0) { _observedInitialThreadIdByPid[pid] = view.EventThreadId; } } } } private bool TryDescribeHookStartupContext(BrokerEtwEventView view, out string headline, out string detail) { headline = string.Empty; detail = string.Empty; if (!EventDetailFormatting.IsApiGraphCandidate(view)) { return false; } uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid; if (pid == 0) { return false; } if (!_observedProcessStartUtcByPid.TryGetValue(pid, out DateTime startUtc)) { if (_currentSession != null && _currentSession.Pid == unchecked((int)pid)) { startUtc = _currentSession.CaptureStartUtc; } else { return false; } } DateTime eventUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; if (eventUtc < startUtc) { startUtc = eventUtc; } double ageMs = Math.Max(0, (eventUtc - startUtc).TotalMilliseconds); if (ageMs > 4000) { return false; } string callerOrigin = view.CallerOriginLabel; bool processImageCaller = callerOrigin.Equals("process-image", StringComparison.OrdinalIgnoreCase); bool plausibleStartupCaller = processImageCaller || callerOrigin.Equals("system", StringComparison.OrdinalIgnoreCase) || callerOrigin.Equals("non-system-dll", StringComparison.OrdinalIgnoreCase); if (!plausibleStartupCaller) { return false; } uint initialThreadId = _observedInitialThreadIdByPid.TryGetValue(pid, out uint trackedThreadId) ? trackedThreadId : 0; bool primaryThread = initialThreadId != 0 && view.EventThreadId == initialThreadId; string phase = ageMs <= 750 ? "very early process startup" : "early process startup"; headline = ageMs <= 750 ? "Startup Path" : "Early Startup"; if (processImageCaller && primaryThread && ageMs <= 750) { detail = $"StartupContext: inferred {phase} on the initial thread from the process image; likely loader / CRT / compiler-generated initializer activity. " + "TLS callbacks or .CRT$XL* initializer paths are possible here, but this remains an inference from timing and caller-origin telemetry."; return true; } if (primaryThread && ageMs <= 2000) { detail = $"StartupContext: inferred {phase} on the initial thread; likely loader or CRT initialization traffic rather than steady-state behavior."; return true; } detail = $"StartupContext: inferred {phase}; this call landed inside the startup window and may still reflect loader, CRT, static-initializer, or DLL attach activity."; return true; } private void HandleBrokerEtwView(BrokerEtwEventView view) { HandleBrokerEtwViews(new[] { view }); } private void HandleBrokerEtwViews(IReadOnlyList views) { if (views.Count == 0) { return; } var etwRows = new List(views.Count); var heuristics = new List(); var relations = new List(); var memoryAttributions = new List(16); var timelineEvents = new List(views.Count); for (int i = 0; i < views.Count; i += 1) { BrokerEtwEventView view = views[i]; RememberObservedProcessStart(view); TelemetryEvent? apiTimelineEvent = null; UpdateHookPipelineDiagnostics(view); ObserveTargetOutputEvent(view); ObserveTargetLifecycleEvent(view); IReadOnlyList signatureIntelFindings = QueueSignatureIntelForView(view); if (signatureIntelFindings.Count > 0) { heuristics.AddRange(signatureIntelFindings); } PersistObservedHookStackSnapshot(view); QueueThreadStackFallbackCapture(view); MemoryRegionAttributionSample? memoryAttribution = CreateMemoryRegionAttributionSample(view); if (memoryAttribution != null) { memoryAttributions.Add(memoryAttribution); HeuristicEventView? memoryLifecycleHeuristic = EvaluateMemoryLifecycleHeuristic(memoryAttribution); if (memoryLifecycleHeuristic != null) { heuristics.Add(memoryLifecycleHeuristic); } } if (EventDetailFormatting.IsApiGraphCandidate(view)) { apiTimelineEvent = HandleApiHookEvent(view); if (apiTimelineEvent != null) { timelineEvents.Add(apiTimelineEvent); } HeuristicEventView? memPatternHeuristic = EvaluateCrossProcessMemoryHeuristic(view); if (memPatternHeuristic != null) { heuristics.Add(memPatternHeuristic); } HeuristicEventView? antiAnalysisHeuristic = EvaluateAntiAnalysisHeuristic(view); if (antiAnalysisHeuristic != null) { heuristics.Add(antiAnalysisHeuristic); } HeuristicEventView? imageMapHeuristic = EvaluateImageSectionMapHeuristic(view); if (imageMapHeuristic != null) { heuristics.Add(imageMapHeuristic); } } bool keepEtw = ShouldKeepEtwEvent(view); if (keepEtw) { etwRows.Add(view); } if (ObserveExtendedActivity(view)) { ScheduleExtendedActivitySnapshot(); } ProcessRelationView? relation = MapEtwRelation(view); if (relation != null) { relations.Add(relation); } string detection = view.DetectionName; DiagnosticsState.SetValue("ETW Status", "Live"); if (detection.Equals("USERMODE_PROCESS_TERMINATE_BREAKPOINT", StringComparison.OrdinalIgnoreCase)) { uint terminatedPid = view.TargetPid != 0 ? view.TargetPid : view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid; string reason = string.IsNullOrWhiteSpace(view.Reason) ? $"NtTerminateProcess observed from {ProcessIdentityResolver.Describe(view.ActorPid)}" : view.Reason; RememberTargetExitReason(terminatedPid, reason); DiagnosticsState.SetValue("Target Exit Cause", reason); } if (detection.Equals("KERNEL_HOOK_STATUS", StringComparison.OrdinalIgnoreCase)) { uint installed = view.CorrelationFlags; uint total = view.CorrelationAccessMask; uint requiredMiss = view.CorrelationAgeMs; string hookStatus = requiredMiss == 0 ? $"OK ({installed}/{total})" : $"DEGRADED ({installed}/{total}, {requiredMiss} required miss)"; DiagnosticsState.SetValue("Kernel Hooks", hookStatus); } if (IsHookTamperDetection(view)) { DiagnosticsState.SetValue("Hook Integrity", "TAMPERED"); } else if (detection.Equals("USERMODE_HOOK_INTEGRITY_OK", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("Hook Integrity", "OK"); MarkSr71HookReadyFromTelemetry(); } if (detection.Equals("AMSI_PATCH_TAMPERED", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("AMSI Integrity", "TAMPERED"); } else if (detection.Equals("AMSI_PATCH_OK", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("AMSI Integrity", "OK"); MarkSr71HookReadyFromTelemetry(); } if (detection.Equals("ETW_PATCH_TAMPERED", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("ETW Integrity", "TAMPERED"); } else if (detection.Equals("ETW_PATCH_OK", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("ETW Integrity", "OK"); MarkSr71HookReadyFromTelemetry(); } string eventName = view.EventName ?? string.Empty; string displayDetection = BuildFallbackDetectionLabel(detection, eventName, view.Task, view.Opcode, view.EventId, view.CorrelationFlags); string source = view.Source; uint actor = view.ActorPid; bool isSocketEvent = view.Family == BlackbirdNative.IpcEtwFamilySocket || EventDetailFormatting.IsKernelNetworkEtwSource(view); if (isSocketEvent) { PerformancePaneHost.IngestNetworkView(view); } string socketOperation = string.IsNullOrWhiteSpace(view.Operation) ? eventName : view.Operation; if (string.IsNullOrWhiteSpace(socketOperation)) { socketOperation = $"OP{view.Opcode}"; } string timelineGroup = isSocketEvent ? "Sockets" : "BK-ETW"; string timelineSubtype = isSocketEvent ? socketOperation : eventName; string summary = isSocketEvent ? $"{socketOperation} pid={actor} task={view.Task} opcode={view.Opcode}" : (!string.IsNullOrWhiteSpace(displayDetection) ? $"{source}/{displayDetection} sev={view.Severity}" : $"{source}/{eventName} sev={view.Severity}"); string timelineSignature = $"{timelineGroup}|{eventName}|{actor}|{view.EventThreadId}|{summary}"; bool duplicateTimelineEvent = string.Equals(_lastEtwTimelineSignature, timelineSignature, StringComparison.OrdinalIgnoreCase) && (view.TimestampUtc - _lastEtwTimelineTimestampUtc).TotalMilliseconds <= 900; HeuristicEventView? heuristic = CreatePromotedHeuristic(view); if (heuristic != null) { heuristics.Add(heuristic); } bool persistView = ShouldPersistEtwView(view, keepEtw, relation != null, heuristic != null || signatureIntelFindings.Count > 0, apiTimelineEvent != null); string displayDetails = string.Empty; if (keepEtw || apiTimelineEvent != null || persistView) { EnsureEtwDisplayDetails(view); displayDetails = view.DisplayDetails; } if (persistView) { AppendEtwToCaptureStore(view); } if (keepEtw && !duplicateTimelineEvent) { int timelinePid = view.EventProcessId == 0 ? unchecked((int)actor) : unchecked((int)view.EventProcessId); timelineEvents.Add(new TelemetryEvent { TimestampUtc = view.TimestampUtc, PID = timelinePid, TID = unchecked((int)view.EventThreadId), Group = timelineGroup, SubType = timelineSubtype, Summary = summary, Details = displayDetails }); _lastEtwTimelineSignature = timelineSignature; _lastEtwTimelineTimestampUtc = view.TimestampUtc; } } if (etwRows.Count > 0) { EtwPaneHost.PushEvents(etwRows); _explorer.FirstOrDefault(x => x.Name == "ETW")?.PushPreviewValue(EtwPaneHost.TotalRawCount); SetExplorerHasData("ETW", EtwPaneHost.ItemCount > 0); } if (timelineEvents.Count > 0) { AppendEvents(timelineEvents); } if (memoryAttributions.Count > 0) { PerformancePaneHost.PushMemoryRegionAttributions(memoryAttributions); if (_currentSession != null) { for (int i = 0; i < memoryAttributions.Count; i += 1) { _currentSession.MemoryRegionAttributionHistory.Add( CloneMemoryRegionAttributionSample(memoryAttributions[i])); } if (_currentSession.MemoryRegionAttributionHistory.Count > 12_000) { _currentSession.MemoryRegionAttributionHistory.RemoveRange( 0, _currentSession.MemoryRegionAttributionHistory.Count - 12_000); } } } if (heuristics.Count > 0) { HeuristicsPaneHost.PushHeuristics(heuristics); bool extendedChanged = false; for (int i = 0; i < heuristics.Count; i += 1) { extendedChanged |= ObserveSignatureIntelActivity(heuristics[i]); } if (extendedChanged) { ScheduleExtendedActivitySnapshot(); } _explorer.FirstOrDefault(x => x.Name == "Heuristics") ?.PushPreviewValue(HeuristicsPaneHost.TotalRawCount); SetExplorerHasData("Heuristics", HeuristicsPaneHost.ItemCount > 0); DiagnosticsState.Increment("Heuristics", heuristics.Count); } if (relations.Count > 0) { ProcessRelationsPaneHost.PushRelations(relations); _explorer.FirstOrDefault(x => x.Name == "Process Relations") ?.PushPreviewValue(ProcessRelationsPaneHost.TotalRawCount); SetExplorerHasData("Process Relations", ProcessRelationsPaneHost.ItemCount > 0); } } private static bool IsTargetOutputEvent(BrokerEtwEventView view) => view.SourceId == BlackbirdNative.IpcEtwSourceBlackbird && view.EventName.Equals("TargetOutput", StringComparison.OrdinalIgnoreCase); private static bool IsTargetLifecycleEvent(BrokerEtwEventView view) => view.SourceId == BlackbirdNative.IpcEtwSourceBlackbird && view.EventName.Equals("TargetExit", StringComparison.OrdinalIgnoreCase); private static void ObserveTargetOutputEvent(BrokerEtwEventView view) { if (!IsTargetOutputEvent(view)) { return; } uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid; if (pid == 0 || pid > int.MaxValue) { return; } string stream = string.IsNullOrWhiteSpace(view.Operation) ? "stdout" : view.Operation.Trim(); string message = !string.IsNullOrWhiteSpace(view.CommandLine) ? view.CommandLine : view.Reason; if (string.IsNullOrWhiteSpace(message)) { return; } DebugConsoleService.WriteExternal($"TARGET/{stream}", unchecked((int)pid), message); } private void ObserveTargetLifecycleEvent(BrokerEtwEventView view) { if (!IsTargetLifecycleEvent(view)) { return; } uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid; if (pid == 0 || pid > int.MaxValue) { return; } string message = string.IsNullOrWhiteSpace(view.Reason) ? $"target exited status=0x{view.CreateStatus:X8}" : view.Reason; RememberTargetExitReason(pid, message); DebugConsoleService.WriteExternal("TARGET/lifecycle", unchecked((int)pid), message); } private void RememberTargetExitReason(uint pid, string reason) { if (pid == 0 || string.IsNullOrWhiteSpace(reason)) { return; } string normalized = reason.Trim(); _targetExitReasonByPid[pid] = normalized; if (_currentSession != null && _currentSession.Pid == unchecked((int)pid)) { _currentSession.TargetExitReason = normalized; } } private static bool ShouldPersistEtwView(BrokerEtwEventView view, bool keepEtw, bool hasRelation, bool hasPromotedHeuristic, bool hasApiTimelineEvent) { if (hasRelation || hasPromotedHeuristic || hasApiTimelineEvent) { return true; } if (!keepEtw) { return false; } if (!string.IsNullOrWhiteSpace(view.DetectionName) || view.Severity >= 4) { return true; } return view.Family is BlackbirdNative.IpcEtwFamilyThread or BlackbirdNative .IpcEtwFamilyApc or BlackbirdNative.IpcEtwFamilyUserHook or BlackbirdNative.IpcEtwFamilySocket; } private void UpdateHookPipelineDiagnostics(BrokerEtwEventView view) { if (view.Family == BlackbirdNative.IpcEtwFamilyUserHook && view.SourceId == BlackbirdNative.IpcEtwSourceUserHook) { long count = Interlocked.Increment(ref _usermodeHookEventCount); string kind = EventDetailFormatting.HookKindName(view.NotifyClass); string api = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : view.EventName; string label = !string.IsNullOrWhiteSpace(api) ? api : !string.IsNullOrWhiteSpace(kind) ? kind : "event"; DiagnosticsState.SetValue("Usermode Hooks", $"Active ({count} SR71 events, last={label})"); DiagnosticsState.SetValue("HookDLL->Controller IPC", "Ready (SR71 telemetry)"); DiagnosticsState.SetValue("HookDLL Hooks Set", $"OK ({kind})"); MarkSr71HookReadyFromTelemetry(); if (ShouldPromoteIntegrityStatus(DiagnosticsState.GetValue("Hook Integrity"))) { DiagnosticsState.SetValue("Hook Integrity", "OK"); } if (ShouldPromoteIntegrityStatus(DiagnosticsState.GetValue("AMSI Integrity"))) { DiagnosticsState.SetValue("AMSI Integrity", "OK"); } return; } if (EventDetailFormatting.IsKernelHookTelemetry(view)) { long count = Interlocked.Increment(ref _kernelHookEventCount); string api = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : view.EventName; DiagnosticsState.SetValue("Kernel Hooks", string.IsNullOrWhiteSpace(api) ? $"Active ({count} events)" : $"Active ({count} events, last={api})"); return; } if (EventDetailFormatting.IsUsermodeSensorTelemetry(view)) { string kind = EventDetailFormatting.HookKindName(view.NotifyClass); DiagnosticsState.SetValue("Usermode Hooks", $"Active ({kind})"); DiagnosticsState.SetValue("HookDLL->Controller IPC", "Ready (SR71 telemetry)"); DiagnosticsState.SetValue("HookDLL Hooks Set", $"OK ({kind})"); MarkSr71HookReadyFromTelemetry(); if (ShouldPromoteIntegrityStatus(DiagnosticsState.GetValue("Hook Integrity"))) { DiagnosticsState.SetValue("Hook Integrity", "OK"); } if (ShouldPromoteIntegrityStatus(DiagnosticsState.GetValue("AMSI Integrity"))) { DiagnosticsState.SetValue("AMSI Integrity", "OK"); } } } private static bool ShouldPromoteIntegrityStatus(string? current) { if (string.IsNullOrWhiteSpace(current)) { return true; } return current.Contains("Unknown", StringComparison.OrdinalIgnoreCase) || current.Contains("Awaiting", StringComparison.OrdinalIgnoreCase) || current.Contains("Review", StringComparison.OrdinalIgnoreCase) || current.Contains("Disabled (no usermode hooks)", StringComparison.OrdinalIgnoreCase); } private static void MarkSr71HookReadyFromTelemetry() { string? hookReady = DiagnosticsState.GetValue("SR71 Hook Ready"); if (string.IsNullOrWhiteSpace(hookReady) || hookReady.Contains("Awaiting", StringComparison.OrdinalIgnoreCase) || hookReady.Contains("DEGRADED", StringComparison.OrdinalIgnoreCase) || hookReady.Contains("missing", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("SR71 Hook Ready", "OK observed via SR71 telemetry"); } string? instrumentation = DiagnosticsState.GetValue("SR71 Instrumentation"); if (string.IsNullOrWhiteSpace(instrumentation) || instrumentation.Contains("Awaiting", StringComparison.OrdinalIgnoreCase) || instrumentation.Contains("DEGRADED", StringComparison.OrdinalIgnoreCase)) { DiagnosticsState.SetValue("SR71 Instrumentation", "OK observed via SR71 telemetry"); } } private static void TryAppendObservedModule(BrokerEtwEventView view, List rows) { string path = view.ImagePath ?? string.Empty; ulong baseAddress = view.ImageBase; ulong imageSize = view.ImageSize; if (string.IsNullOrWhiteSpace(path) && view.Family == BlackbirdNative.IpcEtwFamilyUserHook) { string apiName = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : view.EventName; if (apiName.Equals("LdrLoadDll", StringComparison.OrdinalIgnoreCase) || apiName.StartsWith("LoadLibrary", StringComparison.OrdinalIgnoreCase)) { Dictionary fields = BuildHookFieldMap(view); path = DecodeModuleHookName(apiName, view); baseAddress = FirstU64(fields, "handle", "module", "moduleHandle"); } } if (string.IsNullOrWhiteSpace(path)) { return; } rows.Add(new ModuleInfoRow { Name = ModuleNameFromPath(path), BaseAddress = baseAddress == 0 ? "observed" : $"0x{baseAddress:X}", Size = imageSize == 0 ? "observed" : FormatObservedBytes((long)Math.Min(imageSize, (ulong) long.MaxValue)), Path = path }); } private static string FormatObservedBytes(long bytes) { string[] units = { "B", "KB", "MB", "GB", "TB" }; double value = Math.Max(0, bytes); int unit = 0; while (value >= 1024 && unit < units.Length - 1) { value /= 1024; unit += 1; } return unit == 0 ? $"{bytes} B" : $"{value:0.0} {units[unit]}"; } private static string ModuleNameFromPath(string path) { string value = (path ?? string.Empty).Trim(); if (value.Length == 0) { return "unknown"; } int slash = Math.Max(value.LastIndexOf('\\'), value.LastIndexOf('/')); return slash >= 0 && slash + 1 < value.Length ? value[(slash + 1)..] : value; } private void PersistObservedHookStackSnapshot(BrokerEtwEventView view) { if (view.Family != BlackbirdNative.IpcEtwFamilyUserHook || view.SourceId != BlackbirdNative.IpcEtwSourceUserHook || view.StackCount == 0) { return; } uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid; uint tid = view.EventThreadId != 0 ? view.EventThreadId : view.ThreadId; if (pid == 0 || tid == 0 || pid > int.MaxValue || tid > int.MaxValue) { return; } DateTime capturedUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; string throttleKey = $"{pid}:{tid}"; if (_observedHookStackLastPersistByThread.TryGetValue(throttleKey, out DateTime lastPersistUtc) && capturedUtc < lastPersistUtc.AddMilliseconds(250)) { return; } ThreadStackSessionSnapshot? snapshot = CreateObservedHookStackSnapshot(view, capturedUtc); if (snapshot == null || snapshot.Frames.Count == 0) { return; } _observedHookStackLastPersistByThread[throttleKey] = capturedUtc; PersistThreadStackSnapshot(unchecked((int)pid), unchecked((int)tid), string.Empty, snapshot); } private void QueueThreadStackFallbackCapture(BrokerEtwEventView view) { if (view.StackCount != 0 || !ShouldCaptureThreadStackFallback(view)) { return; } uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid != 0 ? view.ActorPid : view.EventProcessId; uint tid = view.EventThreadId != 0 ? view.EventThreadId : view.ThreadId; if (pid == 0 || tid == 0 || pid > int.MaxValue || tid > int.MaxValue) { return; } DateTime capturedUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; string key = $"{pid}:{tid}"; if (_threadStackFallbackLastCaptureByThread.TryGetValue(key, out DateTime lastCaptureUtc) && capturedUtc < lastCaptureUtc.AddSeconds(6)) { return; } if (_pendingThreadStackFallbackCaptures.Count >= 32) { return; } if (!_pendingThreadStackFallbackCaptures.TryAdd(key, 0)) { return; } _threadStackFallbackLastCaptureByThread[key] = capturedUtc; int capturePid = unchecked((int)pid); int captureTid = unchecked((int)tid); _ = Task.Run(() => { ThreadStackResolveResult result = ThreadStackResolver.Resolve(capturePid, captureTid, string.Empty); return CreateThreadStackFallbackSnapshot(capturedUtc, result); }) .ContinueWith( task => { _pendingThreadStackFallbackCaptures.TryRemove(key, out _); if (task.Status != TaskStatus.RanToCompletion || task.Result == null || task.Result.Frames.Count == 0) { return; } Dispatcher.BeginInvoke( new Action( () => { PersistThreadStackSnapshot(capturePid, captureTid, string.Empty, task.Result); DebugConsoleService.WriteLocal( $"[STACK] captured fallback thread stack pid={capturePid} tid={captureTid} frames={task.Result.Frames.Count}"); }), DispatcherPriority.Background); }, TaskScheduler.Default); } private static bool ShouldCaptureThreadStackFallback(BrokerEtwEventView view) { if (view.Family != BlackbirdNative.IpcEtwFamilyUserHook) { return false; } string api = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : view.EventName; if (string.IsNullOrWhiteSpace(api)) { return false; } if (!api.StartsWith("Nt", StringComparison.OrdinalIgnoreCase) && !api.StartsWith("Zw", StringComparison.OrdinalIgnoreCase) && !api.StartsWith("Co", StringComparison.OrdinalIgnoreCase) && !api.Contains("Trace", StringComparison.OrdinalIgnoreCase)) { return false; } bool highSignal = view.Severity >= 4 || IsDirectSyscallDetection(view) || api.Equals("NtMapViewOfSection", StringComparison.OrdinalIgnoreCase) || api.Equals("ZwMapViewOfSection", StringComparison.OrdinalIgnoreCase) || api.Equals("NtCreateSection", StringComparison.OrdinalIgnoreCase) || api.Equals("ZwCreateSection", StringComparison.OrdinalIgnoreCase) || api.Equals("NtWriteVirtualMemory", StringComparison.OrdinalIgnoreCase) || api.Equals("NtProtectVirtualMemory", StringComparison.OrdinalIgnoreCase) || api.Equals("NtCreateThreadEx", StringComparison.OrdinalIgnoreCase); if (!highSignal) { return false; } return view.SourceId == BlackbirdNative.IpcEtwSourceBlackbird || view.SourceId == BlackbirdNative.IpcEtwSourceUserHook || EventDetailFormatting.IsKernelHookTelemetry(view); } private static ThreadStackSessionSnapshot? CreateThreadStackFallbackSnapshot(DateTime capturedUtc, ThreadStackResolveResult result) { if (result.Frames.Count == 0) { return null; } return new ThreadStackSessionSnapshot { CapturedAtUtc = capturedUtc, TebAddress = result.TebAddress, StackBase = result.StackBase, StackTop = result.StackTop, TebFlags = result.TebFlags, StackPointer = result.StackPointer, ContextSnapshot = CloneThreadContextSnapshot(result.ContextSnapshot), Frames = result.Frames.Select(CloneStackFrameRow).ToList() }; } private static StackFrameRow CloneStackFrameRow(StackFrameRow frame) => new() { Index = frame.Index, Address = frame.Address, Module = frame.Module, Symbol = frame.Symbol, InstructionPointerRaw = frame.InstructionPointerRaw, FramePointerRaw = frame.FramePointerRaw, FrameSpanBytes = frame.FrameSpanBytes, IsCurrent = frame.IsCurrent }; private static ThreadContextSnapshot? CloneThreadContextSnapshot(ThreadContextSnapshot? snapshot) { if (snapshot == null) { return null; } return new ThreadContextSnapshot { Rip = snapshot.Rip, Rsp = snapshot.Rsp, Rbp = snapshot.Rbp, Rax = snapshot.Rax, Rbx = snapshot.Rbx, Rcx = snapshot.Rcx, Rdx = snapshot.Rdx, Rsi = snapshot.Rsi, Rdi = snapshot.Rdi, R8 = snapshot.R8, R9 = snapshot.R9, R10 = snapshot.R10, R11 = snapshot.R11, R12 = snapshot.R12, R13 = snapshot.R13, R14 = snapshot.R14, R15 = snapshot.R15, Dr0 = snapshot.Dr0, Dr1 = snapshot.Dr1, Dr2 = snapshot.Dr2, Dr3 = snapshot.Dr3, Dr6 = snapshot.Dr6, Dr7 = snapshot.Dr7, EFlags = snapshot.EFlags }; } private static ThreadStackSessionSnapshot? CreateObservedHookStackSnapshot(BrokerEtwEventView view, DateTime capturedUtc) { ulong[] stack = view.Stack ?? Array.Empty(); int count = Math.Min(Math.Min((int)view.StackCount, stack.Length), BlackbirdNative.MaxIpcStackFrames); if (count <= 0) { return null; } Dictionary fields = BuildHookFieldMap(view); var frames = new List(count); for (int i = 0; i < count; i += 1) { ulong ip = stack[i]; if (ip == 0) { continue; } string symbol = ReadTrimmedField(fields, $"stack{i}Symbol"); string path = ReadTrimmedField(fields, $"stack{i}Path"); string module = ModuleNameFromPath(path); if (module.Equals("unknown", StringComparison.OrdinalIgnoreCase)) { module = ExtractModuleFromSymbol(symbol); } frames.Add(new StackFrameRow { Index = frames.Count, Address = $"0x{ip:X}", Module = module, Symbol = string.IsNullOrWhiteSpace(symbol) ? $"0x{ip:X}" : symbol, InstructionPointerRaw = ip, IsCurrent = frames.Count == 0 }); } if (frames.Count == 0) { return null; } ulong stackPointer = FirstU64(fields, "rsp", "sp", "stackPointer", "StackPointer"); return new ThreadStackSessionSnapshot { CapturedAtUtc = capturedUtc, StackPointer = stackPointer, Frames = frames }; } private static string ReadTrimmedField(IReadOnlyDictionary fields, string key) { return fields.TryGetValue(key, out string? value) && !string.IsNullOrWhiteSpace(value) ? value.Trim() : string.Empty; } private static string ExtractModuleFromSymbol(string symbol) { string value = (symbol ?? string.Empty).Trim(); int bang = value.IndexOf('!'); return bang > 0 ? value[..bang].Trim() : string.Empty; } private static uint DirectSyscallSeverityFloor(BrokerEtwEventView view) { bool highRisk = IsHighRiskIoctlAccess(view.DesiredAccess, (view.Flags & HandleFlagThreadObject) != 0); if ((view.Flags & HandleFlagSyscallExportMismatch) != 0) { return 6; } if (highRisk || (view.Flags & HandleFlagStackSpoofSuspect) != 0) { return 5; } if ((view.Flags & HandleFlagExecProtect) != 0 && (view.Flags & HandleFlagFromNtdll) == 0) { return 4; } return 3; } private static string BuildDirectSyscallDetectionName(BrokerEtwEventView view) { string syscallName = EventDetailFormatting.ResolveDirectSyscallApi(view.DesiredAccess, view.Flags); return string.IsNullOrWhiteSpace(syscallName) ? "DIRECT_SYSCALL_SUSPECT_HANDLE_OPERATION" : $"DIRECT_SYSCALL_SUSPECT_HANDLE_OPERATION [{syscallName}]"; } private static string BuildEtwDirectSyscallEvidenceText(BrokerEtwEventView view) { string syscallName = EventDetailFormatting.ResolveDirectSyscallApi(view.DesiredAccess, view.Flags); string syscallLabel = EventDetailFormatting.BuildDirectSyscallLabel( view.DesiredAccess, view.Flags, view.DeepSample, (int)view.DeepSampleSize); string accessDecoded = EventDetailFormatting.DescribeHandleAccess(view.DesiredAccess); string flagsDecoded = EventDetailFormatting.DescribeHandleFlags(view.Flags); string originModule = EventDetailFormatting.ModuleNameFromPath(view.OriginPath); string sampleHex = EventDetailFormatting.FormatSampleHex(view.DeepSample, (int)view.DeepSampleSize); string sampleDisasm = EventDetailFormatting.InferSampleDisassembly(view.DeepSample, (int)view.DeepSampleSize); string stack0 = view.StackCount > 0 && view.Stack.Length > 0 ? $"0x{view.Stack[0]:X}" : "n/a"; string stack1 = view.StackCount > 1 && view.Stack.Length > 1 ? $"0x{view.Stack[1]:X}" : "n/a"; return $"etwEvidence class={view.ClassName} syscallName={syscallName} syscallLabel={syscallLabel.Replace(' ', '_')} " + $"access=0x{view.DesiredAccess:X8} ({accessDecoded}) flags=0x{view.Flags:X8} ({flagsDecoded}) handleFlags=0x{view.Flags:X8} " + $"origin=0x{view.OriginAddress:X} protect=0x{view.OriginProtect:X8} module={originModule} " + $"path={view.OriginPath} allocationBase=0x{view.DeepAllocationBase:X} regionSize=0x{view.DeepRegionSize:X} " + $"regionProtect=0x{view.DeepRegionProtect:X8} ({EventDetailFormatting.DescribeMemoryProtection(view.DeepRegionProtect)}) " + $"stack0={stack0} stack1={stack1} deepSampleSize={view.DeepSampleSize} deepSample={sampleHex} sampleDisasmHint={sampleDisasm}"; } private HeuristicEventView? CreatePromotedHeuristic(BrokerEtwEventView view) { /* Never promote BK's own instrumentation pages as heuristics */ if (IsBlackbirdOwnEvent(view)) return null; string detection = view.DetectionName; if (string.IsNullOrWhiteSpace(detection) && IsDirectSyscallDetection(view)) { detection = BuildDirectSyscallDetectionName(view); } if (string.IsNullOrWhiteSpace(detection) || !ShouldPromoteHeuristic(view)) { return null; } uint actor = view.ActorPid; uint target = view.TargetPid; string reasonText = string.IsNullOrWhiteSpace(view.Reason) ? "" : view.Reason; uint sanitizedCorrFlags = view.CorrelationFlags & CorrelationIntentMask; string corrFlagsDecoded = EventDetailFormatting.DescribeCorrelationFlags(sanitizedCorrFlags); string corrAccessDecoded = EventDetailFormatting.DescribeHandleAccess(view.CorrelationAccessMask); string heuristicEvidence = ""; bool hasEvidence = TryGetHandleEvidence(actor, target, out IoctlParsedEvent evidence); if (hasEvidence) { heuristicEvidence = BuildHandleEvidenceText(evidence); } if (IsDirectSyscallDetection(view)) { if (hasEvidence && !ShouldKeepDirectSyscallHeuristicFromEvidence(evidence)) { return null; } if (!hasEvidence && view.Family == BlackbirdNative.IpcEtwFamilyHandle) { heuristicEvidence = BuildEtwDirectSyscallEvidenceText(view); } } string rawCorrFlagsSuffix = view.CorrelationFlags == sanitizedCorrFlags ? string.Empty : $"; rawCorrFlags=0x{view.CorrelationFlags:X8}"; uint severity = IsDirectSyscallDetection(view) ? Math.Max(view.Severity, DirectSyscallSeverityFloor(view)) : view.Severity; return new HeuristicEventView { TimestampUtc = view.TimestampUtc, LastSeenUtc = view.TimestampUtc, Severity = severity, DetectionName = detection, ActorPid = actor, TargetPid = target, Source = view.Source, EventName = view.EventName ?? string.Empty, CorrelationFlags = sanitizedCorrFlags, CorrelationAccessMask = view.CorrelationAccessMask, CorrelationAgeMs = view.CorrelationAgeMs, Reason = $"reason={reasonText}; corrFlags={corrFlagsDecoded}; corrAccess={corrAccessDecoded}; corrAgeMs={view.CorrelationAgeMs}{rawCorrFlagsSuffix}", Evidence = heuristicEvidence, RepeatCount = 1 }; } private static string BuildFallbackDetectionLabel(string detectionName, string eventName, ushort task, ushort opcode, ushort eventId, uint correlationFlags) { if (!string.IsNullOrWhiteSpace(detectionName) && !detectionName.Equals("UNKNOWN", StringComparison.OrdinalIgnoreCase)) { return detectionName; } string name = eventName?.Trim() ?? string.Empty; if (name.Equals("ThreadTelemetry", StringComparison.OrdinalIgnoreCase)) { if (correlationFlags != 0) { return $"THREAD_ACTIVITY[{EventDetailFormatting.DescribeCorrelationFlags(correlationFlags)}]"; } return "THREAD_ACTIVITY"; } if (name.Equals("HandleTelemetry", StringComparison.OrdinalIgnoreCase)) { return "HANDLE_ACTIVITY"; } if (name.Equals("ApcTelemetry", StringComparison.OrdinalIgnoreCase)) { return "APC_ACTIVITY"; } if (name.Equals("DetectionTelemetry", StringComparison.OrdinalIgnoreCase)) { return "DETECTION_UNSPECIFIED"; } if (!string.IsNullOrWhiteSpace(name) && name.EndsWith("Telemetry", StringComparison.OrdinalIgnoreCase)) { string baseName = name[..^ "Telemetry".Length].Trim(); if (!string.IsNullOrWhiteSpace(baseName)) { return $"{baseName.ToUpperInvariant()}_ACTIVITY"; } } if (!string.IsNullOrWhiteSpace(detectionName)) { return detectionName; } if (task == 0 && opcode == 0 && eventId == 0) { return "TELEMETRY"; } if (!string.IsNullOrWhiteSpace(name)) { return name; } return "UNCLASSIFIED_EVENT"; } private TelemetryEvent? HandleApiHookEvent(BrokerEtwEventView view) { if (!ShouldIncludeApiGraphView(view)) { return null; } string apiName = !string.IsNullOrWhiteSpace(view.EventName) ? view.EventName : (!string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : "unknown"); string sensorOrigin = EventDetailFormatting.ClassifyHookSensorOrigin(view); uint sourcePid = view.ActorPid != 0 ? view.ActorPid : view.ProcessPid; if (sourcePid == 0) { sourcePid = view.EventProcessId; } uint targetPid = view.TargetPid != 0 ? view.TargetPid : sourcePid; uint threadId = view.ThreadId != 0 ? view.ThreadId : view.EventThreadId; if (sourcePid == 0) { return null; } string callerOrigin = NormalizeApiCallerOrigin(view.CallerOriginLabel); Dictionary hookFields = BuildHookFieldMap(view); string originModule = NormalizeApiOriginModule(ResolveHookOriginModule(view, hookFields)); string key = BuildApiGraphKey(sourcePid, targetPid, threadId, apiName, sensorOrigin, callerOrigin, originModule); int currentHits; if (_apiGraphRowsByKey.TryGetValue(key, out ApiCallGraphRowSnapshot? existing)) { existing.Hits = Math.Max(1, existing.Hits + 1); existing.LastSeenUtc = view.TimestampUtc; existing.SensorOrigin = sensorOrigin; existing.CallerOrigin = callerOrigin; existing.OriginModule = originModule; currentHits = existing.Hits; } else { _apiGraphRowsByKey[key] = new ApiCallGraphRowSnapshot { ApiName = apiName, SensorOrigin = sensorOrigin, CallerOrigin = callerOrigin, OriginModule = originModule, SourcePid = sourcePid, TargetPid = targetPid, ThreadId = threadId, Hits = 1, FirstSeenUtc = view.TimestampUtc, LastSeenUtc = view.TimestampUtc }; currentHits = 1; } string rawReason = view.Reason ?? string.Empty; _apiGraphReasonByKey[key] = rawReason; (string decodedAction, string decodedDetail) = BuildApiDecodedAction(view, rawReason); _apiGraphActionByKey[key] = decodedAction; _apiGraphDecodedByKey[key] = decodedDetail; string frameSummary = BuildHookFrameSummary(view, hookFields); _apiGraphFramesByKey[key] = frameSummary; _apiGraphSensorByKey[key] = sensorOrigin; ApiCallStructuredFields structured = BuildApiCallStructuredFields(apiName, rawReason, decodedAction, view); ApiCallGraphRowSnapshot snapshot = _apiGraphRowsByKey[key]; snapshot.ActionLabel = decodedAction; snapshot.DetailFull = decodedDetail; snapshot.CallChainLabel = frameSummary; snapshot.ContextLabel = structured.Field2Value; snapshot.FlagsLabel = structured.Field4Value; ScheduleApiGraphSnapshot(); if (string.IsNullOrWhiteSpace(decodedDetail)) { return null; } if (!ShouldEmitApiTimelineEvent(key, currentHits, view.TimestampUtc)) { return null; } return new TelemetryEvent { TimestampUtc = view.TimestampUtc, PID = unchecked((int)sourcePid), TID = unchecked((int)threadId), Group = EventDetailFormatting.HookTimelineGroup(view), SubType = apiName, Summary = $"{apiName} [caller {sourcePid} target {targetPid} hits {currentHits}]", Details = decodedDetail }; } private bool ShouldIncludeApiGraphView(BrokerEtwEventView view) { return EventDetailFormatting.IsApiGraphCandidate(view); } private bool ShouldEmitApiTimelineEvent(string key, int hits, DateTime timestampUtc) { if (hits <= 1) { _apiGraphTimelineLastEmitByKey[key] = timestampUtc; return true; } if (!_apiGraphTimelineLastEmitByKey.TryGetValue(key, out DateTime lastEmitUtc)) { _apiGraphTimelineLastEmitByKey[key] = timestampUtc; return true; } bool milestone = (hits & (hits - 1)) == 0 || (hits % 32) == 0; bool elapsed = (timestampUtc - lastEmitUtc) >= ApiTimelineEmissionWindow; if (!milestone && !elapsed) { return false; } _apiGraphTimelineLastEmitByKey[key] = timestampUtc; return true; } private void PublishApiGraphSnapshot() { BackfillApiGraphFromEtwHistoryIfNeeded(); var snapshot = _apiGraphRowsByKey.Values.Select(static x => x.Clone()) .OrderByDescending(x => x.Hits) .ThenByDescending(x => x.LastSeenUtc) .Take(800) .ToList(); _apiGraphSnapshotRows.Clear(); _apiGraphSnapshotRows.AddRange(snapshot); RefreshApiViewPresentation(); } private void BackfillApiGraphFromEtwHistoryIfNeeded() { if (_apiGraphRowsByKey.Count != 0) { return; } IEnumerable sourceGroups = EtwPaneHost.ItemCount > 0 ? EtwPaneHost.SnapshotItems() : (_currentSession != null && _etwHistoryByPid.TryGetValue(_currentSession.Pid, out var history) ? history : Array.Empty()); foreach (GroupedEventDetailRow detail in sourceGroups.SelectMany(x => x.Details)) { ObserveApiGraphDetailFallback(detail); } } private void ObserveApiGraphDetailFallback(GroupedEventDetailRow detail) { if (detail == null || string.IsNullOrWhiteSpace(detail.Event) || !IsLikelyApiHookDetail(detail)) { return; } string apiName = detail.Event; string sensorOrigin = detail.Source.IndexOf("kernel", StringComparison.OrdinalIgnoreCase) >= 0 ? "Kernel Hook" : detail.Source.IndexOf("hook", StringComparison.OrdinalIgnoreCase) >= 0 || detail.Detection.StartsWith("USERMODE_", StringComparison.OrdinalIgnoreCase) ? "Usermode Hook" : "Unclassified"; string callerOrigin = "unknown"; string originModule = "unknown"; uint sourcePid = detail.ActorPid; uint targetPid = detail.TargetPid != 0 ? detail.TargetPid : detail.ActorPid; uint threadId = 0; _ = uint.TryParse(detail.EventTid, NumberStyles.Integer, CultureInfo.InvariantCulture, out threadId); string key = BuildApiGraphKey(sourcePid, targetPid, threadId, apiName, sensorOrigin, callerOrigin, originModule); if (_apiGraphRowsByKey.TryGetValue(key, out ApiCallGraphRowSnapshot? existing)) { existing.Hits = Math.Max(1, existing.Hits + Math.Max(1, detail.HitCount)); existing.LastSeenUtc = detail.TimestampUtc; return; } _apiGraphRowsByKey[key] = new ApiCallGraphRowSnapshot { ApiName = apiName, SensorOrigin = sensorOrigin, CallerOrigin = callerOrigin, OriginModule = originModule, ActionLabel = string.IsNullOrWhiteSpace(detail.Detection) ? apiName : detail.Detection, DetailFull = string.IsNullOrWhiteSpace(detail.Details) ? detail.ArgumentSummary : detail.Details, CallChainLabel = string.Empty, ContextLabel = detail.ArgumentSummary, FlagsLabel = string.IsNullOrWhiteSpace(detail.Flags) ? detail.Access : detail.Flags, SourcePid = sourcePid, TargetPid = targetPid, ThreadId = threadId, Hits = Math.Max(1, detail.HitCount), FirstSeenUtc = detail.TimestampUtc, LastSeenUtc = detail.TimestampUtc }; _apiGraphSensorByKey[key] = sensorOrigin; } private static bool IsLikelyApiHookDetail(GroupedEventDetailRow detail) { if (detail.Source.IndexOf("hook", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (detail.Detection.StartsWith("USERMODE_", StringComparison.OrdinalIgnoreCase)) { return !detail.Detection.Contains("INTEGRITY", StringComparison.OrdinalIgnoreCase); } return detail.Event.StartsWith("Nt", StringComparison.OrdinalIgnoreCase) || detail.Event.StartsWith("Zw", StringComparison.OrdinalIgnoreCase); } private bool ObserveExtendedActivity(BrokerEtwEventView view) { if (!TryBuildExtendedActivityRow(view, out ExtendedActivityRowSnapshot? row) || row == null) { return false; } string key = BuildExtendedActivityKey(row.TypeLabel, row.ActorLabel, row.TargetLabel, row.SubjectLabel, row.OperationLabel); if (_extendedRowsByKey.TryGetValue(key, out ExtendedActivityRowSnapshot? existing)) { existing.Hits = Math.Max(1, existing.Hits + 1); existing.LastSeenUtc = row.LastSeenUtc; existing.LastSeenLabel = row.LastSeenLabel; existing.DetailLabel = row.DetailLabel; return true; } _extendedRowsByKey[key] = row; return true; } private void ScheduleExtendedActivitySnapshot() { _extendedViewSnapshotDirty = true; _extendedViewRefreshTimer.Start(); } private void FlushExtendedActivitySnapshot() { if (!_extendedViewSnapshotDirty) { _extendedViewRefreshTimer.Stop(); return; } _extendedViewSnapshotDirty = false; PublishExtendedActivitySnapshot(); } private void PublishExtendedActivitySnapshot() { List snapshot = _extendedRowsByKey.Values.Select(static x => x.Clone()) .OrderByDescending(x => x.LastSeenUtc) .ThenByDescending(x => x.Hits) .Take(800) .ToList(); _extendedViewRows.ReplaceAll(snapshot); _extendedComRows.ReplaceAll(snapshot.Where(IsExtendedComRow).ToList()); _extendedEtwRows.ReplaceAll(snapshot.Where(IsExtendedEtwRow).ToList()); _extendedJobRows.ReplaceAll(snapshot.Where(IsExtendedJobRow).ToList()); _extendedYaraRows.ReplaceAll(snapshot.Where(IsExtendedYaraRow).ToList()); if (ExtendedViewSummaryBlock != null) { ExtendedViewSummaryBlock.Text = snapshot.Count == 0 ? "No extended activity yet" : $"Activities: {snapshot.Count} / Hits: {snapshot.Sum(x => x.Hits)}"; } } private static bool IsExtendedComRow(ExtendedActivityRowSnapshot row) { return row.TypeLabel.Contains("COM", StringComparison.OrdinalIgnoreCase) || row.TypeLabel.Contains("WMI", StringComparison.OrdinalIgnoreCase); } private static bool IsExtendedEtwRow(ExtendedActivityRowSnapshot row) { return row.TypeLabel.Contains("ETW", StringComparison.OrdinalIgnoreCase); } private static bool IsExtendedJobRow(ExtendedActivityRowSnapshot row) { return row.TypeLabel.Contains("Job", StringComparison.OrdinalIgnoreCase); } private static bool IsExtendedYaraRow(ExtendedActivityRowSnapshot row) { return row.TypeLabel.Contains("YARA", StringComparison.OrdinalIgnoreCase) || row.TypeLabel.Contains("SIGMA", StringComparison.OrdinalIgnoreCase) || row.TypeLabel.Contains("Rules", StringComparison.OrdinalIgnoreCase); } private bool TryBuildExtendedActivityRow(BrokerEtwEventView view, out ExtendedActivityRowSnapshot? row) { row = null; string detection = view.DetectionName ?? string.Empty; string type; string operation; switch (detection) { case "USERMODE_COM_INIT": type = "COM Init"; operation = "Initialize"; break; case "USERMODE_COM_SECURITY_INIT": type = "COM Security"; operation = "InitializeSecurity"; break; case "USERMODE_COM_INSTANCE_CREATE": type = "COM Activation"; operation = "CreateInstance"; break; case "USERMODE_WMI_ACTIVITY": type = "WMI"; operation = "Locator Activation"; break; case "USERMODE_ETW_PROVIDER_REGISTER": type = "ETW Provider"; operation = "Register"; break; case "USERMODE_ETW_PROVIDER_UNREGISTER": type = "ETW Provider"; operation = "Unregister"; break; case "USERMODE_ETW_SESSION_CONTROL": type = "ETW Session"; operation = "StartTrace"; break; case "USERMODE_ETW_SUBSCRIPTION": type = "ETW Subscription"; operation = "EnableTrace"; break; case "USERMODE_JOB_OBJECT_ACTIVITY": type = "Job Object"; operation = !string.IsNullOrWhiteSpace(view.EventName) ? view.EventName : "Activity"; break; default: if (!TryBuildGenericExtendedActivityLabels(view, out type, out operation)) { return false; } break; } uint actorPid = ResolveExtendedActorPid(view); uint targetPid = ResolveExtendedTargetPid(view, actorPid); string subject = ExtractExtendedSubject(view); row = new ExtendedActivityRowSnapshot { TypeLabel = type, ActorLabel = FormatApiProcessLabel(actorPid), TargetLabel = FormatApiProcessLabel(targetPid), SubjectLabel = subject, OperationLabel = operation, DetailLabel = FirstNonBlank(view.Reason, view.ArgumentSummary, view.Details), LastSeenUtc = view.TimestampUtc, LastSeenLabel = FormatApiRelativeAge(view.TimestampUtc), Hits = 1 }; return true; } private static bool TryBuildGenericExtendedActivityLabels(BrokerEtwEventView view, out string type, out string operation) { type = view.Family switch { BlackbirdNative.IpcEtwFamilyHandle => "Handle", BlackbirdNative.IpcEtwFamilyThread => "Thread", BlackbirdNative.IpcEtwFamilyProcess => "Process", BlackbirdNative.IpcEtwFamilyImage => "Image", BlackbirdNative.IpcEtwFamilyRegistry => "Registry", BlackbirdNative.IpcEtwFamilyApc => "APC", BlackbirdNative.IpcEtwFamilyDetection => "Detection", BlackbirdNative.IpcEtwFamilyThreatIntel => "Threat Intel", BlackbirdNative.IpcEtwFamilySocket => "Socket", BlackbirdNative.IpcEtwFamilyUserHook => "API", _ => string.IsNullOrWhiteSpace(view.Source) ? string.Empty : view.Source }; operation = FirstNonBlank(view.Operation, view.EventName, view.DetectionName, "Activity"); if (string.IsNullOrWhiteSpace(type)) { return false; } return view.Family == BlackbirdNative.IpcEtwFamilyUserHook || view.Family == BlackbirdNative.IpcEtwFamilySocket || view.Family == BlackbirdNative.IpcEtwFamilyRegistry || view.Family == BlackbirdNative.IpcEtwFamilyImage || view.Family == BlackbirdNative.IpcEtwFamilyProcess || view.Family == BlackbirdNative.IpcEtwFamilyThread || view.Family == BlackbirdNative.IpcEtwFamilyHandle || !string.IsNullOrWhiteSpace(view.DetectionName) || view.Severity >= 2; } private static uint ResolveExtendedActorPid(BrokerEtwEventView view) => FirstNonZero(view.ActorPid, view.CallerPid, view.CreatorPid, view.ProcessPid, view.EventProcessId); private static uint ResolveExtendedTargetPid(BrokerEtwEventView view, uint actorPid) => FirstNonZero(view.TargetPid, view.ExplicitTargetPid, view.ProcessPid, view.EventProcessId, actorPid); private static string ExtractExtendedSubject(BrokerEtwEventView view) { string reason = view.Reason ?? string.Empty; string[] tokens = new[] { "class=", "provider=", "name=", "mode=", "class=" }; foreach (string token in tokens) { int start = reason.IndexOf(token, StringComparison.OrdinalIgnoreCase); if (start < 0) { continue; } start += token.Length; int end = reason.IndexOf(' ', start); if (end < 0) { end = reason.Length; } string value = reason.Substring(start, end - start).Trim(); if (!string.IsNullOrWhiteSpace(value)) { return value; } } return FirstNonBlank(view.KeyPath, view.ValueName, view.ImagePath, view.OriginPath, view.CommandLine, view.ClassName, view.ArgumentSummary, view.EventName, view.DetectionName); } private static uint FirstNonZero(params uint[] values) { foreach (uint value in values) { if (value != 0) { return value; } } return 0; } private static string FirstNonBlank(params string?[] values) { foreach (string? value in values) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } } return string.Empty; } private static string BuildExtendedActivityKey(string type, string actor, string target, string subject, string operation) { return string.Join("|", type, actor, target, subject, operation); } private void RefreshApiViewPresentation() { string? selectedKey = (ApiViewDataGrid?.SelectedItem as ApiCallGraphMainRowView)?.GraphKey; bool apiViewVisible = _mainViewMode == MainInterfaceViewMode.Api && ApiViewBorder != null && ApiViewBorder.Visibility == Visibility.Visible; List sourceRows = _apiViewPresentationMode == ApiViewPresentationMode.ThreadTimeline ? BuildApiThreadTimelineRows(_apiGraphSnapshotRows) : BuildApiCallGraphRows(_apiGraphSnapshotRows); _apiViewSnapshotRows.Clear(); _apiViewSnapshotRows.AddRange(sourceRows); List filteredRows = ApplyApiViewFilters(sourceRows); _apiViewRows.ReplaceAll(filteredRows); if (ApiViewSummaryBlock != null) { string noun = _apiViewPresentationMode == ApiViewPresentationMode.ThreadTimeline ? "Thread/API Rows" : "Patterns"; ApiViewSummaryBlock.Text = sourceRows.Count == 0 ? "No API hook data yet" : filteredRows.Count == sourceRows.Count ? $"{noun}: {filteredRows.Count} / Calls: {filteredRows.Sum(x => x.Hits)}" : $"{noun}: {filteredRows.Count}/{sourceRows.Count} / Calls: {filteredRows.Sum(x => x.Hits)}"; } if (apiViewVisible && ApiViewGraphCanvas != null) { RenderApiPresentationCanvas(filteredRows, selectedKey); } DiagnosticsState.SetValue( "API Graph", $"{(_apiViewPresentationMode == ApiViewPresentationMode.ThreadTimeline ? "threads" : "patterns")}={filteredRows.Count}/{sourceRows.Count} visible={apiViewVisible}"); if (ApiViewDataGrid != null) { ApiCallGraphMainRowView? selected = null; if (!string.IsNullOrWhiteSpace(selectedKey)) { selected = _apiViewRows.FirstOrDefault( x => string.Equals(x.GraphKey, selectedKey, StringComparison.Ordinal)); } if (selected == null && _apiViewRows.Count > 0) { selected = _apiViewRows[0]; } ApiViewDataGrid.SelectedItem = selected; UpdateApiViewSelection(selected); } } private void RefreshApiGraphSelectionVisual() { if (_mainViewMode != MainInterfaceViewMode.Api || ApiViewBorder == null || ApiViewBorder.Visibility != Visibility.Visible || ApiViewGraphCanvas == null) { return; } string? selectedKey = (ApiViewDataGrid?.SelectedItem as ApiCallGraphMainRowView)?.GraphKey; List filteredRows = ApplyApiViewFilters(_apiViewSnapshotRows); RenderApiPresentationCanvas(filteredRows, selectedKey); } private List BuildApiCallGraphRows(IReadOnlyList snapshot) { int maxHits = Math.Max(1, snapshot.Count == 0 ? 1 : snapshot.Max(x => Math.Max(1, x.Hits))); var rows = new List(snapshot.Count); foreach (ApiCallGraphRowSnapshot row in snapshot) { uint target = row.TargetPid != 0 ? row.TargetPid : row.SourcePid; string sensor = string.IsNullOrWhiteSpace(row.SensorOrigin) ? "Unclassified" : row.SensorOrigin; string callerOrigin = NormalizeApiCallerOrigin(row.CallerOrigin); string originModule = NormalizeApiOriginModule(row.OriginModule); string key = BuildApiGraphKey(row.SourcePid, row.TargetPid, row.ThreadId, row.ApiName, sensor, callerOrigin, originModule); double heatPercent = Math.Clamp((row.Hits / (double)maxHits) * 100.0, 0.0, 100.0); rows.Add(new ApiCallGraphMainRowView { GraphKey = key, ThreadGroupKey = $"{row.SourcePid}|{row.ThreadId}", ViewModeKey = "call", ApiName = string.IsNullOrWhiteSpace(row.ApiName) ? "unknown" : row.ApiName, OriginModule = originModule, ActionLabel = row.ActionLabel, SensorLabel = sensor, CallerOriginKey = callerOrigin, CallerOriginLabel = GetApiCallerOriginDisplayLabel(callerOrigin, sensor), CallChainLabel = row.CallChainLabel, CallerOriginBackground = BuildApiCallerOriginBackground(callerOrigin), CallerOriginForeground = BuildApiCallerOriginForeground(callerOrigin), SensorBackground = BuildApiSensorBackground(sensor), SensorForeground = BuildApiSensorForeground(sensor), HeatTrackBackground = BuildApiHeatTrackBackground(sensor, callerOrigin), HeatFillBackground = BuildApiHeatFillBackground(sensor, callerOrigin), RowBackground = BuildApiRowBackground(sensor, callerOrigin), RowBorderBrush = BuildApiRowBorder(sensor, callerOrigin), SourceLabel = FormatApiProcessLabel(row.SourcePid), TargetLabel = FormatApiProcessLabel(target), ThreadLabel = row.ThreadId == 0 ? string.Empty : row.ThreadId.ToString(CultureInfo.InvariantCulture), SizeLabel = row.ContextLabel, ProtectLabel = row.FlagsLabel, Field2Label = "Context", Field4Label = "Flags", Hits = Math.Max(1, row.Hits), HeatPercent = heatPercent, ActivityFillWidth = Math.Round((heatPercent / 100.0) * 110.0), FirstSeen = FormatApiRelativeAge(row.FirstSeenUtc), AbsoluteFirstSeen = row.FirstSeenUtc == default ? string.Empty : row.FirstSeenUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture), FirstSeenUtc = row.FirstSeenUtc, LastSeen = FormatApiRelativeAge(row.LastSeenUtc), AbsoluteLastSeen = row.LastSeenUtc == default ? string.Empty : row.LastSeenUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture), LastSeenUtc = row.LastSeenUtc, DetailFull = row.DetailFull }); } return rows; } private List BuildApiThreadTimelineRows(IReadOnlyList snapshot) { var groups = snapshot .GroupBy( row => $"{row.SourcePid}|{row.TargetPid}|{row.ThreadId}|{NormalizeApiOriginModule(row.OriginModule)}|{row.ApiName}", StringComparer.Ordinal) .Select( group => { ApiCallGraphRowSnapshot latest = group.OrderByDescending(x => x.LastSeenUtc).First(); int hits = group.Sum(x => Math.Max(1, x.Hits)); DateTime firstSeenUtc = group.Where(x => x.FirstSeenUtc != default) .Select(x => x.FirstSeenUtc) .DefaultIfEmpty(latest.LastSeenUtc) .Min(); DateTime lastSeenUtc = group.Max(x => x.LastSeenUtc); string sensor = group.Select(x => x.SensorOrigin).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x)) ?? "Unclassified"; string callerOrigin = NormalizeApiCallerOrigin(latest.CallerOrigin); string originModule = NormalizeApiOriginModule(latest.OriginModule); return new { Key = $"thread|{latest.SourcePid}|{latest.TargetPid}|{latest.ThreadId}|{originModule}|{latest.ApiName}", ThreadGroupKey = $"{latest.SourcePid}|{latest.ThreadId}", Latest = latest, Hits = hits, FirstSeenUtc = firstSeenUtc, LastSeenUtc = lastSeenUtc, Sensor = sensor, CallerOrigin = callerOrigin, OriginModule = originModule, OperationSummary = BuildApiOperationSummary(group) }; }) .OrderBy(x => x.ThreadGroupKey, StringComparer.Ordinal) .ThenBy(x => x.FirstSeenUtc) .ThenBy(x => x.LastSeenUtc) .Take(600) .ToList(); int maxHits = Math.Max(1, groups.Count == 0 ? 1 : groups.Max(x => Math.Max(1, x.Hits))); var rows = new List(groups.Count); foreach (var group in groups) { ApiCallGraphRowSnapshot row = group.Latest; uint target = row.TargetPid != 0 ? row.TargetPid : row.SourcePid; double heatPercent = Math.Clamp((group.Hits / (double)maxHits) * 100.0, 0.0, 100.0); string threadLabel = BuildThreadTimelineLabel(row.SourcePid, row.ThreadId, group.FirstSeenUtc, group.LastSeenUtc); rows.Add(new ApiCallGraphMainRowView { GraphKey = group.Key, ThreadGroupKey = group.ThreadGroupKey, ViewModeKey = "thread", ApiName = string.IsNullOrWhiteSpace(row.ApiName) ? "unknown" : row.ApiName, OriginModule = group.OriginModule, ActionLabel = string.IsNullOrWhiteSpace(row.ActionLabel) ? "Call activity" : row.ActionLabel, SensorLabel = group.Sensor, CallerOriginKey = group.CallerOrigin, CallerOriginLabel = GetApiCallerOriginDisplayLabel(group.CallerOrigin, group.Sensor), CallChainLabel = BuildThreadTimelineCallChain(row, group.FirstSeenUtc, group.LastSeenUtc), CallerOriginBackground = BuildApiCallerOriginBackground(group.CallerOrigin), CallerOriginForeground = BuildApiCallerOriginForeground(group.CallerOrigin), SensorBackground = BuildApiSensorBackground(group.Sensor), SensorForeground = BuildApiSensorForeground(group.Sensor), HeatTrackBackground = BuildApiHeatTrackBackground(group.Sensor, group.CallerOrigin), HeatFillBackground = BuildApiHeatFillBackground(group.Sensor, group.CallerOrigin), RowBackground = BuildApiRowBackground(group.Sensor, group.CallerOrigin), RowBorderBrush = BuildApiRowBorder(group.Sensor, group.CallerOrigin), SourceLabel = FormatApiProcessLabel(row.SourcePid), TargetLabel = FormatApiProcessLabel(target), ThreadLabel = threadLabel, SizeLabel = group.OriginModule, ProtectLabel = group.OperationSummary, Field2Label = "Module", Field4Label = "Ops", Hits = group.Hits, HeatPercent = heatPercent, ActivityFillWidth = Math.Round((heatPercent / 100.0) * 110.0), FirstSeen = FormatApiRelativeAge(group.FirstSeenUtc), AbsoluteFirstSeen = group.FirstSeenUtc == default ? string.Empty : group.FirstSeenUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture), FirstSeenUtc = group.FirstSeenUtc, LastSeen = FormatApiRelativeAge(group.LastSeenUtc), AbsoluteLastSeen = group.LastSeenUtc == default ? string.Empty : group.LastSeenUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture), LastSeenUtc = group.LastSeenUtc, DetailFull = BuildThreadTimelineDetail(row, group.FirstSeenUtc, group.LastSeenUtc, group.Hits, group.OriginModule, group.OperationSummary) }); } return rows; } private void RenderApiPresentationCanvas(IReadOnlyList rows, string? selectedKey) { if (_apiViewPresentationMode == ApiViewPresentationMode.ThreadTimeline) { RenderApiThreadTimelineCanvas(rows, selectedKey); return; } var byKey = rows.ToDictionary(x => x.GraphKey, StringComparer.Ordinal); List snapshot = _apiGraphSnapshotRows .Where(x => byKey.ContainsKey(BuildApiGraphKey( x.SourcePid, x.TargetPid, x.ThreadId, x.ApiName, string.IsNullOrWhiteSpace(x.SensorOrigin) ? "Unclassified" : x.SensorOrigin, NormalizeApiCallerOrigin(x.CallerOrigin), NormalizeApiOriginModule(x.OriginModule)))) .OrderByDescending(x => x.Hits) .ThenByDescending(x => x.LastSeenUtc) .ToList(); RenderApiGraphCanvas(snapshot, selectedKey); } private string BuildThreadTimelineLabel(uint pid, uint threadId, DateTime firstSeenUtc, DateTime lastSeenUtc) { string tid = threadId == 0 ? "T?" : $"T{threadId}"; string lifetime = BuildThreadLifetimeSummary(pid, threadId, firstSeenUtc, lastSeenUtc); return string.IsNullOrWhiteSpace(lifetime) ? tid : $"{tid} · {lifetime}"; } private string BuildThreadLifetimeSummary(uint pid, uint threadId, DateTime firstSeenUtc, DateTime lastSeenUtc) { if (_currentSession == null || threadId == 0) { return firstSeenUtc != default && lastSeenUtc > firstSeenUtc ? $"{(lastSeenUtc - firstSeenUtc).TotalSeconds:0.0}s" : string.Empty; } IEnumerable history = _currentSession.ThreadLifecycleHistory.Where(x => x.ProcessPid == pid && x.ThreadId == threadId) .OrderBy(x => x.TimestampUtc); ThreadLifecycleEventSample? start = history.FirstOrDefault(x => x.EventKind.Equals("Start", StringComparison.OrdinalIgnoreCase)); ThreadLifecycleEventSample? exit = history.LastOrDefault(x => x.EventKind.Equals("Exit", StringComparison.OrdinalIgnoreCase)); DateTime startUtc = start?.TimestampUtc ?? firstSeenUtc; DateTime endUtc = exit?.TimestampUtc ?? lastSeenUtc; if (startUtc == default || endUtc == default || endUtc < startUtc) { return string.Empty; } string state = exit == null ? "live" : "exited"; return $"{(endUtc - startUtc).TotalSeconds:0.0}s {state}"; } private static string BuildApiOperationSummary(IEnumerable rows) { int query = 0, write = 0, exec = 0, map = 0, open = 0; foreach (ApiCallGraphRowSnapshot row in rows) { string text = $"{row.ApiName} {row.ActionLabel} {row.DetailFull}".ToLowerInvariant(); int weight = Math.Max(1, row.Hits); if (text.Contains("query") || text.Contains("read")) query += weight; if (text.Contains("write") || text.Contains("patch") || text.Contains("protect")) write += weight; if (text.Contains("thread") || text.Contains("apc") || text.Contains("execute")) exec += weight; if (text.Contains("map") || text.Contains("section") || text.Contains("image")) map += weight; if (text.Contains("open") || text.Contains("handle") || text.Contains("token")) open += weight; } var parts = new List(5); if (query != 0) parts.Add($"query:{query}"); if (write != 0) parts.Add($"write:{write}"); if (exec != 0) parts.Add($"exec:{exec}"); if (map != 0) parts.Add($"map:{map}"); if (open != 0) parts.Add($"open:{open}"); return parts.Count == 0 ? "mixed" : string.Join(" ", parts); } private string BuildThreadTimelineCallChain(ApiCallGraphRowSnapshot row, DateTime firstSeenUtc, DateTime lastSeenUtc) { string module = NormalizeApiOriginModule(row.OriginModule); string first = firstSeenUtc == default ? string.Empty : firstSeenUtc.ToLocalTime().ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); string last = lastSeenUtc == default ? string.Empty : lastSeenUtc.ToLocalTime().ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); string summary = $"Module {module}"; if (first.Length != 0 || last.Length != 0) { summary += $"{Environment.NewLine}Window {first} -> {last}"; } if (!string.IsNullOrWhiteSpace(row.CallChainLabel)) { summary += $"{Environment.NewLine}{Environment.NewLine}{row.CallChainLabel}"; } return summary.Trim(); } private string BuildThreadTimelineDetail(ApiCallGraphRowSnapshot row, DateTime firstSeenUtc, DateTime lastSeenUtc, int hits, string originModule, string operationSummary) { var sb = new StringBuilder(512); sb.AppendLine($"API: {row.ApiName}"); sb.AppendLine($"Module: {originModule}"); sb.AppendLine($"Hits: {hits.ToString(CultureInfo.InvariantCulture)}"); if (firstSeenUtc != default) sb.AppendLine($"First Seen: {firstSeenUtc.ToLocalTime():yyyy-MM-dd HH:mm:ss.fff}"); if (lastSeenUtc != default) sb.AppendLine($"Last Seen: {lastSeenUtc.ToLocalTime():yyyy-MM-dd HH:mm:ss.fff}"); sb.AppendLine($"Ops: {operationSummary}"); if (!string.IsNullOrWhiteSpace(row.ActionLabel)) sb.AppendLine().AppendLine(row.ActionLabel); if (!string.IsNullOrWhiteSpace(row.DetailFull)) sb.AppendLine().Append(row.DetailFull.Trim()); return sb.ToString().TrimEnd(); } private void RenderApiThreadTimelineCanvas(IReadOnlyList rows, string? selectedKey) { if (ApiViewGraphCanvas == null) { return; } ApiViewGraphCanvas.Children.Clear(); if (rows.Count == 0) { ApiViewGraphCanvas.Width = 540; ApiViewGraphCanvas.Height = 240; var empty = new TextBlock { Text = "No thread timeline yet", Foreground = (System.Windows.Media.Brush)FindResource("WinMutedTextBrush") }; Canvas.SetLeft(empty, 16); Canvas.SetTop(empty, 16); ApiViewGraphCanvas.Children.Add(empty); return; } DateTime minUtc = rows.Where(x => x.FirstSeenUtc != default) .Select(x => x.FirstSeenUtc) .DefaultIfEmpty(rows.Min(x => x.LastSeenUtc)) .Min(); DateTime maxUtc = rows.Select(x => x.LastSeenUtc).DefaultIfEmpty(minUtc).Max(); if (maxUtc <= minUtc) { maxUtc = minUtc.AddSeconds(1); } var lanes = rows.GroupBy(x => x.ThreadGroupKey, StringComparer.Ordinal) .Select(g => new { ThreadGroupKey = g.Key, Rows = g.OrderBy(x => x.FirstSeenUtc == default ? x.LastSeenUtc : x.FirstSeenUtc) .ThenBy(x => x.LastSeenUtc) .ThenByDescending(x => x.Hits) .Take(14) .ToList(), Hits = g.Sum(x => x.Hits), Label = g.First().ThreadLabel, First = g.Where(x => x.FirstSeenUtc != default) .Select(x => x.FirstSeenUtc) .DefaultIfEmpty(g.Min(x => x.LastSeenUtc)) .Min(), Last = g.Select(x => x.LastSeenUtc).DefaultIfEmpty(minUtc).Max() }) .OrderBy(x => x.First) .ThenBy(x => x.ThreadGroupKey, StringComparer.Ordinal) .Take(24) .ToList(); double left = 132; double right = 22; double top = 34; double rowHeight = 22; double laneHeaderHeight = 24; double laneGap = 10; double durationSeconds = Math.Max(1, (maxUtc - minUtc).TotalSeconds); double canvasWidth = Math.Max(920, Math.Min(2400, left + right + (durationSeconds * 90))); double width = canvasWidth - left - right; double canvasHeight = Math.Max( 260, top + lanes.Sum(x => laneHeaderHeight + Math.Max(1, x.Rows.Count) * rowHeight + laneGap) + 24); ApiViewGraphCanvas.Width = canvasWidth; ApiViewGraphCanvas.Height = canvasHeight; double ToX(DateTime utc) => left + ((utc - minUtc).TotalMilliseconds / (maxUtc - minUtc).TotalMilliseconds) * width; var axis = new System.Windows.Shapes.Line { X1 = left, X2 = left + width, Y1 = top - 10, Y2 = top - 10, Stroke = (System.Windows.Media.Brush)FindResource( "WinSubtleBorderBrush"), StrokeThickness = 1 }; ApiViewGraphCanvas.Children.Add(axis); int tickCount = Math.Min(6, Math.Max(2, (int)Math.Ceiling(width / 220.0))); for (int tick = 0; tick <= tickCount; tick += 1) { double ratio = tick / (double)tickCount; DateTime tickUtc = minUtc.AddMilliseconds((maxUtc - minUtc).TotalMilliseconds * ratio); double x = left + width * ratio; var tickLine = new System.Windows.Shapes.Line { X1 = x, X2 = x, Y1 = top - 14, Y2 = canvasHeight - 18, Stroke = (System.Windows.Media.Brush)FindResource("WinSubtleBorderBrush"), StrokeThickness = 1, Opacity = tick == 0 || tick == tickCount ? 0.55 : 0.22 }; ApiViewGraphCanvas.Children.Add(tickLine); var tickLabel = new TextBlock { Text = tickUtc.ToLocalTime().ToString("HH:mm:ss", CultureInfo.InvariantCulture), Foreground = (System.Windows.Media.Brush)FindResource("WinMutedTextBrush"), FontSize = 10 }; Canvas.SetLeft(tickLabel, Math.Max(left, Math.Min(left + width - 54, x - 22))); Canvas.SetTop(tickLabel, 4); ApiViewGraphCanvas.Children.Add(tickLabel); } double yCursor = top; for (int i = 0; i < lanes.Count; i += 1) { double y = yCursor; double laneBlockHeight = laneHeaderHeight + Math.Max(1, lanes[i].Rows.Count) * rowHeight; var label = new TextBlock { Text = lanes[i].Label, Width = left - 18, TextTrimming = TextTrimming.CharacterEllipsis, Foreground = (System.Windows.Media.Brush)FindResource("WinTextBrush"), FontWeight = FontWeights.SemiBold }; Canvas.SetLeft(label, 8); Canvas.SetTop(label, y + 2); ApiViewGraphCanvas.Children.Add(label); var laneBackground = new System.Windows.Shapes.Rectangle { Width = width, Height = laneBlockHeight, Fill = System.Windows.Media.Brushes.Transparent, Stroke = (System.Windows.Media.Brush)FindResource("WinSubtleBorderBrush"), StrokeThickness = 1, Opacity = 0.7 }; Canvas.SetLeft(laneBackground, left); Canvas.SetTop(laneBackground, y); ApiViewGraphCanvas.Children.Add(laneBackground); for (int rowIndex = 0; rowIndex < lanes[i].Rows.Count; rowIndex += 1) { ApiCallGraphMainRowView row = lanes[i].Rows[rowIndex]; double rowY = y + laneHeaderHeight + rowIndex * rowHeight; double x1 = ToX(row.FirstSeenUtc == default ? row.LastSeenUtc : row.FirstSeenUtc); double x2 = ToX(row.LastSeenUtc); if (x2 < x1) { (x1, x2) = (x2, x1); } double markerWidth = Math.Max(8, Math.Min(168, x2 - x1 + Math.Min(24, row.Hits * 1.6))); bool selected = string.Equals(row.GraphKey, selectedKey, StringComparison.Ordinal); var border = new Border { Width = markerWidth, Height = 18, CornerRadius = new CornerRadius(4), Background = row.HeatFillBackground, BorderBrush = row.RowBorderBrush, BorderThickness = new Thickness(selected ? 2 : 1), Opacity = selected ? 1.0 : 0.88, Child = new TextBlock { Text = $"{row.ApiName} [{row.OriginModule}]", Margin = new Thickness(6, 1, 6, 0), Foreground = (System.Windows.Media.Brush)FindResource("WinTextBrush"), FontSize = 11, TextTrimming = TextTrimming.CharacterEllipsis } }; Canvas.SetLeft(border, x1); Canvas.SetTop(border, rowY + 1); ApiViewGraphCanvas.Children.Add(border); } yCursor += laneBlockHeight + laneGap; } } private void EnsureEtwDisplayDetails(BrokerEtwEventView view) { if (!string.IsNullOrWhiteSpace(view.DisplayDetails)) { return; } view.DisplayDetails = BuildEtwDisplayDetail(view); } private List ApplyApiViewFilters(IEnumerable rows) { string callFilter = (ApiFilterCallBox?.Text ?? string.Empty).Trim(); string actionFilter = (ApiFilterActionBox?.Text ?? string.Empty).Trim(); string sensorFilter = ((ApiFilterSensorBox?.SelectedItem as ComboBoxItem)?.Content as string ?? "All Sensors").Trim(); string originFilter = ((ApiFilterOriginBox?.SelectedItem as ComboBoxItem)?.Content as string ?? "All Origins").Trim(); string callerFilter = (ApiFilterCallerBox?.Text ?? string.Empty).Trim(); string targetFilter = (ApiFilterTargetBox?.Text ?? string.Empty).Trim(); string threadFilter = (ApiFilterThreadBox?.Text ?? string.Empty).Trim(); string regionFilter = (ApiFilterRegionBox?.Text ?? string.Empty).Trim(); string protectFilter = (ApiFilterProtectBox?.Text ?? string.Empty).Trim(); string minHitsFilter = (ApiFilterMinHitsBox?.Text ?? string.Empty).Trim(); int minHits = 0; _ = int.TryParse(minHitsFilter, NumberStyles.Integer, CultureInfo.InvariantCulture, out minHits); bool Matches(string candidate, string filter) => string.IsNullOrWhiteSpace(filter) || (!string.IsNullOrWhiteSpace(candidate) && candidate.Contains(filter, StringComparison.OrdinalIgnoreCase)); bool MatchesSensor(string candidate) => string.IsNullOrWhiteSpace(sensorFilter) || sensorFilter.Equals("All Sensors", StringComparison.OrdinalIgnoreCase) || string.Equals(candidate, sensorFilter, StringComparison.OrdinalIgnoreCase); bool MatchesOrigin(string candidate) => string.IsNullOrWhiteSpace(originFilter) || originFilter.Equals("All Origins", StringComparison.OrdinalIgnoreCase) || string.Equals(candidate, originFilter, StringComparison.OrdinalIgnoreCase); return rows .Where(row => Matches(row.ApiName, callFilter) && Matches(row.ActionLabel, actionFilter) && MatchesSensor(row.SensorLabel) && MatchesOrigin(row.CallerOriginLabel) && Matches(row.SourceLabel, callerFilter) && Matches(row.TargetLabel, targetFilter) && Matches(row.ThreadLabel, threadFilter) && (Matches(row.BaseLabel, regionFilter) || Matches(row.SizeLabel, regionFilter) || Matches(row.AllocTypeLabel, regionFilter) || Matches(row.ProtectLabel, regionFilter)) && (Matches(row.ProtectLabel, protectFilter) || Matches(row.AllocTypeLabel, protectFilter)) && row.Hits >= Math.Max(0, minHits)) .OrderByDescending(x => x.Hits) .ThenByDescending(x => x.LastSeen, StringComparer.Ordinal) .ToList(); } private ApiCallStructuredFields BuildApiCallStructuredFields(string apiName, string rawReason, string decodedAction, BrokerEtwEventView? view) { Dictionary fields = ParseReasonFields(rawReason); string action = SummarizeApiReason(decodedAction); string field1Label = "Base"; string field2Label = "Context"; string field3Label = "Alloc Type"; string field4Label = "Flags"; string field1Value = string.Empty; string field2Value = string.Empty; string field3Value = string.Empty; string field4Value = string.Empty; if (apiName.Equals("NtAllocateVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "base", "c0", "a1"); ulong regionSize = FirstU64(fields, "size", "c1", "a3"); uint allocationType = (uint)FirstU64(fields, "allocType", "c2", "a4"); uint protect = (uint)FirstU64(fields, "protect", "c3", "a5"); field1Value = FormatObservedPointer(view, baseAddress); field2Value = regionSize == 0 ? string.Empty : $"base={(baseAddress == 0 ? "?" : FormatObservedPointer(view, baseAddress))} size=0x{regionSize:X}"; field3Value = allocationType == 0 ? string.Empty : $"0x{allocationType:X} {DescribeMemoryAllocationType(allocationType)}"; field4Value = protect == 0 ? string.Empty : $"0x{protect:X} {DescribeMemoryProtect(protect)}"; } else if (apiName.Equals("NtProtectVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "base", "c0", "a1"); ulong regionSize = FirstU64(fields, "size", "c1", "a2"); uint protect = (uint)FirstU64(fields, "newProtect", "c2", "a3"); field1Value = FormatObservedPointer(view, baseAddress); field2Value = regionSize == 0 ? string.Empty : $"base={(baseAddress == 0 ? "?" : FormatObservedPointer(view, baseAddress))} size=0x{regionSize:X}"; field4Value = protect == 0 ? string.Empty : $"0x{protect:X} {DescribeMemoryProtect(protect)}"; } else if (apiName.Equals("NtWriteVirtualMemory", StringComparison.OrdinalIgnoreCase) || apiName.Equals("NtReadVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "base", "c0", "a1"); ulong regionSize = FirstU64(fields, "size", "c1", "a3"); field1Value = FormatObservedPointer(view, baseAddress); field2Value = regionSize == 0 ? string.Empty : $"base={(baseAddress == 0 ? "?" : FormatObservedPointer(view, baseAddress))} size=0x{regionSize:X}"; } else if (apiName.Equals("LdrLoadDll", StringComparison.OrdinalIgnoreCase) || apiName.StartsWith("LoadLibrary", StringComparison.OrdinalIgnoreCase)) { field1Label = "Module"; field2Label = "Context"; field3Label = "Flags"; field4Label = "Result"; field1Value = DecodeModuleHookName(apiName, view); ulong handle = FirstU64(fields, "handle"); ulong flags = FirstU64(fields, "flags"); ulong status = FirstU64(fields, "status"); field2Value = handle == 0 ? (string.IsNullOrWhiteSpace(field1Value) ? string.Empty : field1Value) : $"{field1Value} handle=0x{handle:X}".Trim(); field3Value = flags == 0 ? "0x0" : $"0x{flags:X}"; field4Value = apiName.Equals("LdrLoadDll", StringComparison.OrdinalIgnoreCase) ? $"0x{status:X8}" : string.Empty; } else if (apiName.Equals("RtlAddFunctionTable", StringComparison.OrdinalIgnoreCase) || apiName.Equals("RtlInstallFunctionTableCallback", StringComparison.OrdinalIgnoreCase) || apiName.Equals("RtlDeleteFunctionTable", StringComparison.OrdinalIgnoreCase)) { field1Label = "Base"; field2Label = "Context"; field3Label = "Length"; field4Label = "Callback"; ulong baseAddress = FirstU64(fields, "baseAddress", "a2", "a1"); ulong length = FirstU64(fields, "length", "a2"); ulong callback = FirstU64(fields, "callback", "a3"); field1Value = FormatObservedPointer(view, baseAddress); field2Value = apiName.Equals("RtlDeleteFunctionTable", StringComparison.OrdinalIgnoreCase) ? $"table={FormatObservedPointer(view, FirstU64(fields, "table", "a0"))}" : $"table={FormatObservedPointer(view, FirstU64(fields, "tableId", "table", "a0"))}"; field3Value = length == 0 ? string.Empty : $"0x{length:X}"; field4Value = FormatObservedPointer(view, callback); } return new ApiCallStructuredFields { Action = string.IsNullOrWhiteSpace(action) ? apiName : action, Field1Label = field1Label, Field2Label = field2Label, Field3Label = field3Label, Field4Label = field4Label, Field1Value = field1Value, Field2Value = field2Value, Field3Value = field3Value, Field4Value = field4Value }; } private static string DecodeModuleHookName(string apiName, BrokerEtwEventView? view) { if (view == null || view.DeepSample == null || view.DeepSample.Length == 0 || view.DeepSampleSize == 0) { return string.Empty; } int sampleSize = Math.Min(view.DeepSample.Length, (int)view.DeepSampleSize); if (sampleSize <= 0) { return string.Empty; } if (apiName.Equals("LoadLibraryA", StringComparison.OrdinalIgnoreCase) || apiName.Equals("LoadLibraryExA", StringComparison.OrdinalIgnoreCase)) { int zeroIndex = Array.IndexOf(view.DeepSample, (byte)0, 0, sampleSize); int length = zeroIndex >= 0 ? zeroIndex : sampleSize; return length > 0 ? Encoding.ASCII.GetString(view.DeepSample, 0, length).Trim() : string.Empty; } int byteLength = sampleSize & ~1; if (byteLength <= 0) { return string.Empty; } string decoded = Encoding.Unicode.GetString(view.DeepSample, 0, byteLength); int nul = decoded.IndexOf('\0'); if (nul >= 0) { decoded = decoded[..nul]; } return decoded.Trim(); } private void RenderApiGraphCanvas(IReadOnlyList rows, string? selectedKey) { if (ApiViewGraphCanvas == null) { return; } ApiViewGraphCanvas.Children.Clear(); if (rows.Count == 0) { ApiViewGraphCanvas.Width = 540; ApiViewGraphCanvas.Height = 240; var empty = new System.Windows.Controls.TextBlock { Text = "No live call graph yet", Foreground = (System.Windows.Media.Brush)FindResource("WinMutedTextBrush") }; System.Windows.Controls.Canvas.SetLeft(empty, 16); System.Windows.Controls.Canvas.SetTop(empty, 16); ApiViewGraphCanvas.Children.Add(empty); return; } ApiCallGraphRowSnapshot? selectedRow = rows.FirstOrDefault( row => { string sensor = string.IsNullOrWhiteSpace(row.SensorOrigin) ? "Unclassified" : row.SensorOrigin; string callerOrigin = NormalizeApiCallerOrigin(row.CallerOrigin); string originModule = NormalizeApiOriginModule(row.OriginModule); string key = BuildApiGraphKey(row.SourcePid, row.TargetPid, row.ThreadId, row.ApiName, sensor, callerOrigin, originModule); return string.Equals(key, selectedKey, StringComparison.Ordinal); }); List visible = rows.OrderByDescending(x => x.Hits).ThenByDescending(x => x.LastSeenUtc).Take(12).ToList(); if (selectedRow != null && !visible.Contains(selectedRow)) { visible.Add(selectedRow); } var sourceNodes = visible.GroupBy(x => x.SourcePid) .Select(x => new { Pid = x.Key, Hits = x.Sum(y => Math.Max(1, y.Hits)), Selected = selectedRow != null && x.Any(y => y.SourcePid == selectedRow.SourcePid) }) .Where(x => x.Pid != 0) .OrderByDescending(x => x.Hits) .Take(6) .ToList(); var apiNodes = visible.GroupBy(x => string.IsNullOrWhiteSpace(x.ApiName) ? "unknown" : x.ApiName) .Select(x => new { Api = x.Key, Hits = x.Sum(y => Math.Max(1, y.Hits)), Selected = selectedRow != null && x.Any(y => string.Equals(y.ApiName, selectedRow.ApiName, StringComparison.OrdinalIgnoreCase)) }) .OrderByDescending(x => x.Hits) .Take(8) .ToList(); var targetNodes = visible.GroupBy(x => x.TargetPid != 0 ? x.TargetPid : x.SourcePid) .Select(x => new { Pid = x.Key, Hits = x.Sum(y => Math.Max(1, y.Hits)), Selected = selectedRow != null && x.Any(y => (y.TargetPid != 0 ? y.TargetPid : y.SourcePid) == (selectedRow.TargetPid != 0 ? selectedRow.TargetPid : selectedRow.SourcePid)) }) .Where(x => x.Pid != 0) .OrderByDescending(x => x.Hits) .Take(6) .ToList(); double canvasWidth = 920; double nodeWidth = 170; double apiNodeWidth = 188; double nodeHeight = 40; double leftX = 26; double middleX = 364; double rightX = canvasWidth - nodeWidth - 26; double topY = 42; double verticalSpacing = 56; double canvasHeight = Math.Max( 264, topY + Math.Max(sourceNodes.Count, Math.Max(apiNodes.Count, targetNodes.Count)) * verticalSpacing + 40); ApiViewGraphCanvas.Width = canvasWidth; ApiViewGraphCanvas.Height = canvasHeight; var sourcePositions = new Dictionary(); var apiPositions = new Dictionary(StringComparer.OrdinalIgnoreCase); var targetPositions = new Dictionary(); AddColumnLabel(leftX + (nodeWidth / 2.0), 14, "CALLERS"); AddColumnLabel(middleX + (apiNodeWidth / 2.0), 14, "APIS"); AddColumnLabel(rightX + (nodeWidth / 2.0), 14, "TARGETS"); for (int i = 0; i < sourceNodes.Count; i += 1) { double y = topY + (i * verticalSpacing); sourcePositions[sourceNodes[i].Pid] = new System.Windows.Point(leftX + nodeWidth, y + (nodeHeight / 2.0)); AddApiGraphProcessNode(leftX, y, nodeWidth, nodeHeight, sourceNodes[i].Pid, true, sourceNodes[i].Selected); } for (int i = 0; i < apiNodes.Count; i += 1) { double y = topY + (i * verticalSpacing); apiPositions[apiNodes[i].Api] = new System.Windows.Point(middleX + apiNodeWidth / 2.0, y + (nodeHeight / 2.0)); AddApiGraphApiNode(middleX, y, apiNodeWidth, nodeHeight, apiNodes[i].Api, apiNodes[i].Selected); } for (int i = 0; i < targetNodes.Count; i += 1) { double y = topY + (i * verticalSpacing); targetPositions[targetNodes[i].Pid] = new System.Windows.Point(rightX, y + (nodeHeight / 2.0)); AddApiGraphProcessNode(rightX, y, nodeWidth, nodeHeight, targetNodes[i].Pid, false, targetNodes[i].Selected); } int maxHits = Math.Max(1, visible.Max(x => Math.Max(1, x.Hits))); foreach (ApiCallGraphRowSnapshot row in visible) { uint sourcePid = row.SourcePid; uint targetPid = row.TargetPid != 0 ? row.TargetPid : row.SourcePid; string apiName = string.IsNullOrWhiteSpace(row.ApiName) ? "unknown" : row.ApiName; string sensor = string.IsNullOrWhiteSpace(row.SensorOrigin) ? "Unclassified" : row.SensorOrigin; string callerOrigin = NormalizeApiCallerOrigin(row.CallerOrigin); string originModule = NormalizeApiOriginModule(row.OriginModule); string rowKey = BuildApiGraphKey(row.SourcePid, row.TargetPid, row.ThreadId, row.ApiName, sensor, callerOrigin, originModule); bool isSelected = !string.IsNullOrWhiteSpace(selectedKey) && string.Equals(rowKey, selectedKey, StringComparison.Ordinal); if (!sourcePositions.TryGetValue(sourcePid, out System.Windows.Point sourcePoint) || !apiPositions.TryGetValue(apiName, out System.Windows.Point apiPoint) || !targetPositions.TryGetValue(targetPid, out System.Windows.Point end)) { continue; } double heat = Math.Clamp(row.Hits / (double)maxHits, 0.0, 1.0); var lineBrush = BuildApiGraphEdgeBrush(sensor, callerOrigin, heat); bool selfLoop = sourcePid == targetPid; DrawCurve(sourcePoint, new System.Windows.Point(middleX, apiPoint.Y), lineBrush, heat, isSelected, forward: true, selfLoop: false); DrawCurve(new System.Windows.Point(middleX + apiNodeWidth, apiPoint.Y), end, lineBrush, heat, isSelected, forward: true, selfLoop: selfLoop); } void AddColumnLabel(double centerX, double y, string label) { var block = new System.Windows.Controls.TextBlock { Text = label, FontSize = 10, FontWeight = FontWeights.SemiBold, Foreground = (System.Windows.Media.Brush)FindResource("WinMutedTextBrush") }; block.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); System.Windows.Controls.Canvas.SetLeft(block, centerX - (block.DesiredSize.Width / 2.0)); System.Windows.Controls.Canvas.SetTop(block, y); ApiViewGraphCanvas.Children.Add(block); } void DrawCurve(System.Windows.Point start, System.Windows.Point end, System.Windows.Media.Brush stroke, double heat, bool selected, bool forward, bool selfLoop) { var figure = new System.Windows.Media.PathFigure { StartPoint = start }; System.Windows.Point arrowBase; System.Windows.Vector direction; if (selfLoop) { double loopWidth = 64; double loopHeight = 28; var first = new System.Windows.Point(start.X + loopWidth * 0.35, start.Y - loopHeight); var second = new System.Windows.Point(end.X - loopWidth * 0.35, end.Y - loopHeight); figure.Segments.Add(new System.Windows.Media.BezierSegment(first, second, end, true)); arrowBase = second; direction = end - second; } else { double controlOffset = Math.Max(48, Math.Abs(end.X - start.X) * 0.32); var first = new System.Windows.Point(start.X + controlOffset, start.Y); var second = new System.Windows.Point(end.X - controlOffset, end.Y); figure.Segments.Add(new System.Windows.Media.BezierSegment(first, second, end, true)); arrowBase = second; direction = end - second; } var geometry = new System.Windows.Media.PathGeometry(); geometry.Figures.Add(figure); ApiViewGraphCanvas.Children.Add( new System.Windows.Shapes.Path { Data = geometry, Stroke = stroke, StrokeThickness = (selected ? 2.1 : 1.0) + (2.9 * heat), Opacity = selected ? 0.98 : 0.28 }); if (forward) { DrawArrowHead(end, direction, stroke, selected ? 0.98 : 0.45); } } void DrawArrowHead(System.Windows.Point tip, System.Windows.Vector direction, System.Windows.Media.Brush stroke, double opacity) { if (direction.LengthSquared < 1) { direction = new System.Windows.Vector(1, 0); } direction.Normalize(); System.Windows.Vector normal = new(-direction.Y, direction.X); const double arrowLength = 9; const double arrowWidth = 4.5; System.Windows.Point p1 = tip - (direction * arrowLength) + (normal * arrowWidth); System.Windows.Point p2 = tip - (direction * arrowLength) - (normal * arrowWidth); var geometry = new System.Windows.Media.PathGeometry(); var figure = new System.Windows.Media.PathFigure { StartPoint = tip, IsClosed = true, IsFilled = true }; figure.Segments.Add(new System.Windows.Media.LineSegment(p1, true)); figure.Segments.Add(new System.Windows.Media.LineSegment(p2, true)); geometry.Figures.Add(figure); ApiViewGraphCanvas.Children.Add(new System.Windows.Shapes.Path { Data = geometry, Fill = stroke, Stroke = stroke, Opacity = opacity }); } void AddApiGraphProcessNode(double x, double y, double width, double height, uint pid, bool sourceSide, bool selected) { string processName = GetApiGraphProcessName(pid); string title = string.IsNullOrWhiteSpace(processName) ? "Process" : processName; var border = new System.Windows.Controls.Border { Width = width, Height = height, CornerRadius = new CornerRadius(8), Background = new System.Windows.Media.SolidColorBrush( sourceSide ? (selected ? System.Windows.Media.Color.FromArgb(235, 22, 86, 140) : System.Windows.Media.Color.FromArgb(220, 17, 63, 103)) : (selected ? System.Windows.Media.Color.FromArgb(235, 140, 34, 38) : System.Windows.Media.Color.FromArgb(220, 110, 25, 28))), BorderBrush = new System.Windows.Media.SolidColorBrush( sourceSide ? (selected ? System.Windows.Media.Color.FromRgb(118, 203, 255) : System.Windows.Media.Color.FromRgb(80, 172, 255)) : (selected ? System.Windows.Media.Color.FromRgb(255, 139, 139) : System.Windows.Media.Color.FromRgb(255, 109, 109))), BorderThickness = new Thickness(selected ? 2 : 1), Opacity = selected ? 1.0 : 0.78, Child = new System.Windows.Controls .StackPanel { VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(10, 3, 10, 3), Children = { new System.Windows.Controls.TextBlock { Text = title, TextAlignment = TextAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, FontWeight = FontWeights.SemiBold, Foreground = new System.Windows.Media .SolidColorBrush(System.Windows.Media.Colors.White) }, new System.Windows.Controls.TextBlock { Text = $"PID {pid}", Margin = new Thickness(0, 1, 0, 0), TextAlignment = TextAlignment.Center, Foreground = new System.Windows.Media.SolidColorBrush(System .Windows .Media .Color .FromRgb( 220, 225, 230)) } } } }; System.Windows.Controls.Canvas.SetLeft(border, x); System.Windows.Controls.Canvas.SetTop(border, y); ApiViewGraphCanvas.Children.Add(border); } void AddApiGraphApiNode(double x, double y, double width, double height, string label, bool selected) { var border = new System.Windows.Controls.Border { Width = width, Height = height, CornerRadius = new CornerRadius(8), Background = new System.Windows.Media.SolidColorBrush( selected ? System.Windows.Media.Color.FromArgb(236, 42, 49, 58) : System.Windows.Media.Color.FromArgb(225, 32, 36, 43)), BorderBrush = new System.Windows.Media.SolidColorBrush( selected ? System.Windows.Media.Color.FromRgb(195, 205, 216) : System.Windows.Media.Color.FromRgb(128, 140, 154)), BorderThickness = new Thickness(selected ? 2 : 1), Opacity = selected ? 1.0 : 0.82, Child = new System.Windows.Controls.TextBlock { Text = label, Margin = new Thickness(10, 0, 10, 0), VerticalAlignment = VerticalAlignment.Center, TextAlignment = TextAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, FontWeight = FontWeights.SemiBold, Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.White) }, Cursor = System.Windows.Input.Cursors.Hand }; border.MouseLeftButtonDown += (_, e) => { SelectApiViewRowByApiName(label); if (e.ClickCount >= 2) { OpenApiInspector(label); } e.Handled = true; }; System.Windows.Controls.Canvas.SetLeft(border, x); System.Windows.Controls.Canvas.SetTop(border, y); ApiViewGraphCanvas.Children.Add(border); } } private static string SummarizeApiReason(string? reason) { if (string.IsNullOrWhiteSpace(reason)) { return string.Empty; } string compact = reason.Replace('\r', ' ').Replace('\n', ' ').Trim(); if (compact.Length <= 260) { return compact; } return compact[..260] + "..."; } private static string BuildApiGraphKey(uint sourcePid, uint targetPid, uint threadId, string apiName, string sensorOrigin, string callerOrigin, string originModule) => $"{sourcePid}|{targetPid}|{threadId}|{apiName}|{sensorOrigin}|{callerOrigin}|{originModule}"; private static string NormalizeApiCallerOrigin(string? callerOrigin) { string normalized = (callerOrigin ?? string.Empty).Trim().ToLowerInvariant(); return normalized.Length == 0 ? "unknown" : normalized; } private static string NormalizeApiOriginModule(string? originModule) { string normalized = (originModule ?? string.Empty).Trim(); return normalized.Length == 0 ? "unknown" : normalized; } private static string GetApiCallerOriginDisplayLabel(string callerOrigin, string? sensor = null) { return NormalizeApiCallerOrigin( callerOrigin) switch { "process-image" => "Process Image", "non-system-dll" => "Other DLL", "unbacked" => "Unbacked / Shellcode", "system" => "System DLL Chain", _ => !string.IsNullOrWhiteSpace(sensor) && sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase) ? "Kernel / Driver" : "Unknown" }; } private static bool IsInternalHookFrame(string? modulePath, string? frameText = null) { if (EventDetailFormatting.IsBlackbirdInternalPath(modulePath) || EventDetailFormatting.IsBlackbirdInternalModule(modulePath)) { return true; } string candidate = (frameText ?? string.Empty).Trim(); return candidate.StartsWith("SR71", StringComparison.OrdinalIgnoreCase) || candidate.StartsWith("J58", StringComparison.OrdinalIgnoreCase) || candidate.StartsWith("BlackbirdController", StringComparison.OrdinalIgnoreCase) || candidate.StartsWith("BlackbirdInterface", StringComparison.OrdinalIgnoreCase); } private static List BuildHookFrameList(BrokerEtwEventView view, IReadOnlyDictionary fields) { ulong[] stack = view.Stack ?? Array.Empty(); var frames = new List(BlackbirdNative.MaxIpcStackFrames); void AddFrameText(string frame) { if (string.IsNullOrWhiteSpace(frame)) { return; } frame = frame.Trim(); if (frames.Count == 0 || !string.Equals(frames[^1], frame, StringComparison.OrdinalIgnoreCase)) { frames.Add(frame); } } for (int i = 0; i < Math.Min((int)view.StackCount, BlackbirdNative.MaxIpcStackFrames); i += 1) { string symbolKey = $"stack{i}Symbol"; string pathKey = $"stack{i}Path"; if (fields.TryGetValue(symbolKey, out string? symbolValue) && !string.IsNullOrWhiteSpace(symbolValue)) { string frameText = symbolValue.Trim(); string? pathValue = null; if (fields.TryGetValue(pathKey, out string? resolvedPath) && !string.IsNullOrWhiteSpace(resolvedPath)) { pathValue = resolvedPath.Trim(); } if (IsInternalHookFrame(pathValue, frameText)) { continue; } if (!string.IsNullOrWhiteSpace(pathValue)) { string moduleName = EventDetailFormatting.ModuleNameFromPath(pathValue); if (!string.IsNullOrWhiteSpace(moduleName) && !moduleName.Equals("unknown", StringComparison.OrdinalIgnoreCase) && frameText.IndexOf(moduleName, StringComparison.OrdinalIgnoreCase) < 0) { frameText = $"{moduleName}!{frameText}"; } } AddFrameText(frameText); continue; } ulong rawIp = i < stack.Length ? stack[i] : 0; if (rawIp != 0) { AddFrameText($"0x{rawIp:X}"); } } if (frames.Count == 0 && fields.TryGetValue("originSymbol", out string? originSymbol) && !string.IsNullOrWhiteSpace(originSymbol) && !IsInternalHookFrame(null, originSymbol)) { AddFrameText(originSymbol); } return frames; } private static string BuildHookCallChainText(BrokerEtwEventView view, IReadOnlyDictionary fields) { List frames = BuildHookFrameList(view, fields); return frames.Count == 0 ? string.Empty : "CallChain: " + string.Join(" -> ", frames); } private string FormatObservedPointer(BrokerEtwEventView? view, ulong address, string? modulePath = null, ulong moduleBase = 0, ulong moduleSize = 0) { if (address == 0) { return string.Empty; } string directModuleText = EventDetailFormatting.FormatModuleRelativeAddress(modulePath, moduleBase, moduleSize, address); if (!string.IsNullOrWhiteSpace(directModuleText) && !string.Equals(directModuleText, $"0x{address:X}", StringComparison.OrdinalIgnoreCase)) { return directModuleText; } if (view != null) { uint targetPid = view.TargetPid != 0 ? view.TargetPid : (view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid); ulong processStartKey = ResolveObservedProcessStartKey(targetPid, view.ProcessStartKey); if (TryResolveKnownRegionForAddress(targetPid, processStartKey, address, out _, out ulong regionBase, out ulong regionSize, out string regionKind, out uint currentProtection)) { ulong pageBase = address & ~0xFFFUL; string regionText = $"{regionKind.ToLowerInvariant()}+0x{address - regionBase:X} [0x{address:X}; page 0x{pageBase:X}; region 0x{regionBase:X}+0x{regionSize:X}; {EventDetailFormatting.DescribeMemoryProtection(currentProtection)}]"; if (string.Equals(regionKind, "Image", StringComparison.OrdinalIgnoreCase)) { string imageText = EventDetailFormatting.FormatModuleRelativeAddress( view.ImagePath, view.ImageBase != 0 ? view.ImageBase : regionBase, view.ImageSize != 0 ? view.ImageSize : regionSize, address); if (!string.IsNullOrWhiteSpace(imageText) && !string.Equals(imageText, $"0x{address:X}", StringComparison.OrdinalIgnoreCase)) { return $"{imageText}; page 0x{pageBase:X}"; } } return regionText; } string viewImageText = EventDetailFormatting.FormatModuleRelativeAddress(view.ImagePath, view.ImageBase, view.ImageSize, address); if (!string.IsNullOrWhiteSpace(viewImageText) && !string.Equals(viewImageText, $"0x{address:X}", StringComparison.OrdinalIgnoreCase)) { return viewImageText; } } return $"0x{address:X}"; } private string BuildHookFrameSummary(BrokerEtwEventView view, IReadOnlyDictionary fields) => AppendThreadStackFallbackSummary(view, BuildHookFrameSummaryCore(view, fields)); private static string BuildHookFrameSummaryCore(BrokerEtwEventView view, IReadOnlyDictionary fields) { var sb = new StringBuilder(512); string sensor = EventDetailFormatting.ClassifyHookSensorOrigin(view); string origin = GetApiCallerOriginDisplayLabel(NormalizeApiCallerOrigin(view.CallerOriginLabel), sensor); string immediate = EventDetailFormatting.HookImmediateCallerLabel(view.Flags); string deepOrigin = EventDetailFormatting.HookDeepOriginLabel(view.Flags); string sr71Component = EventDetailFormatting.HookComponentLabel(view.Flags); string originModule = ResolveHookOriginModule(view, fields); string resolvedReturnAddress = ResolveHookReturnAddressLabel(view, fields, originModule); bool kernelCaller = (view.Flags & BlackbirdNative.IpcEtwFlagHookKernelCaller) != 0; bool userCaller = (view.Flags & BlackbirdNative.IpcEtwFlagHookUserCaller) != 0; bool currentTarget = IsHookCurrentProcessTarget(view); bool imageSection = IsHookImageSection(view); bool containsOwnModule = EventDetailFormatting.HookTraceContainsOwnModule(view.Flags); bool returnAddressResolved = !string.IsNullOrWhiteSpace(resolvedReturnAddress); bool returnAddressPresentInStack = false; ulong[] stack = view.Stack ?? Array.Empty(); int compareFrames = Math.Min((int)view.StackCount, stack.Length); for (int i = 0; i < compareFrames; i += 1) { if (stack[i] == view.OriginAddress && view.OriginAddress != 0) { returnAddressPresentInStack = true; break; } } sb.Append("Origin: ").AppendLine(origin); if (kernelCaller || userCaller) { sb.Append("Caller Mode: ").AppendLine(kernelCaller ? "Kernel" : "User"); } sb.Append("Target Scope: ").AppendLine(currentTarget ? "Current Process" : "External / Cross-Process"); sb.Append("Memory Backing: ").AppendLine(imageSection ? "Image-backed" : "Private / Non-image"); if (!string.IsNullOrWhiteSpace(sr71Component)) { sb.Append("SR71 Component: ").AppendLine(sr71Component); } sb.Append("SR71 Frames Present: ").AppendLine(containsOwnModule ? "yes" : "no"); if (!string.IsNullOrWhiteSpace(immediate) && !immediate.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) { sb.Append("Immediate Caller: ").AppendLine(immediate); } if (!string.IsNullOrWhiteSpace(deepOrigin) && !deepOrigin.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) { sb.Append("Deep Origin: ").AppendLine(deepOrigin); } if (!string.IsNullOrWhiteSpace(originModule)) { sb.Append("Origin Module: ").AppendLine(originModule); } if (view.OriginAddress != 0) { sb.Append("Return Address: ") .AppendLine(!string.IsNullOrWhiteSpace(resolvedReturnAddress) ? resolvedReturnAddress : $"0x{view.OriginAddress.ToString("X", CultureInfo.InvariantCulture)}"); sb.Append("Return Address Resolved: ").AppendLine(returnAddressResolved ? "yes" : "no"); sb.Append("Return Address In Stack: ").AppendLine(returnAddressPresentInStack ? "yes" : "no"); } sb.Append("Stack Frames Captured: ") .Append(view.StackCount.ToString(CultureInfo.InvariantCulture)) .AppendLine(); if (fields.TryGetValue("startSymbol", out string? startSymbol) && !string.IsNullOrWhiteSpace(startSymbol)) { sb.Append("Start Routine: ").AppendLine(startSymbol); } List frames = BuildHookFrameList(view, fields); if (frames.Count > 0) { sb.AppendLine("Frames:"); for (int i = 0; i < frames.Count; i += 1) { sb.Append(" ").Append(i + 1).Append(". ").AppendLine(frames[i]); string pathKey = $"stack{i}Path"; if (fields.TryGetValue(pathKey, out string? framePath) && !string.IsNullOrWhiteSpace(framePath)) { sb.Append(" ").AppendLine(framePath.Trim()); } } } if (!string.IsNullOrWhiteSpace(view.OriginPath)) { sb.Append("Origin Path: ").AppendLine(view.OriginPath.Trim()); } return sb.ToString().TrimEnd(); } private string AppendThreadStackFallbackSummary(BrokerEtwEventView view, string summary) { if (view.StackCount != 0) { return summary; } string fallback = BuildThreadStackFallbackSummary(view); if (string.IsNullOrWhiteSpace(fallback)) { string source = EventDetailFormatting.IsKernelHookTelemetry(view) || view.SourceId == BlackbirdNative.IpcEtwSourceBlackbird ? "kernel/driver telemetry did not include user frames" : "hook telemetry did not include frames"; string note = $"Thread Stack Fallback: pending or unavailable ({source})"; return string.IsNullOrWhiteSpace(summary) ? note : $"{summary}{Environment.NewLine}{note}"; } return string.IsNullOrWhiteSpace(summary) ? fallback : $"{summary}{Environment.NewLine}{fallback}"; } private string BuildThreadStackFallbackSummary(BrokerEtwEventView view) { uint pid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid != 0 ? view.ActorPid : view.EventProcessId; uint tid = view.EventThreadId != 0 ? view.EventThreadId : view.ThreadId; if (pid == 0 || tid == 0 || pid > int.MaxValue || tid > int.MaxValue) { return string.Empty; } DateTime observedUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; IReadOnlyList history = GetThreadStackHistory(unchecked((int)pid), unchecked((int)tid), string.Empty); ThreadStackSessionSnapshot? snapshot = history.Where(x => x.Frames.Count > 0) .OrderBy(x => Math.Abs((x.CapturedAtUtc - observedUtc).TotalMilliseconds)) .FirstOrDefault(); if (snapshot == null) { return string.Empty; } double deltaMs = Math.Abs((snapshot.CapturedAtUtc - observedUtc).TotalMilliseconds); if (deltaMs > 5000) { return string.Empty; } var sb = new StringBuilder(512); sb.Append("Thread Stack Fallback: ") .Append(snapshot.Frames.Count.ToString(CultureInfo.InvariantCulture)) .Append(" frames from Thread Stack window history, deltaMs=") .Append(deltaMs.ToString("0", CultureInfo.InvariantCulture)) .AppendLine(); int frameCount = Math.Min(12, snapshot.Frames.Count); for (int i = 0; i < frameCount; i += 1) { StackFrameRow frame = snapshot.Frames[i]; string label = !string.IsNullOrWhiteSpace(frame.Symbol) ? frame.Symbol : !string.IsNullOrWhiteSpace(frame.Address) ? frame.Address : $"0x{frame.InstructionPointerRaw:X}"; if (!string.IsNullOrWhiteSpace(frame.Module) && label.IndexOf(frame.Module, StringComparison.OrdinalIgnoreCase) < 0) { label = $"{frame.Module}!{label}"; } sb.Append(" ").Append(i + 1).Append(". ").AppendLine(label); } return sb.ToString().TrimEnd(); } private static string ResolveHookOriginModule(BrokerEtwEventView view, IReadOnlyDictionary fields) { string originModule = EventDetailFormatting.ModuleNameFromPath(view.OriginPath); if (!string.Equals(originModule, "unknown", StringComparison.OrdinalIgnoreCase) && !EventDetailFormatting.IsBlackbirdInternalModule(originModule) && !EventDetailFormatting.IsBlackbirdInternalPath(view.OriginPath)) { return originModule; } if (fields.TryGetValue("originSymbol", out string? originSymbol) && !string.IsNullOrWhiteSpace(originSymbol)) { string trimmed = originSymbol.Trim(); int plus = trimmed.IndexOf('+'); if (plus > 0) { trimmed = trimmed[..plus].Trim(); } if (!IsInternalHookFrame(null, trimmed)) { return trimmed; } } for (int i = 0; i < Math.Min((int)view.StackCount, BlackbirdNative.MaxIpcStackFrames); i += 1) { string pathKey = $"stack{i}Path"; string symbolKey = $"stack{i}Symbol"; string? pathValue = null; if (fields.TryGetValue(pathKey, out string? resolvedPath) && !string.IsNullOrWhiteSpace(resolvedPath)) { pathValue = resolvedPath.Trim(); if (!IsInternalHookFrame(pathValue)) { string moduleFromPath = EventDetailFormatting.ModuleNameFromPath(pathValue); if (!string.Equals(moduleFromPath, "unknown", StringComparison.OrdinalIgnoreCase)) { return moduleFromPath; } } } if (fields.TryGetValue(symbolKey, out string? symbolValue) && !string.IsNullOrWhiteSpace(symbolValue) && !IsInternalHookFrame(pathValue, symbolValue)) { string trimmed = symbolValue.Trim(); int plus = trimmed.IndexOf('+'); if (plus > 0) { trimmed = trimmed[..plus].Trim(); } if (!string.IsNullOrWhiteSpace(trimmed)) { return trimmed; } } } return originModule; } private static string ResolveHookReturnAddressLabel(BrokerEtwEventView view, IReadOnlyDictionary fields, string originModule) { ulong[] stack = view.Stack ?? Array.Empty(); if (fields.TryGetValue("originSymbol", out string? originSymbol) && !string.IsNullOrWhiteSpace(originSymbol)) { string trimmed = originSymbol.Trim(); if (!IsInternalHookFrame(null, trimmed)) { return trimmed; } } for (int i = 0; i < Math.Min((int)view.StackCount, BlackbirdNative.MaxIpcStackFrames); i += 1) { if (stack.Length <= i || stack[i] != view.OriginAddress) { continue; } string symbolKey = $"stack{i}Symbol"; if (fields.TryGetValue(symbolKey, out string? stackSymbol) && !string.IsNullOrWhiteSpace(stackSymbol)) { return stackSymbol.Trim(); } } if (!string.IsNullOrWhiteSpace(originModule) && !originModule.Equals("unknown", StringComparison.OrdinalIgnoreCase) && view.OriginAddress != 0) { return $"{originModule}!0x{view.OriginAddress:X}"; } return string.Empty; } private static string BuildHookFrameSummary(IReadOnlyDictionary fields) { var view = new BrokerEtwEventView(); if (fields.TryGetValue("originPath", out string? originPath)) { view.OriginPath = originPath; } if (fields.TryGetValue("kind", out string? kind) && string.Equals(kind, "kernel_ntapi", StringComparison.OrdinalIgnoreCase)) { view.Source = "BK"; view.SourceId = BlackbirdNative.IpcEtwSourceBlackbird; view.Family = BlackbirdNative.IpcEtwFamilyUserHook; } return BuildHookFrameSummaryCore(view, fields); } private string FormatApiProcessLabel(uint pid) { if (pid == 0) { return string.Empty; } string processName = GetApiGraphProcessName(pid); return string.IsNullOrWhiteSpace(processName) ? pid.ToString(CultureInfo.InvariantCulture) : $"{processName} ({pid.ToString(CultureInfo.InvariantCulture)})"; } private static string FormatApiRelativeAge(DateTime lastSeenUtc) { if (lastSeenUtc == default) { return string.Empty; } TimeSpan age = DateTime.UtcNow - lastSeenUtc; if (age < TimeSpan.Zero) { age = TimeSpan.Zero; } if (age.TotalSeconds >= 1) { return $"{age.TotalSeconds:0.0}s ago"; } return $"{Math.Max(0, age.TotalMilliseconds):0} ms ago"; } private (string Action, string Detail) BuildApiDecodedAction(BrokerEtwEventView view, string rawReason) { string apiName = !string.IsNullOrWhiteSpace(view.Operation) ? view.Operation : view.EventName; if (string.IsNullOrWhiteSpace(apiName)) { apiName = "unknown"; } Dictionary fields = BuildHookFieldMap(view); string action = BuildGenericApiActionLabel(apiName, fields); string detail; string argumentText = BuildResolvedHookArgumentsText(apiName, view, fields); bool hasExecutionContext = TryDescribeHookExecutionContext(view, fields, out string contextHeadline, out string contextDetail); if (TryBuildMemoryAction(apiName, view, fields, out string memoryAction, out string memoryDetail)) { action = memoryAction; string frameSummary = BuildHookFrameSummary(view, fields); var detailBuilder = new StringBuilder(memoryDetail.TrimEnd()); if (!string.IsNullOrWhiteSpace(frameSummary)) { detailBuilder.AppendLine().AppendLine().Append(frameSummary); } if (!string.IsNullOrWhiteSpace(argumentText)) { detailBuilder.AppendLine().AppendLine().Append(argumentText); } detail = detailBuilder.ToString(); if (hasExecutionContext) { if (contextHeadline.Equals("Loader / Image Mapping", StringComparison.OrdinalIgnoreCase)) { action += " [loader]"; } else if (contextHeadline.Equals("Kernel Caller", StringComparison.OrdinalIgnoreCase)) { action += " [kernel]"; } detail = $"{contextHeadline}{Environment.NewLine}{contextDetail}{Environment.NewLine}{Environment.NewLine}{detail}"; } } else { var sb = new StringBuilder(512); sb.AppendLine(action); if (hasExecutionContext) { if (contextHeadline.Equals("Loader / Image Mapping", StringComparison.OrdinalIgnoreCase)) { action += " [loader]"; } else if (contextHeadline.Equals("Kernel Caller", StringComparison.OrdinalIgnoreCase)) { action += " [kernel]"; } else if (contextHeadline.Contains("Startup", StringComparison.OrdinalIgnoreCase)) { action += " [startup]"; } sb.AppendLine().AppendLine(contextHeadline); sb.AppendLine(contextDetail); } string frameSummary = BuildHookFrameSummary(view, fields); if (!string.IsNullOrWhiteSpace(frameSummary)) { sb.AppendLine().AppendLine(frameSummary); } if (!string.IsNullOrWhiteSpace(argumentText)) { sb.AppendLine().AppendLine(argumentText); } string contextText = BuildGenericEtwDisplayDetail(view, fields, includeHeadline: false); if (!string.IsNullOrWhiteSpace(contextText)) { sb.AppendLine().Append(contextText); } detail = sb.ToString().Trim(); } return (action, detail); } private bool TryBuildMemoryAction(string apiName, BrokerEtwEventView view, IReadOnlyDictionary fields, out string action, out string detail) { action = string.Empty; detail = string.Empty; DateTime now = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc; if (apiName.Equals("NtAllocateVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "base", "c0", "a1"); ulong regionSize = FirstU64(fields, "size", "c1", "a3"); ulong allocationType = FirstU64(fields, "allocType", "c2", "a4"); uint protect = (uint)FirstU64(fields, "protect", "c3", "a5"); ulong page = baseAddress & ~0xFFFUL; if (page != 0) { ApiMemoryPageSignal state = GetOrCreateApiMemoryPageSignal(page); if (protect != 0) { state.Protect = protect; state.LastProtectChangeUtc = now; } } string protectLabel = DescribeMemoryProtect(protect); (string contextActionSuffix, string contextDetail) = DescribeMemoryRegionContext(view, baseAddress, regionSize); action = $"Allocates 0x{regionSize:X} bytes at 0x{baseAddress:X} ({protectLabel}){contextActionSuffix}"; detail = $"Action: memory.alloc\n" + $"API: {apiName}\n" + $"Base: 0x{baseAddress:X}\n" + $"Size: 0x{regionSize:X}\n" + $"AllocType: 0x{allocationType:X} ({DescribeMemoryAllocationType((uint)allocationType)})\n" + $"Protect: 0x{protect:X} ({protectLabel})\n" + contextDetail.TrimEnd(); return true; } if (apiName.Equals("NtProtectVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "base", "c0", "a1"); ulong regionSize = FirstU64(fields, "size", "c1", "a2"); uint newProtect = (uint)FirstU64(fields, "newProtect", "c2", "a3"); ulong page = baseAddress & ~0xFFFUL; ApiMemoryPageSignal state = GetOrCreateApiMemoryPageSignal(page); bool protectChanged = state.Protect != 0 && newProtect != 0 && state.Protect != newProtect; bool rapidFlip = false; if (protectChanged) { state.ProtectFlipCount += 1; rapidFlip = state.LastProtectChangeUtc != default && (now - state.LastProtectChangeUtc).TotalMilliseconds <= 900; } if (newProtect != 0) { state.Protect = newProtect; } state.LastProtectChangeUtc = now; string protectLabel = DescribeMemoryProtect(newProtect); (string contextActionSuffix, string contextDetail) = DescribeMemoryRegionContext(view, baseAddress, regionSize); action = $"Changes protection to {protectLabel} at 0x{baseAddress:X}{contextActionSuffix}"; detail = $"Action: memory.protect\n" + $"API: {apiName}\n" + $"Base: 0x{baseAddress:X}\n" + $"Size: 0x{regionSize:X}\n" + $"NewProtect: 0x{newProtect:X} ({protectLabel})\n" + $"ProtectFlips: {state.ProtectFlipCount}\n" + $"RapidFlip: {(rapidFlip ? "yes" : "no")}\n" + contextDetail.TrimEnd(); return true; } if (apiName.Equals("NtWriteVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "base", "c0", "a1"); ulong size = FirstU64(fields, "size", "c1", "a3"); ulong page = baseAddress & ~0xFFFUL; ApiMemoryPageSignal state = GetOrCreateApiMemoryPageSignal(page); int sampleLen = (int)Math.Min(view.DeepSampleSize, (uint)(view.DeepSample?.Length ?? 0)); double entropy = ComputeSampleEntropyBits(view.DeepSample, sampleLen); if (entropy < 0 && TryReadDouble(fields, out double parsedEntropy, "entropy")) { entropy = parsedEntropy; } bool entropyChanged = !double.IsNaN(state.LastEntropyBits) && entropy >= 0 && Math.Abs(state.LastEntropyBits - entropy) >= MemoryEntropyFlipDeltaBits; bool rapidEntropyFlip = false; if (entropyChanged) { state.EntropyFlipCount += 1; rapidEntropyFlip = state.LastEntropyChangeUtc != default && (now - state.LastEntropyChangeUtc).TotalMilliseconds <= 900; } if (entropy >= 0) { state.LastEntropyBits = entropy; state.LastEntropyChangeUtc = now; } string entropyText = entropy >= 0 ? entropy.ToString("F2", CultureInfo.InvariantCulture) : "n/a"; (string contextActionSuffix, string contextDetail) = DescribeMemoryRegionContext(view, baseAddress, size); action = $"Writes 0x{size:X} bytes at 0x{baseAddress:X} (entropy {entropyText}){contextActionSuffix}"; detail = $"Action: memory.write\n" + $"API: {apiName}\n" + $"Base: 0x{baseAddress:X}\n" + $"Size: 0x{size:X}\n" + $"Entropy(bits/byte): {entropyText}\n" + $"EntropyFlips: {state.EntropyFlipCount}\n" + $"ProtectFlips: {state.ProtectFlipCount}\n" + $"RapidEntropyFlip: {(rapidEntropyFlip ? "yes" : "no")}\n" + $"SampleBytes: {sampleLen}\n" + contextDetail.TrimEnd(); return true; } return false; } private MemoryRegionAttributionSample? CreateMemoryRegionAttributionSample(BrokerEtwEventView view) { string apiName = string.IsNullOrWhiteSpace(view.EventName) ? view.Operation ?? string.Empty : view.EventName; Dictionary fields = BuildHookFieldMap(view); string eventKind = string.Empty; string regionKind = string.Empty; ulong allocationBase = 0; ulong baseAddress = 0; ulong regionSize = 0; ulong threadStartAddress = 0; uint initialProtection = 0; uint currentProtection = 0; uint previousProtection = 0; uint protectFlipCount = 0; uint sampleBytes = 0; bool threadStartObserved = false; bool functionTableRegistered = false; ulong functionTablePointer = 0; double entropyBits = -1; switch (view.Family) { case BlackbirdNative.IpcEtwFamilyImage: eventKind = "ImageMap"; regionKind = "Image"; allocationBase = view.ImageBase; baseAddress = view.ImageBase; regionSize = view.ImageSize; currentProtection = view.StartRegionProtect; initialProtection = currentProtection; break; case BlackbirdNative.IpcEtwFamilyThread: eventKind = "ThreadStart"; regionKind = view.ImageBase != 0 ? "Image" : "Unknown"; allocationBase = view.ImageBase != 0 ? view.ImageBase : NormalizeRegionAddress(view.StartAddress); baseAddress = allocationBase; regionSize = view.ImageSize != 0 ? view.ImageSize : 1; currentProtection = view.StartRegionProtect; initialProtection = currentProtection; threadStartObserved = true; threadStartAddress = view.StartAddress; break; case BlackbirdNative.IpcEtwFamilyUserHook: if (string.IsNullOrWhiteSpace(apiName)) { return null; } if (apiName.Equals("NtAllocateVirtualMemory", StringComparison.OrdinalIgnoreCase) || apiName.Equals("NtAllocateVirtualMemoryEx", StringComparison.OrdinalIgnoreCase)) { eventKind = "PrivateAllocate"; regionKind = "Private"; allocationBase = FirstU64(fields, "base", "c1", "a1"); baseAddress = allocationBase; regionSize = FirstU64(fields, "size", "c3", "a3"); currentProtection = (uint)FirstU64(fields, "protect", "c3", "a5", "c5"); initialProtection = currentProtection; } else if (apiName.Equals("NtProtectVirtualMemory", StringComparison.OrdinalIgnoreCase)) { eventKind = "ProtectChange"; allocationBase = FirstU64(fields, "base", "c1", "a1"); baseAddress = allocationBase; regionSize = FirstU64(fields, "size", "c2", "a2"); currentProtection = (uint)FirstU64(fields, "newProtect", "c2", "a3", "c3"); previousProtection = (uint)FirstU64(fields, "oldProtect", "a4", "c4"); protectFlipCount = (uint)FirstU64(fields, "protectFlips"); sampleBytes = (uint)FirstU64(fields, "sampleBytes"); } else if (apiName.Equals("NtWriteVirtualMemory", StringComparison.OrdinalIgnoreCase)) { eventKind = "MemoryWrite"; allocationBase = FirstU64(fields, "base", "c1", "a1"); baseAddress = allocationBase; regionSize = Math.Max(1UL, FirstU64(fields, "size", "c3", "a3")); protectFlipCount = (uint)FirstU64(fields, "protectFlips"); sampleBytes = (uint)FirstU64(fields, "sampleBytes"); if (TryReadDouble(fields, out double parsedEntropy, "entropy")) { entropyBits = parsedEntropy; } } else if (apiName.Equals("NtMapViewOfSection", StringComparison.OrdinalIgnoreCase) || apiName.Equals("NtMapViewOfSectionEx", StringComparison.OrdinalIgnoreCase)) { eventKind = "SectionMap"; regionKind = IsHookImageSection(view) ? "Image" : "Mapped"; allocationBase = FirstU64(fields, "baseAddress", "base", "c2", "a2"); baseAddress = allocationBase; regionSize = FirstU64(fields, "viewSize", "size", "c3", "a4", "a6"); currentProtection = (uint)FirstU64(fields, "win32Protect", "a6", "c6", "c5"); initialProtection = currentProtection; } else if (apiName.Equals("RtlAddFunctionTable", StringComparison.OrdinalIgnoreCase)) { eventKind = "FunctionTableRegister"; functionTablePointer = FirstU64(fields, "table", "a0"); allocationBase = FirstU64(fields, "baseAddress", "a2"); baseAddress = allocationBase; regionSize = 1; functionTableRegistered = true; } else if (apiName.Equals("RtlInstallFunctionTableCallback", StringComparison.OrdinalIgnoreCase)) { eventKind = "FunctionTableCallback"; functionTablePointer = FirstU64(fields, "tableId", "a0"); allocationBase = FirstU64(fields, "baseAddress", "a1"); baseAddress = allocationBase; regionSize = Math.Max(1UL, FirstU64(fields, "length", "a2")); functionTableRegistered = true; } else if (apiName.Equals("RtlDeleteFunctionTable", StringComparison.OrdinalIgnoreCase)) { functionTablePointer = FirstU64(fields, "table", "a0"); eventKind = "FunctionTableDelete"; allocationBase = ResolveFunctionTableBase(view, functionTablePointer); baseAddress = allocationBase; regionSize = 1; functionTableRegistered = false; } else { return null; } break; default: return null; } if (sampleBytes == 0 && view.DeepSampleSize != 0) { sampleBytes = view.DeepSampleSize; } if (entropyBits < 0 && view.DeepSample.Length > 1) { int sampleLength = sampleBytes == 0 ? view.DeepSample.Length : (int)Math.Min(sampleBytes, (uint)view.DeepSample.Length); entropyBits = ComputeSampleEntropyBits(view.DeepSample, sampleLength); } uint targetPid = ResolveMemoryRegionTargetPid(view); if (targetPid == 0) { return null; } if (allocationBase == 0) { allocationBase = NormalizeRegionAddress(baseAddress != 0 ? baseAddress : threadStartAddress); } if (baseAddress == 0) { baseAddress = allocationBase; } if (allocationBase == 0 || baseAddress == 0) { return null; } if (regionSize == 0) { if (threadStartObserved || eventKind.StartsWith("FunctionTable", StringComparison.OrdinalIgnoreCase)) { regionSize = 1; } else { return null; } } ulong processStartKey = ResolveObservedProcessStartKey(targetPid, view.ProcessStartKey); if (view.Family == BlackbirdNative.IpcEtwFamilyThread && threadStartAddress != 0 && TryResolveKnownRegionForAddress(targetPid, processStartKey, threadStartAddress, out _, out ulong existingBaseAddress, out ulong existingRegionSize, out string existingRegionKind, out uint existingProtection)) { allocationBase = existingBaseAddress; baseAddress = existingBaseAddress; regionSize = existingRegionSize; regionKind = existingRegionKind; if (currentProtection == 0) { currentProtection = existingProtection; } if (initialProtection == 0) { initialProtection = existingProtection; } } string regionIdentity = BuildRegionIdentity(processStartKey, targetPid, allocationBase); string firstUserFrameModule = ResolveHookOriginModule(view, fields); bool hookFamily = view.Family == BlackbirdNative.IpcEtwFamilyUserHook; string frameSummary = view.Family == BlackbirdNative.IpcEtwFamilyUserHook ? BuildHookFrameSummary(view, fields) : string.Empty; ProcessIdentityResolver.Prime(view.ActorPid); ProcessIdentityResolver.Prime(targetPid); string executionContext = string.Empty; if (TryDescribeHookExecutionContext(view, fields, out string contextHeadline, out _)) { executionContext = contextHeadline; } var sample = new MemoryRegionAttributionSample { TimestampUtc = view.TimestampUtc == default ? DateTime.UtcNow : view.TimestampUtc, ProcessStartKey = processStartKey, TargetPid = targetPid, ActorPid = view.ActorPid != 0 ? view.ActorPid : targetPid, ActorTid = view.ThreadId != 0 ? view.ThreadId : view.EventThreadId, AllocationBase = allocationBase, BaseAddress = baseAddress, RegionSize = regionSize, ApiName = apiName, EventKind = eventKind, RegionKind = string.IsNullOrWhiteSpace(regionKind) ? InferRegionKind(view, currentProtection) : regionKind, RegionIdentity = regionIdentity, OriginPath = view.OriginPath ?? view.ImagePath ?? string.Empty, SourceFamily = view.Family == BlackbirdNative.IpcEtwFamilyUserHook ? "Hook" : view.Family == BlackbirdNative.IpcEtwFamilyImage ? "ImageLoad" : view.Family == BlackbirdNative.IpcEtwFamilyThread ? "Thread" : "ETW", ExecutionContext = executionContext, CallerOrigin = view.CallerOriginLabel, FirstUserFrame = view.OriginAddress, FirstUserFrameModule = firstUserFrameModule, FrameSummary = frameSummary, UnwindClean = hookFamily && IsHookUnwindClean(view.Flags), FrameChainHadGaps = hookFamily && HookFrameChainHasGaps(view.Flags), ObservedByKernel = hookFamily && view.SourceId == BlackbirdNative.IpcEtwSourceBlackbird, ObservedByUserHook = hookFamily && view.SourceId == BlackbirdNative.IpcEtwSourceUserHook, BlackbirdOwned = IsBlackbirdOwnEvent(view), CrossProcess = targetPid != 0 && view.ActorPid != 0 && targetPid != view.ActorPid, ImageBacked = string.Equals(regionKind, "Image", StringComparison.OrdinalIgnoreCase) || IsHookImageSection(view) || view.Family == BlackbirdNative.IpcEtwFamilyImage, InitialProtection = initialProtection, CurrentProtection = currentProtection, PreviousProtection = previousProtection, ProtectFlipCount = protectFlipCount, EntropyBits = entropyBits, SampleBytes = sampleBytes, ThreadStartObserved = threadStartObserved, ThreadId = threadStartObserved ? (view.ThreadId != 0 ? view.ThreadId : view.EventThreadId) : 0, ThreadStartAddress = threadStartAddress, FunctionTableRegistered = functionTableRegistered, FunctionTablePointer = functionTablePointer, SignatureLevel = view.SignatureLevel, SignatureType = view.SignatureType }; return FinalizeMemoryRegionAttribution(sample); } private MemoryRegionAttributionSample FinalizeMemoryRegionAttribution(MemoryRegionAttributionSample sample) { if (string.IsNullOrWhiteSpace(sample.RegionIdentity)) { return sample; } if (!_regionLifecycleByIdentity.TryGetValue(sample.RegionIdentity, out RegionLifecycleState? state)) { state = new RegionLifecycleState(); _regionLifecycleByIdentity[sample.RegionIdentity] = state; } if (state.TargetPid == 0) { state.TargetPid = sample.TargetPid; } if (state.ProcessStartKey == 0) { state.ProcessStartKey = sample.ProcessStartKey; } if (state.BaseAddress == 0) { state.BaseAddress = sample.AllocationBase != 0 ? sample.AllocationBase : sample.BaseAddress; } if (string.IsNullOrWhiteSpace(state.ExecutionContext) && !string.IsNullOrWhiteSpace(sample.ExecutionContext)) { state.ExecutionContext = sample.ExecutionContext; } if (state.RegionSize == 0 && sample.RegionSize != 0) { state.RegionSize = sample.RegionSize; } else if (sample.RegionSize > state.RegionSize) { state.RegionSize = sample.RegionSize; } if (string.IsNullOrWhiteSpace(state.RegionKind) && !string.IsNullOrWhiteSpace(sample.RegionKind)) { state.RegionKind = sample.RegionKind; } if (!state.ObservedByKernel && sample.ObservedByKernel) { state.ObservedByKernel = true; } if (!state.ObservedByUserHook && sample.ObservedByUserHook) { state.ObservedByUserHook = true; } if (!state.BlackbirdOwned && sample.BlackbirdOwned) { state.BlackbirdOwned = true; } if (!state.CrossProcess && sample.CrossProcess) { state.CrossProcess = true; } if (!state.ImageBacked && sample.ImageBacked) { state.ImageBacked = true; } if (sample.InitialProtection != 0 && state.InitialProtection == 0) { state.InitialProtection = sample.InitialProtection; } if (sample.CurrentProtection == 0 && state.CurrentProtection != 0) { sample.CurrentProtection = state.CurrentProtection; } if (sample.InitialProtection == 0 && state.InitialProtection != 0) { sample.InitialProtection = state.InitialProtection; } uint reportedProtectFlipCount = sample.ProtectFlipCount; DateTime observedUtc = sample.TimestampUtc == default ? DateTime.UtcNow : sample.TimestampUtc; if (sample.CurrentProtection != 0) { uint previousProtection = sample.PreviousProtection != 0 ? sample.PreviousProtection : state.CurrentProtection; if (sample.PreviousProtection == 0 && previousProtection != 0 && (sample.EventKind.Equals("ProtectChange", StringComparison.OrdinalIgnoreCase) || sample.EventKind.Equals("SectionMap", StringComparison.OrdinalIgnoreCase) || sample.EventKind.Equals("ImageMap", StringComparison.OrdinalIgnoreCase))) { sample.PreviousProtection = previousProtection; } bool executableNow = IsExecutableProtection(sample.CurrentProtection); bool executableBefore = IsExecutableProtection(previousProtection); if (!state.FirstExecutableTransitionSeen && executableNow && !executableBefore) { sample.FirstExecutableTransition = true; state.FirstExecutableTransitionSeen = true; } bool protectionChanged = previousProtection != 0 && !ProtectionValuesEquivalent(previousProtection, sample.CurrentProtection); bool protectSignalEvent = sample.EventKind.Equals("ProtectChange", StringComparison.OrdinalIgnoreCase) || reportedProtectFlipCount > state.ProtectFlipCount; if (protectionChanged && protectSignalEvent) { string transition = BuildProtectionTransitionLabel(previousProtection, sample.CurrentProtection); sample.ProtectionTransition = transition; state.LastProtectionTransition = transition; if (reportedProtectFlipCount > state.ProtectFlipCount) { state.ProtectFlipCount = reportedProtectFlipCount; } else { state.ProtectFlipCount += 1; } double protectDeltaMs = state.LastProtectChangeUtc == default ? double.MaxValue : (observedUtc - state.LastProtectChangeUtc).TotalMilliseconds; if (protectDeltaMs >= 0 && protectDeltaMs <= 1000) { state.RapidProtectFlipCount += 1; } state.LastProtectChangeUtc = observedUtc; if (executableNow != executableBefore) { state.ExecutableFlipCount += 1; } if (IsGuardNoAccessProtectionTransition(previousProtection, sample.CurrentProtection)) { state.GuardNoAccessFlipCount += 1; } if (IsWritableExecutableProtectionTransition(previousProtection, sample.CurrentProtection)) { state.WritableExecutableFlipCount += 1; } } else if (string.IsNullOrWhiteSpace(sample.ProtectionTransition)) { sample.ProtectionTransition = state.LastProtectionTransition; } if (state.InitialProtection == 0) { state.InitialProtection = sample.CurrentProtection; } state.PreviousProtection = previousProtection; state.CurrentProtection = sample.CurrentProtection; } if (sample.EventKind.Equals("SectionMap", StringComparison.OrdinalIgnoreCase) || sample.EventKind.Equals("ImageMap", StringComparison.OrdinalIgnoreCase)) { state.MapCount += 1; } else if (sample.EventKind.Equals("MemoryWrite", StringComparison.OrdinalIgnoreCase)) { state.WriteCount += 1; } else if (sample.EventKind.Equals("ProtectChange", StringComparison.OrdinalIgnoreCase)) { state.ProtectCount += 1; } if (sample.ThreadStartObserved) { state.ThreadStartCount += 1; if (!state.FirstThreadStartSeen) { state.FirstThreadStartSeen = true; state.FirstThreadStartAddress = sample.ThreadStartAddress; } else { sample.ThreadStartObserved = false; } } if (sample.EventKind.Equals("FunctionTableRegister", StringComparison.OrdinalIgnoreCase) || sample.EventKind.Equals("FunctionTableCallback", StringComparison.OrdinalIgnoreCase)) { state.FunctionTableRegistered = true; } else if (sample.EventKind.Equals("FunctionTableDelete", StringComparison.OrdinalIgnoreCase)) { state.FunctionTableRegistered = false; } if (reportedProtectFlipCount > state.ProtectFlipCount) { state.ProtectFlipCount = reportedProtectFlipCount; } if (sample.EntropyBits >= 0) { bool entropyChanged = state.LastEntropyBits >= 0 && Math.Abs(state.LastEntropyBits - sample.EntropyBits) >= MemoryEntropyFlipDeltaBits; if (entropyChanged) { state.EntropyFlipCount += 1; double entropyDeltaMs = state.LastEntropyChangeUtc == default ? double.MaxValue : (observedUtc - state.LastEntropyChangeUtc).TotalMilliseconds; if (entropyDeltaMs >= 0 && entropyDeltaMs <= 1000) { state.RapidEntropyFlipCount += 1; } state.LastEntropyChangeUtc = observedUtc; } if (sample.EventKind.Equals("MemoryWrite", StringComparison.OrdinalIgnoreCase) && sample.EntropyBits >= MemoryHighEntropyBits && (sample.SampleBytes == 0 || sample.SampleBytes >= MemoryEntropyMinSampleBytes)) { state.HighEntropyWriteCount += 1; } state.LastEntropyBits = sample.EntropyBits; if (state.MaxEntropyBits < 0 || sample.EntropyBits > state.MaxEntropyBits) { state.MaxEntropyBits = sample.EntropyBits; } } if (sample.SampleBytes > state.LastSampleBytes) { state.LastSampleBytes = sample.SampleBytes; } if (sample.TargetPid != 0 && sample.BaseAddress != 0) { if (sample.ApiName.Equals("RtlAddFunctionTable", StringComparison.OrdinalIgnoreCase) && sample.FunctionTablePointer != 0) { string tableKey = BuildFunctionTablePointerKey(sample.TargetPid, sample.FunctionTablePointer); _functionTableBaseByPointer[tableKey] = sample.BaseAddress; } else if (sample.ApiName.Equals("RtlDeleteFunctionTable", StringComparison.OrdinalIgnoreCase) && sample.FunctionTablePointer != 0) { _functionTableBaseByPointer.Remove( BuildFunctionTablePointerKey(sample.TargetPid, sample.FunctionTablePointer)); } } sample.FunctionTableRegistered = state.FunctionTableRegistered || sample.FunctionTableRegistered; sample.MapCount = state.MapCount; sample.WriteCount = state.WriteCount; sample.ProtectCount = state.ProtectCount; sample.ThreadStartCount = state.ThreadStartCount; sample.ProtectFlipCount = Math.Max(sample.ProtectFlipCount, state.ProtectFlipCount); sample.RapidProtectFlipCount = state.RapidProtectFlipCount; sample.ExecutableFlipCount = state.ExecutableFlipCount; sample.GuardNoAccessFlipCount = state.GuardNoAccessFlipCount; sample.WritableExecutableFlipCount = state.WritableExecutableFlipCount; if (string.IsNullOrWhiteSpace(sample.ProtectionTransition)) { sample.ProtectionTransition = state.LastProtectionTransition; } if (sample.EntropyBits < 0) { sample.EntropyBits = state.LastEntropyBits; } sample.MaxEntropyBits = state.MaxEntropyBits; sample.EntropyFlipCount = state.EntropyFlipCount; sample.RapidEntropyFlipCount = state.RapidEntropyFlipCount; sample.HighEntropyWriteCount = state.HighEntropyWriteCount; if (sample.SampleBytes == 0) { sample.SampleBytes = state.LastSampleBytes; } sample.ObservedByKernel = sample.ObservedByKernel || state.ObservedByKernel; sample.ObservedByUserHook = sample.ObservedByUserHook || state.ObservedByUserHook; sample.BlackbirdOwned = sample.BlackbirdOwned || state.BlackbirdOwned; sample.CrossProcess = sample.CrossProcess || state.CrossProcess; sample.ImageBacked = sample.ImageBacked || state.ImageBacked; if (string.IsNullOrWhiteSpace(sample.ExecutionContext)) { sample.ExecutionContext = state.ExecutionContext; } sample.LifecycleSummary = BuildMemoryLifecycleSummary(sample); return sample; } private static string BuildMemoryLifecycleSummary(MemoryRegionAttributionSample sample) { var parts = new List(6); if (sample.MapCount != 0) { parts.Add($"map:{sample.MapCount}"); } if (sample.WriteCount != 0) { parts.Add($"write:{sample.WriteCount}"); } if (sample.ProtectCount != 0) { parts.Add($"protect:{sample.ProtectCount}"); } if (sample.ThreadStartCount != 0) { parts.Add($"thread:{sample.ThreadStartCount}"); } if (sample.FirstExecutableTransition) { parts.Add("first-exec"); } if (sample.FunctionTableRegistered) { parts.Add("unwind"); } string summary = parts.Count == 0 ? sample.EventKind : string.Join(" ", parts); if (sample.CrossProcess) { summary += " xproc"; } if (sample.ImageBacked) { summary += " image"; } if (sample.ProtectFlipCount != 0) { summary += $" pflip:{sample.ProtectFlipCount}"; } if (sample.RapidProtectFlipCount != 0) { summary += $" rapid-pflip:{sample.RapidProtectFlipCount}"; } if (!string.IsNullOrWhiteSpace(sample.ProtectionTransition)) { summary += $" transition:{sample.ProtectionTransition}"; } if (sample.ExecutableFlipCount != 0) { summary += $" xflip:{sample.ExecutableFlipCount}"; } if (sample.GuardNoAccessFlipCount != 0) { summary += $" guard/noaccess:{sample.GuardNoAccessFlipCount}"; } if (sample.WritableExecutableFlipCount != 0) { summary += $" wxflip:{sample.WritableExecutableFlipCount}"; } if (sample.EntropyBits >= 0) { summary += $" H:{sample.EntropyBits:F1}"; } if (sample.MaxEntropyBits >= 0 && sample.MaxEntropyBits > sample.EntropyBits + 0.05) { summary += $" Hmax:{sample.MaxEntropyBits:F1}"; } if (sample.EntropyFlipCount != 0) { summary += $" eflip:{sample.EntropyFlipCount}"; } if (sample.RapidEntropyFlipCount != 0) { summary += $" rapid-eflip:{sample.RapidEntropyFlipCount}"; } if (sample.HighEntropyWriteCount != 0) { summary += $" high-H:{sample.HighEntropyWriteCount}"; } return summary.Trim(); } private uint ResolveMemoryRegionTargetPid(BrokerEtwEventView view) { return view.Family switch { BlackbirdNative.IpcEtwFamilyImage => view.ProcessPid != 0 ? view.ProcessPid : view.EventProcessId, BlackbirdNative.IpcEtwFamilyThread => view.ProcessPid != 0 ? view.ProcessPid : (view.TargetPid != 0 ? view.TargetPid : view.EventProcessId), BlackbirdNative.IpcEtwFamilyUserHook => view.TargetPid != 0 ? view.TargetPid : (view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid), _ => 0 }; } private ulong ResolveObservedProcessStartKey(uint pid, ulong eventStartKey) { if (eventStartKey != 0) { _observedProcessStartKeyByPid[pid] = eventStartKey; return eventStartKey; } return _observedProcessStartKeyByPid.TryGetValue(pid, out ulong cached) ? cached : 0; } private static string BuildRegionIdentity(ulong processStartKey, uint targetPid, ulong allocationBase) => $"{targetPid:X8}:{processStartKey:X16}:{allocationBase:X16}"; private static string BuildFunctionTablePointerKey(uint pid, ulong functionTable) => $"{pid:X8}:{functionTable:X16}"; private ulong ResolveFunctionTableBase(BrokerEtwEventView view, ulong functionTable) { if (view.ProcessPid != 0 && functionTable != 0) { string key = BuildFunctionTablePointerKey(view.ProcessPid, functionTable); if (_functionTableBaseByPointer.TryGetValue(key, out ulong baseAddress) && baseAddress != 0) { return baseAddress; } } return 0; } private bool TryResolveKnownRegionForAddress(uint targetPid, ulong processStartKey, ulong address, out string regionIdentity, out ulong baseAddress, out ulong regionSize, out string regionKind, out uint currentProtection) { regionIdentity = string.Empty; baseAddress = 0; regionSize = 0; regionKind = string.Empty; currentProtection = 0; if (targetPid == 0 || address == 0 || _regionLifecycleByIdentity.Count == 0) { return false; } RegionLifecycleState? best = null; foreach ((string candidateIdentity, RegionLifecycleState candidate) in _regionLifecycleByIdentity) { if (candidate.TargetPid != targetPid || candidate.BaseAddress == 0 || candidate.RegionSize == 0) { continue; } if (processStartKey != 0 && candidate.ProcessStartKey != 0 && candidate.ProcessStartKey != processStartKey) { continue; } ulong regionEnd = candidate.BaseAddress + candidate.RegionSize; if (regionEnd <= candidate.BaseAddress) { regionEnd = ulong.MaxValue; } if (address < candidate.BaseAddress || address >= regionEnd) { continue; } if (best == null || candidate.BaseAddress >= best.BaseAddress) { best = candidate; regionIdentity = candidateIdentity; } } if (best == null) { return false; } baseAddress = best.BaseAddress; regionSize = best.RegionSize; regionKind = best.RegionKind; currentProtection = best.CurrentProtection; return true; } private static ulong NormalizeRegionAddress(ulong address) => address == 0 ? 0 : (address & ~0xFFFUL); private static bool IsExecutableProtection(uint protection) { uint normalized = protection & 0xFFu; return normalized == 0x10u || normalized == 0x20u || normalized == 0x40u || normalized == 0x80u; } private static bool IsWritableProtection(uint protection) { uint normalized = protection & 0xFFu; return normalized == 0x04u || normalized == 0x08u || normalized == 0x40u || normalized == 0x80u; } private static bool IsNoAccessProtection(uint protection) { return (protection & 0xFFu) == 0x01u; } private static bool HasGuardProtection(uint protection) { return (protection & 0x100u) != 0; } private static bool IsWritableExecutableProtection(uint protection) { uint normalized = protection & 0xFFu; return normalized == 0x40u || normalized == 0x80u; } private static uint NormalizeProtectionForFlip(uint protection) { return protection & 0x1FFu; } private static bool ProtectionValuesEquivalent(uint left, uint right) { return NormalizeProtectionForFlip(left) == NormalizeProtectionForFlip(right); } private static bool IsGuardNoAccessProtectionTransition(uint previousProtection, uint currentProtection) { return IsNoAccessProtection(previousProtection) || IsNoAccessProtection(currentProtection) || HasGuardProtection(previousProtection) || HasGuardProtection(currentProtection); } private static bool IsWritableExecutableProtectionTransition(uint previousProtection, uint currentProtection) { return IsWritableExecutableProtection(previousProtection) || IsWritableExecutableProtection(currentProtection) || (IsWritableProtection(previousProtection) && IsExecutableProtection(currentProtection)) || (IsExecutableProtection(previousProtection) && IsWritableProtection(currentProtection)); } private static string BuildProtectionTransitionLabel(uint previousProtection, uint currentProtection) { return $"{CompactMemoryProtectionLabel(previousProtection)}->{CompactMemoryProtectionLabel(currentProtection)}"; } private static string CompactMemoryProtectionLabel(uint protection) { string baseLabel = (protection & 0xFFu) switch { 0x01u => "NO_ACCESS", 0x02u => "R", 0x04u => "RW", 0x08u => "WC", 0x10u => "X", 0x20u => "RX", 0x40u => "RWX", 0x80u => "XWC", _ => protection == 0 ? "UNKNOWN" : $"0x{protection & 0xFFu:X}" }; return HasGuardProtection(protection) ? $"GUARD|{baseLabel}" : baseLabel; } private static bool IsMemoryPatternMilestone(uint count) { return count == 2 || count == 3 || count == 5 || count == 10 || count == 25 || count == 50 || (count >= 100 && count % 100 == 0); } private static bool IsHighEntropyWriteMilestone(uint count) { return count == 1 || count == 3 || count == 10 || count == 25 || count == 50 || (count >= 100 && count % 100 == 0); } private static bool HasUsableEntropySample(MemoryRegionAttributionSample sample) { return sample.EntropyBits >= 0 && (sample.SampleBytes == 0 || sample.SampleBytes >= MemoryEntropyMinSampleBytes); } private static uint ComputeMemoryLifecycleSeverity(MemoryRegionAttributionSample sample, bool protectSignal, bool entropySignal) { uint severity = protectSignal ? 5u : 4u; if (protectSignal) { if (sample.ProtectFlipCount >= 50 || sample.RapidProtectFlipCount >= 10) { severity = Math.Max(severity, 9u); } else if (sample.ProtectFlipCount >= 10 || sample.RapidProtectFlipCount >= 4) { severity = Math.Max(severity, 8u); } else if (sample.ProtectFlipCount >= 5 || sample.GuardNoAccessFlipCount != 0 || sample.WritableExecutableFlipCount != 0) { severity = Math.Max(severity, 7u); } else if (sample.ProtectFlipCount >= 2 || sample.RapidProtectFlipCount != 0 || sample.ExecutableFlipCount != 0) { severity = Math.Max(severity, 6u); } } if (entropySignal) { if (sample.MaxEntropyBits >= 7.7 || sample.HighEntropyWriteCount >= 10) { severity = Math.Max(severity, 7u); } else if (sample.EntropyBits >= MemoryHighEntropyBits || sample.EntropyFlipCount >= 3) { severity = Math.Max(severity, 5u); } } if (sample.CrossProcess) { severity = Math.Max(severity, 7u); } if (sample.FunctionTableRegistered && sample.ProtectFlipCount != 0) { severity = Math.Max(severity, 7u); } return Math.Min(severity, 9u); } private static bool IsHookUnwindClean(uint flags) { return (flags & BlackbirdNative.IpcEtwFlagModuleChainSane) != 0 && (flags & BlackbirdNative.IpcEtwFlagUnwindMetadataValid) != 0 && (flags & BlackbirdNative.IpcEtwFlagTebStackBoundsValid) != 0 && (flags & BlackbirdNative.IpcEtwFlagFramesOutsideTebStack) == 0; } private static bool HookFrameChainHasGaps(uint flags) { return (flags & BlackbirdNative.IpcEtwFlagModuleChainSane) == 0 || (flags & BlackbirdNative.IpcEtwFlagFramesOutsideTebStack) != 0; } private static string InferRegionKind(BrokerEtwEventView view, uint protection) { if (view.Family == BlackbirdNative.IpcEtwFamilyImage) { return "Image"; } if (view.EventName.Equals("NtAllocateVirtualMemory", StringComparison.OrdinalIgnoreCase) || view.EventName.Equals("NtAllocateVirtualMemoryEx", StringComparison.OrdinalIgnoreCase)) { return "Private"; } if (view.EventName.Equals("NtMapViewOfSection", StringComparison.OrdinalIgnoreCase) || view.EventName.Equals("NtMapViewOfSectionEx", StringComparison.OrdinalIgnoreCase)) { return IsExecutableProtection(protection) ? "Mapped-Executable" : "Mapped"; } return "Unknown"; } private bool ShouldTrackRawIoctlRecord(IoctlParsedEvent record) { return record.Type switch { BlackbirdNative.EventTypeHandle => ShouldTrackRawHandleIoctl(record), BlackbirdNative.EventTypeThread => ShouldTrackRawThreadIoctl(record), BlackbirdNative.EventTypeFileSystem => record.FileProcessPid == 0 || IsTrackedPid(record.FileProcessPid), BlackbirdNative.EventTypeRegistry => record.RegistryProcessPid == 0 || IsTrackedPid(record.RegistryProcessPid), BlackbirdNative.EventTypeEnterprise => record.EnterpriseProcessPid == 0 || IsTrackedPid(record.EnterpriseProcessPid), _ => false }; } private bool ShouldTrackRawHandleIoctl(IoctlParsedEvent record) { if (record.CallerPid == 0 || record.TargetPid == 0) { return false; } if (!IsTrackedPid(record.CallerPid)) { return false; } string originModule = EventDetailFormatting.ModuleNameFromPath(record.OriginPath); return !EventDetailFormatting.IsSr71Module(originModule); } private bool ShouldTrackRawThreadIoctl(IoctlParsedEvent record) { if (record.ProcessPid == 0) { return false; } if (record.CreatorPid != 0 && record.CreatorPid != record.ProcessPid && !IsTrackedPid(record.CreatorPid)) { return false; } return true; } private static bool ShouldAdmitIoctlRecord(IoctlParsedEvent record, long pendingCount) { if (pendingCount >= MaxPendingIoctlEvents) { return false; } if (pendingCount < IoctlPressureSoftLimit) { return true; } return pendingCount >= IoctlPressureCriticalLimit ? IsCriticalIoctlRecord(record) : IsImportantIoctlRecord(record); } private static bool IsImportantIoctlRecord(IoctlParsedEvent record) { return record.Type switch { BlackbirdNative.EventTypeHandle => IsImportantHandleIoctl(record), BlackbirdNative.EventTypeThread => IsImportantThreadIoctl(record), BlackbirdNative.EventTypeFileSystem => IsImportantFilesystemIoctl(record), BlackbirdNative.EventTypeEnterprise => true, _ => false }; } private static bool IsCriticalIoctlRecord(IoctlParsedEvent record) { return record.Type switch { BlackbirdNative.EventTypeHandle => IsCriticalHandleIoctl(record), BlackbirdNative.EventTypeThread => IsCriticalThreadIoctl(record), BlackbirdNative.EventTypeFileSystem => IsCriticalFilesystemIoctl(record), BlackbirdNative.EventTypeEnterprise => (record.EnterpriseFlags & BlackbirdNative.EnterpriseFlagCritical) != 0, _ => false }; } private static bool IsImportantHandleIoctl(IoctlParsedEvent record) { bool isThreadObject = (record.HandleFlags & 0x00000010u) != 0; return record.HandleClass == 2 || (record.HandleFlags & (HighSignalHandleMask | 0x00000080u)) != 0 || IsHighRiskIoctlAccess(record.DesiredAccess, isThreadObject); } private static bool IsCriticalHandleIoctl(IoctlParsedEvent record) { bool isThreadObject = (record.HandleFlags & 0x00000010u) != 0; return record.HandleClass == 2 || (record.HandleFlags & HighSignalHandleMask) != 0 || ((record.HandleFlags & 0x00000080u) != 0 && IsHighRiskIoctlAccess(record.DesiredAccess, isThreadObject)); } private static bool IsImportantThreadIoctl(IoctlParsedEvent record) { string kind = DetermineThreadLifecycleKind(record.ThreadFlags, record.StartAddress, record.ImageSize); return kind != "Update" || (record.ThreadFlags & ThreadHighSignalMask) != 0 || (record.CreatorPid != 0 && record.ProcessPid != 0 && record.CreatorPid != record.ProcessPid); } private static bool IsCriticalThreadIoctl(IoctlParsedEvent record) { return (record.ThreadFlags & ThreadHighSignalMask) != 0 || (record.CreatorPid != 0 && record.ProcessPid != 0 && record.CreatorPid != record.ProcessPid); } private static bool IsImportantFilesystemIoctl(IoctlParsedEvent record) { return record.FileOperation switch { BlackbirdNative.FileOperationCreate or BlackbirdNative.FileOperationWrite or BlackbirdNative.FileOperationSetInformation or BlackbirdNative.FileOperationDirectoryControl or BlackbirdNative.FileOperationFsControl => true, _ => record.FileStatus != 0 }; } private static bool IsCriticalFilesystemIoctl(IoctlParsedEvent record) { return record.FileOperation switch { BlackbirdNative.FileOperationCreate or BlackbirdNative.FileOperationWrite or BlackbirdNative.FileOperationSetInformation or BlackbirdNative.FileOperationFsControl => true, _ => record.FileStatus != 0 }; } private static bool ShouldPersistIoctlRecord(IoctlParsedEvent record, TelemetryEvent? telemetry, ProcessRelationView? relation, HeuristicEventView? heuristic, IoctlParsedEvent? filesystem) { if (filesystem != null || heuristic != null || relation != null) { return true; } if (telemetry == null) { return false; } return record.Type switch { BlackbirdNative.EventTypeHandle => IsImportantHandleIoctl(record), BlackbirdNative.EventTypeThread => IsImportantThreadIoctl(record), BlackbirdNative.EventTypeEnterprise => true, _ => false }; } private bool IsTrackedPid(uint pid) { return pid == 0 || _filterTrackedPids.IsEmpty || _filterTrackedPids.ContainsKey(pid); } private static bool ShouldTrackRawEtwEvent(BlackbirdNative.BkIpcEtwEvent etw) { byte[] detectionName = etw.DetectionName ?? Array.Empty(); if (detectionName.Length > 0 && detectionName[0] != 0) { return true; } if (IsRawDirectSyscallEtwEvent(etw)) { return true; } if (etw.Severity >= 4) { return true; } if (etw.Source == BlackbirdNative.IpcEtwSourceThreatIntel) { return etw.Task == 1 || etw.Task == 2 || etw.Task == 7; } return etw.Family switch { BlackbirdNative.IpcEtwFamilyProcess => (etw.Flags & BlackbirdNative.IpcEtwFlagProcessIsCreate) != 0, BlackbirdNative.IpcEtwFamilyThread or BlackbirdNative.IpcEtwFamilyApc or BlackbirdNative.IpcEtwFamilyUserHook or BlackbirdNative.IpcEtwFamilySocket => true, BlackbirdNative.IpcEtwFamilyRegistry => (etw.Flags & BlackbirdNative.IpcEtwFlagRegistryHighValue) != 0, _ => false }; } private static bool IsRawDirectSyscallEtwEvent(BlackbirdNative.BkIpcEtwEvent etw) { if ((etw.DetectionTraits & BlackbirdNative.IpcEtwTraitDirectSyscall) != 0) { return true; } if (etw.Family != BlackbirdNative.IpcEtwFamilyHandle) { return false; } string className = BlackbirdNative.AnsiBufferToString(etw.ClassName); if (className.Equals("DIRECT-SYSCALL-SUSPECT", StringComparison.OrdinalIgnoreCase)) { return true; } bool exportMismatch = (etw.Flags & HandleFlagSyscallExportMismatch) != 0; bool stackSpoof = (etw.Flags & HandleFlagStackSpoofSuspect) != 0; bool execOutsideNtdll = (etw.Flags & HandleFlagExecProtect) != 0 && (etw.Flags & HandleFlagFromNtdll) == 0; return exportMismatch || stackSpoof || execOutsideNtdll; } private static bool ShouldAdmitEtwEvent(BlackbirdNative.BkIpcEtwEvent etw, long pendingCount) { byte[] detectionName = etw.DetectionName ?? Array.Empty(); if (pendingCount >= MaxPendingEtwEvents) { return false; } if (pendingCount < EtwPressureSoftLimit) { return true; } bool critical = (detectionName.Length > 0 && detectionName[0] != 0) || etw.Severity >= 6 || IsRawDirectSyscallEtwEvent(etw) || (etw.Family == BlackbirdNative.IpcEtwFamilyProcess && (etw.Flags & BlackbirdNative.IpcEtwFlagProcessIsCreate) != 0) || etw.Family == BlackbirdNative.IpcEtwFamilyUserHook; if (pendingCount >= EtwPressureCriticalLimit) { return critical; } return critical || etw.Family == BlackbirdNative.IpcEtwFamilyThread || etw.Family == BlackbirdNative.IpcEtwFamilyApc || etw.Source == BlackbirdNative.IpcEtwSourceThreatIntel; } private string BuildEtwDisplayDetail(BrokerEtwEventView view) { string rawReason = view.Reason ?? string.Empty; if (EventDetailFormatting.IsApiGraphCandidate(view)) { return BuildApiDecodedAction(view, rawReason).Detail; } return BuildGenericEtwDisplayDetail(view, BuildHookFieldMap(view), includeHeadline: true); } private string BuildFallbackApiDetail(string apiName, BrokerEtwEventView view, string rawReason, string action) { Dictionary fields = BuildHookFieldMap(rawReason, Array.Empty(), 0); string argumentText = BuildResolvedHookArgumentsText(apiName, view, fields); if (string.IsNullOrWhiteSpace(argumentText)) { return action; } return $"{action}{Environment.NewLine}{Environment.NewLine}{argumentText}"; } private string BuildGenericEtwDisplayDetail(BrokerEtwEventView view, IReadOnlyDictionary fields, bool includeHeadline) { var sb = new StringBuilder(512); string eventName = string.IsNullOrWhiteSpace(view.EventName) ? "unknown" : view.EventName.Trim(); string detection = view.DetectionName?.Trim() ?? string.Empty; string operation = view.Operation?.Trim() ?? string.Empty; string actor = ProcessIdentityResolver.Describe(view.ActorPid); string target = ProcessIdentityResolver.Describe(view.TargetPid); if (includeHeadline) { sb.AppendLine(!string.IsNullOrWhiteSpace(detection) ? detection : eventName); } if (!string.IsNullOrWhiteSpace(view.Source)) { sb.Append("Source: ").AppendLine(view.Source); } sb.Append("Event: ").AppendLine(eventName); if (!string.IsNullOrWhiteSpace(operation) && !operation.Equals(eventName, StringComparison.OrdinalIgnoreCase)) { sb.Append("Operation: ").AppendLine(operation); } if (!string.IsNullOrWhiteSpace(detection)) { sb.Append("Detection: ").AppendLine(detection); } if (view.ActorPid != 0) { sb.Append("Actor: ").AppendLine(actor); } if (view.TargetPid != 0) { sb.Append("Target: ").AppendLine(target); } if (!string.IsNullOrWhiteSpace(view.ArgumentSummary)) { sb.Append("Arguments: ").AppendLine(view.ArgumentSummary); } string frameSummary = BuildHookFrameSummary(view, fields); if (!string.IsNullOrWhiteSpace(frameSummary)) { sb.AppendLine(frameSummary); } if (includeHeadline && TryDescribeHookExecutionContext(view, fields, out string executionHeadline, out string executionDetail)) { sb.Append(executionHeadline).Append(": ").AppendLine(executionDetail); } if (!string.IsNullOrWhiteSpace(view.ClassName)) { sb.Append("Class: ").AppendLine(view.ClassName); } if (view.DesiredAccess != 0) { sb.Append("DesiredAccess: 0x") .Append(view.DesiredAccess.ToString("X8", CultureInfo.InvariantCulture)) .AppendLine(); } if (view.CorrelationFlags != 0) { sb.Append("CorrelationFlags: ") .Append(EventDetailFormatting.DescribeCorrelationFlags(view.CorrelationFlags)) .Append(" (0x") .Append(view.CorrelationFlags.ToString("X8", CultureInfo.InvariantCulture)) .AppendLine(")"); } if (view.CorrelationAccessMask != 0) { sb.Append("CorrelationAccess: ") .Append(EventDetailFormatting.DescribeHandleAccess(view.CorrelationAccessMask)) .Append(" (0x") .Append(view.CorrelationAccessMask.ToString("X8", CultureInfo.InvariantCulture)) .AppendLine(")"); } if (view.CorrelationAgeMs != 0) { sb.Append("CorrelationAgeMs: ") .Append(view.CorrelationAgeMs.ToString(CultureInfo.InvariantCulture)) .AppendLine(); } if (!string.IsNullOrWhiteSpace(view.ImagePath)) { sb.Append("ImagePath: ").AppendLine(view.ImagePath); } if (!string.IsNullOrWhiteSpace(view.OriginPath)) { sb.Append("OriginPath: ").AppendLine(view.OriginPath); } if (!string.IsNullOrWhiteSpace(view.KeyPath)) { sb.Append("KeyPath: ").AppendLine(view.KeyPath); } if (!string.IsNullOrWhiteSpace(view.ValueName)) { sb.Append("ValueName: ").AppendLine(view.ValueName); } if (fields.TryGetValue("status", out string? status) && !string.IsNullOrWhiteSpace(status)) { sb.Append("Status: ").AppendLine(status); } return sb.ToString().Trim(); } private string GetApiGraphProcessName(uint pid) { if (pid == 0) { return string.Empty; } if (_currentSession != null && _currentSession.Pid == unchecked((int)pid)) { return ExtractProcessName(_currentSession.Title); } ProcessSessionTab? knownTab = _processTabs.FirstOrDefault(x => x.Pid == unchecked((int)pid)); if (knownTab != null) { string knownName = ExtractProcessName(knownTab.Title); if (!string.IsNullOrWhiteSpace(knownName)) { return knownName; } } try { using Process process = Process.GetProcessById(unchecked((int)pid)); return process.ProcessName; } catch { return string.Empty; } } private static string ExtractProcessName(string? title) { if (string.IsNullOrWhiteSpace(title)) { return string.Empty; } string trimmed = title.Trim(); int suffixIndex = trimmed.LastIndexOf(" (", StringComparison.Ordinal); if (suffixIndex > 0 && trimmed.EndsWith(")", StringComparison.Ordinal)) { return trimmed[..suffixIndex].Trim(); } return trimmed; } private (string ActionSuffix, string DetailText) DescribeMemoryContextFromPages(ulong baseAddress) { PerformanceSample? latestSample = _currentSession?.PerformanceHistory.Count > 0 ? _currentSession.PerformanceHistory[^1] : null; MemoryPageSample? page = latestSample?.MemoryPages.FirstOrDefault( x => baseAddress >= x.BaseAddress && baseAddress < (x.BaseAddress + x.RegionSize)); if (page == null) { return (string.Empty, string.Empty); } string suffix = string.IsNullOrWhiteSpace(page.Category) ? string.Empty : $" in {page.Category.ToLowerInvariant()} region"; string detail = $"Region: {page.Category} | Protect: {page.ProtectLabel} | Type: {page.TypeLabel}\n"; return (suffix, detail); } private string DescribeMemoryImageContext(BrokerEtwEventView view, ulong baseAddress) { if (view.ImageBase == 0 || view.ImageSize == 0) { return string.Empty; } ulong imageEnd = view.ImageBase + view.ImageSize; if (baseAddress < view.ImageBase || baseAddress >= imageEnd) { return string.Empty; } string moduleName = EventDetailFormatting.ModuleNameFromPath(view.ImagePath); if (string.IsNullOrWhiteSpace(moduleName)) { moduleName = "image"; } string imagePathLine = string.IsNullOrWhiteSpace(view.ImagePath) ? string.Empty : $"ImagePath: {view.ImagePath}\n"; return $"Image: {moduleName}\n" + imagePathLine + $"ImageBase: 0x{view.ImageBase:X}\n" + $"ImageSize: 0x{view.ImageSize:X}\n"; } private (string ActionSuffix, string DetailText) DescribeMemoryRegionContext(BrokerEtwEventView view, ulong baseAddress, ulong regionSize) { if (baseAddress == 0) { return (string.Empty, string.Empty); } string imageContext = DescribeMemoryImageContext(view, baseAddress); if (!string.IsNullOrWhiteSpace(imageContext)) { string moduleName = EventDetailFormatting.ModuleNameFromPath(view.ImagePath); string suffix = string.IsNullOrWhiteSpace(moduleName) ? string.Empty : $" in image {moduleName}"; return (suffix, imageContext); } (string pageActionSuffix, string pageContext) = DescribeMemoryContextFromPages(baseAddress); if (!string.IsNullOrWhiteSpace(pageContext)) { return (pageActionSuffix, pageContext); } if (view.DeepRegionType != 0 || view.DeepRegionProtect != 0 || regionSize != 0) { string typeLabel = EventDetailFormatting.DescribeMemoryType( view.DeepRegionType != 0 ? view.DeepRegionType : view.StartRegionType); string protectLabel = EventDetailFormatting.DescribeMemoryProtection( view.DeepRegionProtect != 0 ? view.DeepRegionProtect : view.StartRegionProtect); string detailText = string.Empty; if (!string.IsNullOrWhiteSpace(typeLabel)) { detailText += $"RegionType: {typeLabel}\n"; } if (!string.IsNullOrWhiteSpace(protectLabel)) { detailText += $"RegionProtect: {protectLabel}\n"; } return (string.Empty, detailText); } return (string.Empty, string.Empty); } private ApiMemoryPageSignal GetOrCreateApiMemoryPageSignal(ulong page) { if (!_apiMemorySignalsByPage.TryGetValue(page, out ApiMemoryPageSignal? state)) { state = new ApiMemoryPageSignal(); _apiMemorySignalsByPage[page] = state; } return state; } private static Dictionary ParseReasonFields(string rawReason) { return EventDetailsParsing.ParseRawFields(rawReason); } private static Dictionary BuildHookFieldMap(string rawReason, IReadOnlyList? hookArgs, uint hookArgCount) { Dictionary fields = ParseReasonFields(rawReason); int count = Math.Min((int)hookArgCount, hookArgs?.Count ?? 0); for (int i = 0; i < count; i += 1) { string key = $"a{i}"; if (!fields.ContainsKey(key)) { fields[key] = $"0x{hookArgs![i]:X}"; } } return fields; } private static Dictionary BuildHookFieldMap(BrokerEtwEventView view) => view.GetOrCreateHookFieldMap(); private static bool IsHookKernelCaller(BrokerEtwEventView view) => (view.Flags & BlackbirdNative.IpcEtwFlagHookKernelCaller) != 0; private static bool IsHookCurrentProcessTarget(BrokerEtwEventView view) { if ((view.Flags & BlackbirdNative.IpcEtwFlagHookTargetCurrentProcess) != 0) { return true; } uint processPid = view.ProcessPid != 0 ? view.ProcessPid : view.ActorPid; return processPid != 0 && (view.TargetPid == 0 || view.TargetPid == processPid); } private static bool IsHookImageSection(BrokerEtwEventView view) => (view.Flags & BlackbirdNative.IpcEtwFlagHookSectionImage) != 0; private bool TryDescribeHookExecutionContext(BrokerEtwEventView view, IReadOnlyDictionary fields, out string headline, out string detail) { headline = string.Empty; detail = string.Empty; if (!EventDetailFormatting.IsApiGraphCandidate(view)) { return false; } string apiName = string.IsNullOrWhiteSpace(view.Operation) ? view.EventName : view.Operation; bool kernelCaller = IsHookKernelCaller(view); bool currentTarget = IsHookCurrentProcessTarget(view); bool imageSection = IsHookImageSection(view); if (kernelCaller) { headline = "Kernel Caller"; detail = "ExecutionContext: this hook fired on a KernelMode caller path. Treat the call as kernel-originated activity or loader/manager plumbing, not a normal user thread directly invoking the API."; return true; } if ((apiName.Equals("NtCreateSection", StringComparison.OrdinalIgnoreCase) || apiName.Equals("NtMapViewOfSection", StringComparison.OrdinalIgnoreCase)) && currentTarget && imageSection) { headline = "Loader / Image Mapping"; detail = "ExecutionContext: image-backed section creation or mapping into the current process. This is commonly loader-style PE/DLL mapping and should be separated from private-memory execution or post-start runtime tampering."; return true; } if (TryDescribeHookStartupContext(view, out string startupHeadline, out string startupDetail)) { headline = startupHeadline; detail = startupDetail; return true; } if (!currentTarget && view.TargetPid != 0) { headline = "Cross-Process Runtime"; detail = $"ExecutionContext: the API targets another process ({ProcessIdentityResolver.Describe(view.TargetPid)}). This is actual runtime cross-process activity, not local loader setup."; return true; } headline = "User Runtime"; detail = "ExecutionContext: user-mode call path against the current process outside the startup window."; return true; } private string BuildResolvedHookArgumentsText(string apiName, BrokerEtwEventView view, IReadOnlyDictionary fields) { List<(string Name, string Value)> args = ResolveHookArguments(apiName, view, fields); if (args.Count == 0) { return string.Empty; } var sb = new StringBuilder(256); sb.AppendLine("Arguments:"); for (int i = 0; i < args.Count; i += 1) { sb.Append(" ").Append(args[i].Name).Append(": ").AppendLine(args[i].Value); } return sb.ToString().TrimEnd(); } private List<(string Name, string Value)> ResolveHookArguments(string apiName, BrokerEtwEventView view, IReadOnlyDictionary fields) { string name = apiName?.Trim() ?? string.Empty; var args = new List<(string Name, string Value)>(8); switch (name) { case "NtWriteVirtualMemory": AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "BaseAddress", ResolvePointer(view, fields, "a1", "c1", "base")); AddResolvedArg(args, "Buffer", ResolvePointer(view, fields, "a2", "c2")); AddResolvedArg(args, "BufferSize", ResolveSize(fields, "a3", "c3", "size")); AddResolvedArg(args, "BytesWritten", ResolvePointer(view, fields, "a4", "c4")); break; case "NtReadVirtualMemory": AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "BaseAddress", ResolvePointer(view, fields, "a1", "c1", "base")); AddResolvedArg(args, "Buffer", ResolvePointer(view, fields, "a2", "c2")); AddResolvedArg(args, "BufferSize", ResolveSize(fields, "a3", "c3", "size")); AddResolvedArg(args, "BytesRead", ResolvePointer(view, fields, "a4", "c4")); break; case "NtAllocateVirtualMemory": AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "BaseAddress*", ResolvePointer(view, fields, "a1", "c1", "base")); AddResolvedArg(args, "ZeroBits", ResolveHex(fields, "a2", "c2")); AddResolvedArg(args, "RegionSize*", ResolveSize(fields, "a3", "c3", "size")); AddResolvedArg(args, "AllocationType", ResolveAllocationType(fields, "a4", "c4", "allocType")); AddResolvedArg(args, "Protect", ResolveProtect(fields, "a5", "c5", "protect")); break; case "NtAllocateVirtualMemoryEx": AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "BaseAddress*", ResolvePointer(view, fields, "a1", "c1", "base")); AddResolvedArg(args, "ZeroBits", ResolveHex(fields, "a2", "c2")); AddResolvedArg(args, "RegionSize*", ResolveSize(fields, "a3", "c3", "size")); AddResolvedArg(args, "AllocationType", ResolveAllocationType(fields, "a4", "c4", "allocType")); AddResolvedArg(args, "ExtendedParameters", ResolvePointer(view, fields, "a5", "c5")); AddResolvedArg(args, "ExtendedParameterCount", ResolveHex(fields, "a6", "c6")); break; case "NtProtectVirtualMemory": AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "BaseAddress*", ResolvePointer(view, fields, "a1", "c1", "base")); AddResolvedArg(args, "RegionSize*", ResolveSize(fields, "a2", "c2", "size")); AddResolvedArg(args, "NewProtect", ResolveProtect(fields, "a3", "c3", "newProtect")); AddResolvedArg(args, "OldProtect*", ResolvePointer(view, fields, "a4", "c4")); break; case "NtCreateSection": AddResolvedArg(args, "SectionHandle*", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "DesiredAccess", ResolveHex(fields, "a1", "c1")); AddResolvedArg(args, "ObjectAttributes", ResolvePointer(view, fields, "a2", "c2")); AddResolvedArg(args, "MaximumSize", ResolveSize(fields, "a3", "c3")); AddResolvedArg(args, "SectionPageProtection", ResolveProtect(fields, "a4", "c4")); AddResolvedArg(args, "AllocationAttributes", ResolveHex(fields, "a5", "c5")); AddResolvedArg(args, "FileHandle", ResolvePointer(view, fields, "a6", "c6")); break; case "NtMapViewOfSection": AddResolvedArg(args, "SectionHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a1", "c1")); AddResolvedArg(args, "BaseAddress*", ResolvePointer(view, fields, "a2", "c2", "base")); AddResolvedArg(args, "ViewSize*", ResolveSize(fields, "a6", "c3", "size")); AddResolvedArg(args, "InheritDisposition", ResolveHex(fields, "a7", "c4")); AddResolvedArg(args, "AllocationType", ResolveAllocationType(fields, "c5")); AddResolvedArg(args, "Win32Protect", ResolveProtect(fields, "c6")); break; case "NtMapViewOfSectionEx": AddResolvedArg(args, "SectionHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a1", "c1")); AddResolvedArg(args, "BaseAddress*", ResolvePointer(view, fields, "a2", "c2", "base")); AddResolvedArg(args, "SectionOffset*", ResolvePointer(view, fields, "a3", "c7")); AddResolvedArg(args, "ViewSize*", ResolveSize(fields, "a4", "c3", "size")); AddResolvedArg(args, "AllocationType", ResolveAllocationType(fields, "a5", "c4")); AddResolvedArg(args, "Win32Protect", ResolveProtect(fields, "a6", "c5")); AddResolvedArg(args, "ExtendedParameters", ResolvePointer(view, fields, "a7", "c6")); break; case "NtQueryInformationProcess": AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "ProcessInformationClass", ResolveProcessInformationClass(fields, "a1", "c1")); AddResolvedArg(args, "ProcessInformation", ResolvePointer(view, fields, "a2", "c2")); AddResolvedArg(args, "ProcessInformationLength", ResolveSize(fields, "a3", "c3")); AddResolvedArg(args, "ReturnLength", ResolvePointer(view, fields, "a4", "c4")); break; case "NtQueryInformationThread": AddResolvedArg(args, "ThreadHandle", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "ThreadInformationClass", ResolveHex(fields, "a1", "c1")); AddResolvedArg(args, "ThreadInformation", ResolvePointer(view, fields, "a2", "c2")); AddResolvedArg(args, "ThreadInformationLength", ResolveSize(fields, "a3", "c3")); AddResolvedArg(args, "ReturnLength", ResolvePointer(view, fields, "a4", "c4")); break; case "NtQuerySystemInformation": case "NtQuerySystemInformationEx": AddResolvedArg(args, "SystemInformationClass", ResolveSystemInformationClass(fields, "a0", "c0")); AddResolvedArg(args, "InputBuffer", ResolvePointer(view, fields, "a1", "c1")); AddResolvedArg(args, "InputBufferLength", ResolveSize(fields, "a2", "c2")); AddResolvedArg(args, "SystemInformation", ResolvePointer(view, fields, "a3", "c3")); AddResolvedArg(args, "SystemInformationLength", ResolveSize(fields, "a4", "c4")); AddResolvedArg(args, "ReturnLength", ResolvePointer(view, fields, "a5", "c5")); break; case "NtQueryPerformanceCounter": AddResolvedArg(args, "RawCounter", ResolveHex(fields, "rawCounter", "a0", "c0")); AddResolvedArg(args, "VirtualCounter", ResolveHex(fields, "virtualCounter", "a1", "c1")); AddResolvedArg(args, "RawDelta", ResolveHex(fields, "rawDelta", "a2", "c2")); AddResolvedArg(args, "VirtualDelta", ResolveHex(fields, "virtualDelta", "a3", "c3")); AddResolvedArg(args, "CorrectionTicks", ResolveHex(fields, "correctionTicks", "a4", "c4")); AddResolvedArg(args, "SourceFlags", ResolveHex(fields, "sourceFlags", "a5", "c5")); AddResolvedArg(args, "AutoBiasTicks", ResolveHex(fields, "autoBiasTicks", "a6", "c6")); break; case "NtCreateThreadEx": AddResolvedArg(args, "ThreadHandle*", ResolvePointer(view, fields, "a0", "c0")); AddResolvedArg(args, "DesiredAccess", ResolveHex(fields, "a1", "c1")); AddResolvedArg(args, "ProcessHandle", ResolvePointer(view, fields, "a2", "c2")); AddResolvedArg(args, "StartRoutine", ResolvePointer(view, fields, "a3", "c3")); AddResolvedArg(args, "Argument", ResolvePointer(view, fields, "a4", "c4")); AddResolvedArg(args, "CreateFlags", ResolveHex(fields, "a5", "c5")); AddResolvedArg(args, "StackSize", ResolveSize(fields, "a6", "c6")); AddResolvedArg(args, "MaximumStackSize", ResolveSize(fields, "a7", "c7")); break; case "RtlAddFunctionTable": AddResolvedArg(args, "FunctionTable", ResolvePointer(view, fields, "a0")); AddResolvedArg(args, "EntryCount", ResolveHex(fields, "a1")); AddResolvedArg(args, "BaseAddress", ResolvePointer(view, fields, "a2", "baseAddress")); break; case "RtlInstallFunctionTableCallback": AddResolvedArg(args, "TableIdentifier", ResolvePointer(view, fields, "a0")); AddResolvedArg(args, "BaseAddress", ResolvePointer(view, fields, "a1", "baseAddress")); AddResolvedArg(args, "Length", ResolveSize(fields, "a2", "length")); AddResolvedArg(args, "Callback", ResolvePointer(view, fields, "a3", "callback")); break; case "RtlDeleteFunctionTable": AddResolvedArg(args, "FunctionTable", ResolvePointer(view, fields, "a0", "table")); break; default: for (int i = 0; i < 8; i += 1) { string value = ResolvePointer(view, fields, $"a{i}", $"c{i}"); AddResolvedArg(args, $"Arg{i}", value); } break; } return args; } private static void AddResolvedArg(List<(string Name, string Value)> args, string name, string? value) { if (!string.IsNullOrWhiteSpace(value)) { args.Add((name, value)); } } private string ResolvePointer(BrokerEtwEventView view, IReadOnlyDictionary fields, params string[] keys) { ulong value = FirstU64(fields, keys); return value == 0 ? string.Empty : FormatObservedPointer(view, value); } private static string ResolveSize(IReadOnlyDictionary fields, params string[] keys) { ulong value = FirstU64(fields, keys); return value == 0 ? string.Empty : $"0x{value:X}"; } private static string ResolveHex(IReadOnlyDictionary fields, params string[] keys) { ulong value = FirstU64(fields, keys); return value == 0 ? string.Empty : $"0x{value:X}"; } private static string ResolveProtect(IReadOnlyDictionary fields, params string[] keys) { uint value = (uint)FirstU64(fields, keys); return value == 0 ? string.Empty : $"0x{value:X} ({DescribeMemoryProtect(value)})"; } private static string ResolveAllocationType(IReadOnlyDictionary fields, params string[] keys) { uint value = (uint)FirstU64(fields, keys); return value == 0 ? string.Empty : $"0x{value:X} ({DescribeMemoryAllocationType(value)})"; } private static string ResolveProcessInformationClass(IReadOnlyDictionary fields, params string[] keys) { uint value = (uint)FirstU64(fields, keys); if (value == 0) { return string.Empty; } string label = value switch { 0 => "ProcessBasicInformation", 7 => "ProcessDebugPort", 26 => "ProcessWow64Information", 27 => "ProcessImageFileName", 29 => "ProcessBreakOnTermination", 30 => "ProcessDebugObjectHandle", 31 => "ProcessDebugFlags", 43 => "ProcessImageFileNameWin32", _ => "Unknown" }; return $"0x{value:X} ({label})"; } private static string ResolveSystemInformationClass(IReadOnlyDictionary fields, params string[] keys) { uint value = (uint)FirstU64(fields, keys); if (value == 0) { return string.Empty; } string label = value switch { 5 => "SystemProcessInformation", 11 => "SystemModuleInformation", 35 => "SystemKernelDebuggerInformation", 76 => "SystemFirmwareTableInformation", 103 => "SystemCodeIntegrityInformation", _ => "Unknown" }; return $"0x{value:X} ({label})"; } private static string BuildGenericApiActionLabel(string apiName, IReadOnlyDictionary fields) { if (apiName.Equals("NtCreateThreadEx", StringComparison.OrdinalIgnoreCase)) { ulong startAddress = FirstU64(fields, "a3", "c3"); ulong targetHandle = FirstU64(fields, "a2", "c0"); return $"Creates thread (start 0x{startAddress:X}) via target handle 0x{targetHandle:X}"; } if (apiName.Equals("NtWriteVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong address = FirstU64(fields, "a1", "c0"); ulong size = FirstU64(fields, "a3", "c1"); return $"Writes 0x{size:X} bytes into virtual memory at 0x{address:X}"; } if (apiName.Equals("NtReadVirtualMemory", StringComparison.OrdinalIgnoreCase)) { ulong address = FirstU64(fields, "a1", "c0"); ulong size = FirstU64(fields, "a3", "c1"); return $"Reads 0x{size:X} bytes from virtual memory at 0x{address:X}"; } if (apiName.Equals("RtlAddFunctionTable", StringComparison.OrdinalIgnoreCase) || apiName.Equals("RtlInstallFunctionTableCallback", StringComparison.OrdinalIgnoreCase)) { ulong baseAddress = FirstU64(fields, "baseAddress", "a2", "a1"); return $"Registers unwind metadata for region near 0x{baseAddress:X}"; } if (apiName.Equals("RtlDeleteFunctionTable", StringComparison.OrdinalIgnoreCase)) { ulong table = FirstU64(fields, "table", "a0"); return $"Deletes dynamic unwind metadata table 0x{table:X}"; } if (apiName.Equals("WSASend", StringComparison.OrdinalIgnoreCase) || apiName.Equals("send", StringComparison.OrdinalIgnoreCase)) { return "Sends network payload"; } if (apiName.Equals("WSARecv", StringComparison.OrdinalIgnoreCase) || apiName.Equals("recv", StringComparison.OrdinalIgnoreCase)) { return "Receives network payload"; } if (fields.TryGetValue("kind", out string? kind) && !string.IsNullOrWhiteSpace(kind)) { return $"{kind} call: {apiName}"; } return apiName; } private static ulong FirstU64(IReadOnlyDictionary fields, params string[] keys) { foreach (string key in keys) { if (fields.TryGetValue(key, out string? value) && TryReadU64(value, out ulong parsed)) { return parsed; } } return 0; } private static bool TryReadU64(string? text, out ulong value) { value = 0; if (string.IsNullOrWhiteSpace(text)) { return false; } string compact = text.Trim(); if (compact.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { return ulong.TryParse(compact[2..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value); } return ulong.TryParse(compact, NumberStyles.Integer, CultureInfo.InvariantCulture, out value); } private static bool TryReadDouble(IReadOnlyDictionary fields, out double value, params string[] keys) { foreach (string key in keys) { if (fields.TryGetValue(key, out string? text) && double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) { return true; } } value = 0; return false; } private static string DescribeMemoryProtect(uint protect) { return EventDetailFormatting.DescribeMemoryProtection(protect); } private static string DescribeMemoryAllocationType(uint allocType) { var labels = new List(4); if ((allocType & 0x1000) != 0) { labels.Add("MEM_COMMIT"); } if ((allocType & 0x2000) != 0) { labels.Add("MEM_RESERVE"); } if ((allocType & 0x1000000) != 0) { labels.Add("MEM_LARGE_PAGES"); } if ((allocType & 0x20000) != 0) { labels.Add("MEM_PHYSICAL"); } return labels.Count == 0 ? "" : string.Join(" | ", labels); } private static double ComputeSampleEntropyBits(byte[]? data, int length) { if (data == null || data.Length == 0 || length <= 1) { return -1; } int sampleLength = Math.Min(length, data.Length); Span counts = stackalloc int[256]; for (int i = 0; i < sampleLength; i += 1) { counts[data[i]] += 1; } double entropy = 0; for (int i = 0; i < counts.Length; i += 1) { int count = counts[i]; if (count == 0) { continue; } double p = count / (double)sampleLength; entropy -= p * Math.Log(p, 2.0); } return entropy; } private sealed class ApiMemoryPageSignal { public uint Protect { get; set; } public int ProtectFlipCount { get; set; } public double LastEntropyBits { get; set; } = double.NaN; public int EntropyFlipCount { get; set; } public DateTime LastProtectChangeUtc { get; set; } public DateTime LastEntropyChangeUtc { get; set; } } private sealed class RecentImageFileAccess { public DateTime TimestampUtc { get; init; } public uint Pid { get; init; } public uint Tid { get; init; } public uint Operation { get; init; } public string Path { get; init; } = string.Empty; public string FileName { get; init; } = string.Empty; } private sealed class RecentImageMapState { public DateTime FirstSeenUtc { get; set; } public DateTime LastSeenUtc { get; set; } public string Path { get; set; } = string.Empty; public string LastApi { get; set; } = string.Empty; public int Count { get; set; } public RecentImageFileAccess? LastLinkedFileAccess { get; set; } public RecentImageMapState Clone() => new() { FirstSeenUtc = FirstSeenUtc, LastSeenUtc = LastSeenUtc, Path = Path, LastApi = LastApi, Count = Count, LastLinkedFileAccess = LastLinkedFileAccess }; } private readonly struct ApiCallStructuredFields { internal string Action { get; init; } internal string Field1Label { get; init; } internal string Field2Label { get; init; } internal string Field3Label { get; init; } internal string Field4Label { get; init; } internal string Field1Value { get; init; } internal string Field2Value { get; init; } internal string Field3Value { get; init; } internal string Field4Value { get; init; } } private enum BackendUiWorkKind { Ioctl, Etw, Status } private readonly struct BackendUiWorkItem { internal BackendUiWorkKind Kind { get; } internal TelemetryEvent? Telemetry { get; } internal ProcessRelationView? Relation { get; } internal HeuristicEventView? Heuristic { get; } internal ThreadLifecycleEventSample? ThreadLifecycle { get; } internal IoctlParsedEvent? Filesystem { get; } internal IoctlParsedEvent? Registry { get; } internal BrokerEtwEventView? EtwView { get; } internal string? StatusLine { get; } private BackendUiWorkItem(BackendUiWorkKind kind, TelemetryEvent? telemetry, ProcessRelationView? relation, HeuristicEventView? heuristic, ThreadLifecycleEventSample? threadLifecycle, IoctlParsedEvent? filesystem, IoctlParsedEvent? registry, BrokerEtwEventView? etwView, string? statusLine) { Kind = kind; Telemetry = telemetry; Relation = relation; Heuristic = heuristic; ThreadLifecycle = threadLifecycle; Filesystem = filesystem; Registry = registry; EtwView = etwView; StatusLine = statusLine; } internal static BackendUiWorkItem FromIoctl(TelemetryEvent? telemetry, ProcessRelationView? relation, HeuristicEventView? heuristic, ThreadLifecycleEventSample? threadLifecycle, IoctlParsedEvent? filesystem, IoctlParsedEvent? registry = null) => new(BackendUiWorkKind.Ioctl, telemetry, relation, heuristic, threadLifecycle, filesystem, registry, null, null); internal static BackendUiWorkItem FromEtw(BrokerEtwEventView etwView) => new(BackendUiWorkKind.Etw, null, null, null, null, null, null, etwView, null); internal static BackendUiWorkItem FromStatus(string statusLine) => new(BackendUiWorkKind.Status, null, null, null, null, null, null, null, statusLine); } private void SaveIntelSessionState(int pid) { if (pid <= 0) { return; } _etwHistoryByPid[pid] = EtwPaneHost.SnapshotItems().Select(x => x.Clone()).ToList(); _heuristicsHistoryByPid[pid] = HeuristicsPaneHost.SnapshotItems().Select(x => x.Clone()).ToList(); _filesystemHistoryByPid[pid] = FilesystemPaneHost.SnapshotItems().Select(x => x.Clone()).ToList(); _registryHistoryByPid[pid] = RegistryPaneHost.SnapshotItems().Select(x => x.Clone()).ToList(); _relationsHistoryByPid[pid] = ProcessRelationsPaneHost.SnapshotItems().Select(x => x.Clone()).ToList(); _apiGraphHistoryByPid[pid] = _apiGraphRowsByKey.Values.Select(static x => x.Clone()).ToList(); _extendedHistoryByPid[pid] = _extendedRowsByKey.Values.Select(static x => x.Clone()).ToList(); } private void RestoreIntelSessionState(int pid) { IEnumerable etw = _etwHistoryByPid.TryGetValue(pid, out var a) ? a : Array.Empty(); IEnumerable heur = _heuristicsHistoryByPid.TryGetValue(pid, out var c) ? c : Array.Empty(); IEnumerable fs = _filesystemHistoryByPid.TryGetValue(pid, out var d) ? d : Array.Empty(); IEnumerable reg = _registryHistoryByPid.TryGetValue(pid, out var dr) ? dr : Array.Empty(); IEnumerable rel = _relationsHistoryByPid.TryGetValue(pid, out var e) ? e : Array.Empty(); IEnumerable apiGraph = _apiGraphHistoryByPid.TryGetValue(pid, out var f) ? f : Array.Empty(); IEnumerable extended = _extendedHistoryByPid.TryGetValue(pid, out var g) ? g : Array.Empty(); EtwPaneHost.LoadHistory(etw.Select(x => x.Clone()).ToList()); HeuristicsPaneHost.LoadHistory(heur.Select(x => x.Clone()).ToList()); FilesystemPaneHost.LoadHistory(fs.Select(x => x.Clone()).ToList()); RegistryPaneHost.LoadHistory(reg.Select(x => x.Clone()).ToList()); ProcessRelationsPaneHost.SetRootPid(pid); ProcessRelationsPaneHost.LoadHistory(rel.Select(x => x.Clone()).ToList()); _apiGraphRowsByKey.Clear(); _extendedRowsByKey.Clear(); _apiGraphReasonByKey.Clear(); _apiGraphActionByKey.Clear(); _apiGraphDecodedByKey.Clear(); _apiGraphFramesByKey.Clear(); _apiGraphSensorByKey.Clear(); _apiGraphTimelineLastEmitByKey.Clear(); _observedHookStackLastPersistByThread.Clear(); _threadStackFallbackLastCaptureByThread.Clear(); _pendingThreadStackFallbackCaptures.Clear(); _apiMemorySignalsByPage.Clear(); _crossProcWriteCountByPair.Clear(); _crossProcRwxAllocCountByPair.Clear(); _antiAnalysisCountByEvidence.Clear(); ResetImageMapCorrelationCaches(); _extendedViewRows.Clear(); foreach (ApiCallGraphRowSnapshot row in apiGraph) { string sensorOrigin = string.IsNullOrWhiteSpace(row.SensorOrigin) ? "Unclassified" : row.SensorOrigin; string callerOrigin = NormalizeApiCallerOrigin(row.CallerOrigin); string originModule = NormalizeApiOriginModule(row.OriginModule); string key = BuildApiGraphKey(row.SourcePid, row.TargetPid, row.ThreadId, row.ApiName, sensorOrigin, callerOrigin, originModule); _apiGraphRowsByKey[key] = row.Clone(); _apiGraphSensorByKey[key] = sensorOrigin; } foreach (ExtendedActivityRowSnapshot row in extended) { string key = BuildExtendedActivityKey(row.TypeLabel, row.ActorLabel, row.TargetLabel, row.SubjectLabel, row.OperationLabel); _extendedRowsByKey[key] = row.Clone(); } PublishApiGraphSnapshot(); PublishExtendedActivitySnapshot(); if (EtwPaneHost.ItemCount > 0) { FindExplorerItem("ETW")?.PushPreviewValue(EtwPaneHost.TotalRawCount); } if (HeuristicsPaneHost.ItemCount > 0) { FindExplorerItem("Heuristics")?.PushPreviewValue(HeuristicsPaneHost.TotalRawCount); } if (FilesystemPaneHost.ItemCount > 0) { FindExplorerItem("Filesystem")?.PushPreviewValue(FilesystemPaneHost.TotalRawCount); } if (RegistryPaneHost.ItemCount > 0) { FindExplorerItem("Registry")?.PushPreviewValue(RegistryPaneHost.TotalRawCount); } if (ProcessRelationsPaneHost.ItemCount > 0) { FindExplorerItem("Process Relations")?.PushPreviewValue(ProcessRelationsPaneHost.TotalRawCount); } RefreshExplorerDataBadges(); } private static System.Windows.Media.Brush BuildApiSensorBackground(string sensor) { return sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x45, 0x1A, 0x1A)) : sensor.StartsWith("Usermode", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x12, 0x2D, 0x4A)) : new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x24, 0x24, 0x24)); } private static System.Windows.Media.Brush BuildApiSensorForeground(string sensor) { return sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xFF, 0xC7, 0xC7)) : sensor.StartsWith("Usermode", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xB9, 0xE3, 0xFF)) : new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xD0, 0xD0, 0xD0)); } private static System.Windows.Media.Brush BuildApiHeatTrackBackground(string sensor, string callerOrigin) { callerOrigin = NormalizeApiCallerOrigin(callerOrigin); return callerOrigin switch { "process-image" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x0F, 0x31, 0x2C)), "non-system-dll" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x38, 0x28, 0x10)), "unbacked" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x3E, 0x10, 0x22)), "system" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x23, 0x28, 0x31)), _ => sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x3A, 0x18, 0x18)) : sensor.StartsWith("Usermode", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x13, 0x27, 0x3D)) : new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x21, 0x26, 0x2D)) }; } private static System.Windows.Media.Brush BuildApiHeatFillBackground(string sensor, string callerOrigin) { callerOrigin = NormalizeApiCallerOrigin(callerOrigin); return callerOrigin switch { "process-image" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x4D, 0xBF, 0xA9)), "non-system-dll" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xD8, 0xA1, 0x45)), "unbacked" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xD1, 0x4E, 0x75)), "system" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x6C, 0x78, 0x88)), _ => sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xB2, 0x4A, 0x4A)) : sensor.StartsWith("Usermode", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x3E, 0x84, 0xC6)) : new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x5C, 0x66, 0x73)) }; } private static System.Windows.Media.Brush BuildApiRowBackground(string sensor, string callerOrigin) { callerOrigin = NormalizeApiCallerOrigin(callerOrigin); return callerOrigin switch { "process-image" => new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x4A, 0x0E, 0x58, 0x4D)), "non-system-dll" => new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x4A, 0x66, 0x43, 0x0D)), "unbacked" => new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x52, 0x71, 0x18, 0x33)), "system" => new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x30, 0x35, 0x3C, 0x45)), _ => sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x44, 0x6E, 0x1D, 0x1D)) : sensor.StartsWith("Usermode", StringComparison.OrdinalIgnoreCase) ? new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x44, 0x16, 0x39, 0x59)) : new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb(0x22, 0x36, 0x36, 0x36)) }; } private static System.Windows.Media.Brush BuildApiRowBorder(string sensor, string callerOrigin) { _ = sensor; callerOrigin = NormalizeApiCallerOrigin(callerOrigin); return callerOrigin switch { "process-image" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x64, 0xD9, 0xC1)), "non-system-dll" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xE2, 0xB0, 0x5E)), "unbacked" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xE0, 0x65, 0x8D)), "system" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x6F, 0x7C, 0x8A)), _ => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x44, 0x4C, 0x56)) }; } private static System.Windows.Media.Brush BuildApiGraphEdgeBrush(string sensor, string callerOrigin, double heat) { heat = Math.Clamp(heat, 0.0, 1.0); callerOrigin = NormalizeApiCallerOrigin(callerOrigin); if (callerOrigin == "process-image") { byte red = (byte)Math.Clamp(62 + (int)Math.Round(28 * heat), 0, 255); byte green = (byte)Math.Clamp(176 + (int)Math.Round(48 * heat), 0, 255); byte blue = (byte)Math.Clamp(152 + (int)Math.Round(40 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(red, green, blue)); } if (callerOrigin == "non-system-dll") { byte red = (byte)Math.Clamp(180 + (int)Math.Round(48 * heat), 0, 255); byte green = (byte)Math.Clamp(128 + (int)Math.Round(40 * heat), 0, 255); byte blue = (byte)Math.Clamp(52 + (int)Math.Round(18 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(red, green, blue)); } if (callerOrigin == "unbacked") { byte red = (byte)Math.Clamp(194 + (int)Math.Round(38 * heat), 0, 255); byte green = (byte)Math.Clamp(68 + (int)Math.Round(22 * heat), 0, 255); byte blue = (byte)Math.Clamp(112 + (int)Math.Round(32 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(red, green, blue)); } if (callerOrigin == "system") { byte shade = (byte)Math.Clamp(118 + (int)Math.Round(52 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromRgb(shade, shade, (byte)Math.Clamp(shade + 8, 0, 255))); } if (sensor.StartsWith("Kernel", StringComparison.OrdinalIgnoreCase)) { byte red = (byte)Math.Clamp(150 + (int)Math.Round(80 * heat), 0, 255); byte green = (byte)Math.Clamp(65 + (int)Math.Round(25 * heat), 0, 255); byte blue = (byte)Math.Clamp(65 + (int)Math.Round(25 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(red, green, blue)); } if (sensor.StartsWith("Usermode", StringComparison.OrdinalIgnoreCase)) { byte red = (byte)Math.Clamp(70 + (int)Math.Round(25 * heat), 0, 255); byte green = (byte)Math.Clamp(125 + (int)Math.Round(45 * heat), 0, 255); byte blue = (byte)Math.Clamp(178 + (int)Math.Round(50 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(red, green, blue)); } byte neutralShade = (byte)Math.Clamp(110 + (int)Math.Round(70 * heat), 0, 255); return new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromRgb(neutralShade, neutralShade, neutralShade)); } private static System.Windows.Media.Brush BuildApiCallerOriginBackground(string callerOrigin) { return NormalizeApiCallerOrigin(callerOrigin) switch { "process-image" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x14, 0x42, 0x39)), "non-system-dll" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x4D, 0x34, 0x12)), "unbacked" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x56, 0x16, 0x2D)), "system" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x2C, 0x33, 0x3B)), _ => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0x26, 0x26, 0x26)) }; } private static System.Windows.Media.Brush BuildApiCallerOriginForeground(string callerOrigin) { return NormalizeApiCallerOrigin(callerOrigin) switch { "process-image" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xC0, 0xFF, 0xEE)), "non-system-dll" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xFF, 0xE1, 0xB0)), "unbacked" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xFF, 0xC2, 0xD4)), "system" => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xD2, 0xDA, 0xE2)), _ => new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xD8, 0xD8, 0xD8)) }; } private async Task RunPreflightAsync(int pid, bool userInitiated = false) { BlackbirdPreflightReport report; try { report = await Task.Run(() => BlackbirdPreflight.Run(pid)); } catch (Exception ex) { report = new BlackbirdPreflightReport { CheckedUtc = DateTime.UtcNow, DriverState = "Unknown", ControllerState = "Unknown", Error = ex.Message }; DiagnosticsState.SetValue("Preflight", $"failed: {ex.Message}"); OutputCapture.AppendLine($"Preflight failed: {ex}"); } _lastPreflight = report; OutputCapture.AppendLine($"Preflight: {report.Summary}"); DiagnosticsState.SetValue("Driver Service", report.DriverDisplayState); DiagnosticsState.SetValue("Controller Service", report.ControllerState); DiagnosticsState.SetValue("Broker Caps", $"0x{report.BrokerCapabilities:X8}"); DiagnosticsState.SetValue("Broker TI", report.ThreatIntelEnabled ? "Enabled" : "Disabled"); DiagnosticsState.SetValue("Broker TI Enable Err", report.ThreatIntelEnableError.ToString()); DiagnosticsState.SetValue("Hook DLL", report.HookDllExists ? "Found" : $"Missing ({report.HookDllPath})"); ApplyConnectivityStatus(report, userInitiated); } private void ApplyConnectivityStatus(BlackbirdPreflightReport report, bool userInitiated) { var issues = new List(); if (!string.IsNullOrWhiteSpace(report.Error)) { issues.Add(report.Error); } if (!report.DriverRuntimeReady) { issues.Add($"driver service={report.DriverState}"); } if (!string.Equals(report.ControllerState, "Running", StringComparison.OrdinalIgnoreCase)) { issues.Add($"controller service={report.ControllerState}"); } if (!report.BrokerConnectOk) { issues.Add("broker link down"); } if (!report.DriverProxyOk) { issues.Add("driver proxy unavailable"); } if (!report.HookDllExists) { issues.Add($"hook dll missing ({report.HookDllPath})"); } if (report.EtwUplinkCapable && !report.EtwUplinkQueryOk) { issues.Add("ETW uplink query failed"); } if (issues.Count == 0) { _lastConnectivityIssueSignature = null; DiagnosticsState.SetValue("Connectivity", "OK"); SetBackendConnectivity(true); return; } string detail = string.Join("; ", issues); string signature = detail; string msg = $"Could not fully connect to driver/service: {detail}"; DiagnosticsState.SetValue("Connectivity", $"FAILED: {detail}"); OutputCapture.AppendLine(msg); SetBackendConnectivity(false); bool shouldWarn = userInitiated || !string.Equals(_lastConnectivityIssueSignature, signature, StringComparison.Ordinal); if (shouldWarn) { ThemedMessageBox.Show(this, $"Could not fully connect to the driver/service uplink.\n\n{detail}", "Blackbird Connectivity", MessageBoxButton.OK, MessageBoxImage.Warning); } _lastConnectivityIssueSignature = signature; } private async void Preflight_Click(object sender, RoutedEventArgs e) { StatusBlock.Text = "Status: Running preflight..."; await RunPreflightAsync(TryGetPid(), userInitiated: true); if (_lastPreflight == null) { StatusBlock.Text = "Status: Preflight unavailable"; } } private void RegisterVm_Click(object sender, RoutedEventArgs e) { var window = new VmRegistrationWindow { Owner = this }; window.ShowDialog(); } private async void DriverStart_Click(object sender, RoutedEventArgs e) { string message = ""; bool ok = await Task.Run( () => BlackbirdServiceControl.TryStart("blackbird", TimeSpan.FromSeconds(8), out message)); OutputCapture.AppendLine(message); if (!ok) { StatusBlock.Text = "Status: Failed to start blackbird"; return; } await RunPreflightAsync(TryGetPid(), userInitiated: true); if (_lastConnectivityIssueSignature == null) { StatusBlock.Text = "Status: Driver started"; } } private async void DriverStop_Click(object sender, RoutedEventArgs e) { if (ThemedMessageBox.Show(this, "Stop the kernel driver 'blackbird'?", "Driver Stop", MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes) { return; } StopBackendSession(); string message = ""; bool ok = await Task.Run( () => BlackbirdServiceControl.TryStop("blackbird", TimeSpan.FromSeconds(8), out message)); OutputCapture.AppendLine(message); StatusBlock.Text = ok ? "Status: Driver stopped" : "Status: Failed to stop blackbird"; await RunPreflightAsync(TryGetPid(), userInitiated: true); } private async void ControllerRestart_Click(object sender, RoutedEventArgs e) { StopBackendSession(); string message = ""; bool ok = await Task.Run( () => BlackbirdServiceControl.TryRestart("BlackbirdController", TimeSpan.FromSeconds(10), out message)); OutputCapture.AppendLine(message); if (!ok) { StatusBlock.Text = "Status: Controller restart failed"; return; } await RunPreflightAsync(TryGetPid(), userInitiated: true); int pid = TryGetPid(); bool useUsermodeHooks = _currentSession?.UseUsermodeHooks ?? false; if (_currentSession != null && !_currentSession.OfflineSnapshot && !_currentSession.TargetExited && _currentSession.Pid == pid) { EnsureLiveCaptureStoreForCurrentSession(pid); StartBackendForPid(pid, useUsermodeHooks, stopExistingSession: false); } else { StartBackendForPid(pid, useUsermodeHooks); } if (_lastConnectivityIssueSignature == null) { StatusBlock.Text = "Status: Controller restarted"; } } private sealed class RegionLifecycleState { public ulong ProcessStartKey { get; set; } public uint TargetPid { get; set; } public ulong BaseAddress { get; set; } public ulong RegionSize { get; set; } public string RegionKind { get; set; } = string.Empty; public string ExecutionContext { get; set; } = string.Empty; public bool ObservedByKernel { get; set; } public bool ObservedByUserHook { get; set; } public bool BlackbirdOwned { get; set; } public bool CrossProcess { get; set; } public bool ImageBacked { get; set; } public uint InitialProtection { get; set; } public uint PreviousProtection { get; set; } public uint CurrentProtection { get; set; } public bool FirstExecutableTransitionSeen { get; set; } public bool FirstThreadStartSeen { get; set; } public ulong FirstThreadStartAddress { get; set; } public bool FunctionTableRegistered { get; set; } public uint MapCount { get; set; } public uint WriteCount { get; set; } public uint ProtectCount { get; set; } public uint ThreadStartCount { get; set; } public uint ProtectFlipCount { get; set; } public uint RapidProtectFlipCount { get; set; } public uint ExecutableFlipCount { get; set; } public uint GuardNoAccessFlipCount { get; set; } public uint WritableExecutableFlipCount { get; set; } public string LastProtectionTransition { get; set; } = string.Empty; public DateTime LastProtectChangeUtc { get; set; } public double LastEntropyBits { get; set; } = -1; public double MaxEntropyBits { get; set; } = -1; public uint EntropyFlipCount { get; set; } public uint RapidEntropyFlipCount { get; set; } public uint HighEntropyWriteCount { get; set; } public DateTime LastEntropyChangeUtc { get; set; } public uint LastSampleBytes { get; set; } } } }