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

8674 lines
408 KiB
C#

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<IoctlParsedEvent>? _sessionIoctlHandler;
private Action<BlackbirdNative.BkIpcEtwEvent>? _sessionEtwHandler;
private Action<BackendStatsView>? _sessionStatsHandler;
private Action<string>? _sessionStatusHandler;
private CaptureProjectionEngine? _captureProjection;
private readonly Dictionary<int, List<GroupedEventRow>> _etwHistoryByPid = new();
private readonly Dictionary<int, List<GroupedEventRow>> _heuristicsHistoryByPid = new();
private readonly Dictionary<int, List<GroupedEventRow>> _filesystemHistoryByPid = new();
private readonly Dictionary<int, List<GroupedEventRow>> _registryHistoryByPid = new();
private readonly Dictionary<int, List<GroupedEventRow>> _relationsHistoryByPid = new();
private readonly Dictionary<int, List<ApiCallGraphRowSnapshot>> _apiGraphHistoryByPid = new();
private readonly Dictionary<int, List<ExtendedActivityRowSnapshot>> _extendedHistoryByPid = new();
private readonly Dictionary<string, ApiCallGraphRowSnapshot> _apiGraphRowsByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, ExtendedActivityRowSnapshot> _extendedRowsByKey =
new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _apiGraphReasonByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _apiGraphActionByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _apiGraphDecodedByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _apiGraphFramesByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _apiGraphSensorByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, DateTime> _apiGraphTimelineLastEmitByKey = new(StringComparer.Ordinal);
private readonly Dictionary<string, DateTime> _observedHookStackLastPersistByThread =
new(StringComparer.Ordinal);
private readonly Dictionary<string, DateTime> _threadStackFallbackLastCaptureByThread =
new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, byte> _pendingThreadStackFallbackCaptures =
new(StringComparer.Ordinal);
private readonly Dictionary<ulong, ApiMemoryPageSignal> _apiMemorySignalsByPage = new();
private readonly Dictionary<uint, string> _targetExitReasonByPid = new();
private long _usermodeHookEventCount;
private long _kernelHookEventCount;
private bool _apiGraphSnapshotDirty;
private bool _extendedViewSnapshotDirty;
private BlackbirdPreflightReport? _lastPreflight;
private string? _lastConnectivityIssueSignature;
private readonly ConcurrentQueue<IoctlParsedEvent> _pendingIoctlEvents = new();
private readonly ConcurrentQueue<BlackbirdNative.BkIpcEtwEvent> _pendingEtwEvents = new();
private readonly ConcurrentQueue<string> _pendingStatusLines = new();
private readonly ConcurrentQueue<BackendUiWorkItem> _pendingUiWork = new();
private readonly AutoResetEvent _backendTransformSignal = new(false);
private CancellationTokenSource? _backendTransformCts;
private Task? _backendTransformTask;
private readonly ConcurrentDictionary<uint, byte> _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<ulong, IoctlParsedEvent> _recentHandleEvidenceByPair = new();
private DateTime _lastHandleEvidencePruneUtc = DateTime.MinValue;
private readonly Dictionary<string, int> _filesystemClusterOperationCounts = new(StringComparer.Ordinal);
private readonly Dictionary<string, (uint Pid, uint Tid, string Path, uint Operation)>
_filesystemClusterSamplesByOperation = new(StringComparer.Ordinal);
private readonly Dictionary<string, int> _crossProcWriteCountByPair = new(StringComparer.Ordinal);
private readonly Dictionary<string, int> _crossProcRwxAllocCountByPair = new(StringComparer.Ordinal);
private readonly Dictionary<string, int> _antiAnalysisCountByEvidence = new(StringComparer.OrdinalIgnoreCase);
private readonly object _imageMapCorrelationLock = new();
private readonly List<RecentImageFileAccess> _recentImageFileAccesses = new();
private readonly Dictionary<string, RecentImageMapState> _recentImageMapByPidPath =
new(StringComparer.OrdinalIgnoreCase);
private DateTime _lastImageMapCorrelationPruneUtc = DateTime.MinValue;
private readonly Dictionary<uint, DateTime> _observedProcessStartUtcByPid = new();
private readonly Dictionary<uint, ulong> _observedProcessStartKeyByPid = new();
private readonly Dictionary<uint, uint> _observedInitialThreadIdByPid = new();
private readonly Dictionary<string, RegionLifecycleState> _regionLifecycleByIdentity =
new(StringComparer.Ordinal);
private readonly Dictionary<string, ulong> _functionTableBaseByPointer = new(StringComparer.Ordinal);
private volatile int _filterRootPid;
private readonly ConcurrentDictionary<uint, byte> _filterTrackedPids = new();
private volatile int _focusedPid;
internal ObservableCollection<MonitoredProcessEntry> MonitoredProcesses { get; } = new();
private readonly HashSet<uint> _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<TelemetryEvent> filesystemClusterEvents =
acceptFilesystem ? AccumulateFilesystemTimelineCluster(ioctl, nowUtc)
: Array.Empty<TelemetryEvent>();
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<HeuristicEventView> signatureIntelIoctlFindings =
ShouldEvaluateSignatureIntelForIoctl(ioctl, acceptFilesystem)
? EvaluateSignatureIntelForIoctl(ioctl)
: Array.Empty<HeuristicEventView>();
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<TelemetryEvent> 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<TelemetryEvent>(96);
var relationBatch = new List<ProcessRelationView>(32);
var heuristicBatch = new List<HeuristicEventView>(32);
var threadLifecycleBatch = new List<ThreadLifecycleEventSample>(32);
var filesystemBatch = new List<IoctlParsedEvent>(32);
var registryBatch = new List<IoctlParsedEvent>(32);
var etwBatch = new List<BrokerEtwEventView>(96);
var statusBatch = new List<string>(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<string, string> 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<string, string> 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<string, string> 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=<none>";
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<string, string> 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=<none> ";
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=<none> ";
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<string>();
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 "<none>";
}
int safeCount = Math.Min(frames.Length, (int)frameCount);
var list = new List<string>(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 ? "<none>" : 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 "<unknown>";
}
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) ? "<unknown>" : 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<TelemetryEvent> FlushFilesystemTimelineClusterIfNeeded(DateTime nowUtc, bool force)
{
if (_filesystemClusterTotal <= 0)
{
return Array.Empty<TelemetryEvent>();
}
if (!force && (nowUtc - _filesystemClusterLastSeenUtc) < FilesystemTimelineClusterIdleFlush)
{
return Array.Empty<TelemetryEvent>();
}
double windowMs =
Math.Max(1, (_filesystemClusterLastSeenUtc - _filesystemClusterWindowStartUtc).TotalMilliseconds);
var emitted = new List<TelemetryEvent>(_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, "<unknown>", 0u);
string operationName = DescribeFileOperation(sample.Operation);
string samplePath = string.IsNullOrWhiteSpace(sample.Path) ? "<unknown>" : 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<TelemetryEvent> AccumulateFilesystemTimelineCluster(IoctlParsedEvent record,
DateTime nowUtc)
{
if (record.Type != BlackbirdNative.EventTypeFileSystem)
{
return Array.Empty<TelemetryEvent>();
}
var emitted = new List<TelemetryEvent>();
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) ? "<unknown>" : record.FilePath, record.FileOperation);
}
if (_filesystemClusterTotal >= FilesystemTimelineClusterFlushCount)
{
emitted.AddRange(FlushFilesystemTimelineClusterIfNeeded(nowUtc, force: true));
}
return emitted.Count == 0 ? Array.Empty<TelemetryEvent>() : 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<string>(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 ? "<none>" : 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<BrokerEtwEventView> views)
{
if (views.Count == 0)
{
return;
}
var etwRows = new List<BrokerEtwEventView>(views.Count);
var heuristics = new List<HeuristicEventView>();
var relations = new List<ProcessRelationView>();
var memoryAttributions = new List<MemoryRegionAttributionSample>(16);
var timelineEvents = new List<TelemetryEvent>(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<HeuristicEventView> 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<ModuleInfoRow> 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<string, string> 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<ulong>();
int count = Math.Min(Math.Min((int)view.StackCount, stack.Length), BlackbirdNative.MaxIpcStackFrames);
if (count <= 0)
{
return null;
}
Dictionary<string, string> fields = BuildHookFieldMap(view);
var frames = new List<StackFrameRow>(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<string, string> 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) ? "<none>" : view.Reason;
uint sanitizedCorrFlags = view.CorrelationFlags & CorrelationIntentMask;
string corrFlagsDecoded = EventDetailFormatting.DescribeCorrelationFlags(sanitizedCorrFlags);
string corrAccessDecoded = EventDetailFormatting.DescribeHandleAccess(view.CorrelationAccessMask);
string heuristicEvidence = "<none>";
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<string, string> 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<GroupedEventRow> sourceGroups =
EtwPaneHost.ItemCount > 0
? EtwPaneHost.SnapshotItems()
: (_currentSession != null && _etwHistoryByPid.TryGetValue(_currentSession.Pid, out var history)
? history
: Array.Empty<GroupedEventRow>());
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<ExtendedActivityRowSnapshot> 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<ApiCallGraphMainRowView> sourceRows =
_apiViewPresentationMode == ApiViewPresentationMode.ThreadTimeline
? BuildApiThreadTimelineRows(_apiGraphSnapshotRows)
: BuildApiCallGraphRows(_apiGraphSnapshotRows);
_apiViewSnapshotRows.Clear();
_apiViewSnapshotRows.AddRange(sourceRows);
List<ApiCallGraphMainRowView> 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<ApiCallGraphMainRowView> filteredRows = ApplyApiViewFilters(_apiViewSnapshotRows);
RenderApiPresentationCanvas(filteredRows, selectedKey);
}
private List<ApiCallGraphMainRowView> BuildApiCallGraphRows(IReadOnlyList<ApiCallGraphRowSnapshot> snapshot)
{
int maxHits = Math.Max(1, snapshot.Count == 0 ? 1 : snapshot.Max(x => Math.Max(1, x.Hits)));
var rows = new List<ApiCallGraphMainRowView>(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<ApiCallGraphMainRowView>
BuildApiThreadTimelineRows(IReadOnlyList<ApiCallGraphRowSnapshot> 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<ApiCallGraphMainRowView>(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<ApiCallGraphMainRowView> rows, string? selectedKey)
{
if (_apiViewPresentationMode == ApiViewPresentationMode.ThreadTimeline)
{
RenderApiThreadTimelineCanvas(rows, selectedKey);
return;
}
var byKey = rows.ToDictionary(x => x.GraphKey, StringComparer.Ordinal);
List<ApiCallGraphRowSnapshot> 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<ThreadLifecycleEventSample> 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<ApiCallGraphRowSnapshot> 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<string>(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<ApiCallGraphMainRowView> 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<ApiCallGraphMainRowView> ApplyApiViewFilters(IEnumerable<ApiCallGraphMainRowView> 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<string, string> 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<ApiCallGraphRowSnapshot> 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<ApiCallGraphRowSnapshot> 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<uint, System.Windows.Point>();
var apiPositions = new Dictionary<string, System.Windows.Point>(StringComparer.OrdinalIgnoreCase);
var targetPositions = new Dictionary<uint, System.Windows.Point>();
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<string> BuildHookFrameList(BrokerEtwEventView view,
IReadOnlyDictionary<string, string> fields)
{
ulong[] stack = view.Stack ?? Array.Empty<ulong>();
var frames = new List<string>(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<string, string> fields)
{
List<string> 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<string, string> fields) =>
AppendThreadStackFallbackSummary(view, BuildHookFrameSummaryCore(view, fields));
private static string BuildHookFrameSummaryCore(BrokerEtwEventView view,
IReadOnlyDictionary<string, string> 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<ulong>();
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<string> 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<ThreadStackSessionSnapshot> 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<string, string> 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<string, string> fields,
string originModule)
{
ulong[] stack = view.Stack ?? Array.Empty<ulong>();
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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string>(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<byte>();
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<byte>();
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<string, string> fields = BuildHookFieldMap(rawReason, Array.Empty<ulong>(), 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<string, string> 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<string, string> ParseReasonFields(string rawReason)
{
return EventDetailsParsing.ParseRawFields(rawReason);
}
private static Dictionary<string, string> BuildHookFieldMap(string rawReason, IReadOnlyList<ulong>? hookArgs,
uint hookArgCount)
{
Dictionary<string, string> 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<string, string>
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<string, string> 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<string, string> 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<string, string> 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<string, string> fields,
params string[] keys)
{
ulong value = FirstU64(fields, keys);
return value == 0 ? string.Empty : FormatObservedPointer(view, value);
}
private static string ResolveSize(IReadOnlyDictionary<string, string> fields, params string[] keys)
{
ulong value = FirstU64(fields, keys);
return value == 0 ? string.Empty : $"0x{value:X}";
}
private static string ResolveHex(IReadOnlyDictionary<string, string> fields, params string[] keys)
{
ulong value = FirstU64(fields, keys);
return value == 0 ? string.Empty : $"0x{value:X}";
}
private static string ResolveProtect(IReadOnlyDictionary<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string>(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 ? "<none>" : 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<int> 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<GroupedEventRow> etw =
_etwHistoryByPid.TryGetValue(pid, out var a) ? a : Array.Empty<GroupedEventRow>();
IEnumerable<GroupedEventRow> heur =
_heuristicsHistoryByPid.TryGetValue(pid, out var c) ? c : Array.Empty<GroupedEventRow>();
IEnumerable<GroupedEventRow> fs =
_filesystemHistoryByPid.TryGetValue(pid, out var d) ? d : Array.Empty<GroupedEventRow>();
IEnumerable<GroupedEventRow> reg =
_registryHistoryByPid.TryGetValue(pid, out var dr) ? dr : Array.Empty<GroupedEventRow>();
IEnumerable<GroupedEventRow> rel =
_relationsHistoryByPid.TryGetValue(pid, out var e) ? e : Array.Empty<GroupedEventRow>();
IEnumerable<ApiCallGraphRowSnapshot> apiGraph =
_apiGraphHistoryByPid.TryGetValue(pid, out var f) ? f : Array.Empty<ApiCallGraphRowSnapshot>();
IEnumerable<ExtendedActivityRowSnapshot> extended =
_extendedHistoryByPid.TryGetValue(pid, out var g) ? g : Array.Empty<ExtendedActivityRowSnapshot>();
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<string>();
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; }
}
}
}