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

1725 lines
69 KiB
C#

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
namespace BlackbirdInterface
{
public partial class ProcessPickerWindow : Window
{
private readonly int _currentPid = Environment.ProcessId;
private readonly List<ProcessItem> _all = new();
private readonly ObservableCollection<ProcessItem> _view = new();
private readonly ObservableCollection<ProcessFilterRule> _rules = new();
private readonly Dictionary<int, (long cpu100ns, DateTime ts)> _cpuBaseline = new();
private readonly Dictionary<string, SignatureTrustState> _signatureCache =
new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ProcessPathMetadata> _pathMetadataCache =
new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ImageSource?> _iconCache = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<int, ProcessEnrichmentSnapshot> _processEnrichmentCache = new();
private readonly object _cacheLock = new();
private readonly ImageSource? _defaultProcessIcon;
private readonly DispatcherTimer _refreshTimer;
private CancellationTokenSource? _enrichmentCts;
private int _refreshGeneration;
private bool _isReady;
private bool _initialListPrepared;
private bool _firstRevealComplete;
private string? _activeSortProperty;
private ListSortDirection _activeSortDirection = ListSortDirection.Ascending;
private Brush _rowDefaultForeground = Brushes.Black;
private Brush _rowUnsignedBackground = Brushes.Transparent;
private Brush _rowUnsignedBorder = Brushes.Transparent;
private Brush _rowUnsignedForeground = Brushes.Black;
private Brush _rowAppContainerBackground = Brushes.Transparent;
private Brush _rowAppContainerBorder = Brushes.Transparent;
private Brush _rowAppContainerForeground = Brushes.Black;
private Brush _rowLowIntegrityBackground = Brushes.Transparent;
private Brush _rowLowIntegrityBorder = Brushes.Transparent;
private Brush _rowElevatedBackground = Brushes.Transparent;
private Brush _rowElevatedBorder = Brushes.Transparent;
private Brush _rowSystemBackground = Brushes.Transparent;
private Brush _rowSystemBorder = Brushes.Transparent;
private Brush _rowSystemForeground = Brushes.Black;
private bool _showLaunchOptions;
public int SelectedPid { get; private set; }
public bool UseUsermodeHooks { get; private set; }
public bool AutoOpenApiGraphWindow { get; private set; }
public bool UseEarlyBirdApcLaunch { get; private set; }
public bool LaunchSelectedImage { get; private set; }
public string LaunchImagePath { get; private set; } = string.Empty;
public LaunchTargetKind SelectedLaunchTargetKind { get; private set; } = LaunchTargetKind.Executable;
public bool ShowLaunchOptions
{
get => _showLaunchOptions;
set {
_showLaunchOptions = value;
UpdateLaunchOptionsUi();
}
}
public ProcessPickerWindow()
{
InitializeComponent();
Opacity = 0;
WindowThemeHelper.WireThemeAwareTitleBar(this);
RefreshThemePalette();
_defaultProcessIcon = GetDefaultProcessIcon();
ProcessGrid.ItemsSource = _view;
RulesGrid.ItemsSource = _rules;
UpdateRulesPanelVisibility();
_refreshTimer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromSeconds(2) };
_refreshTimer.Tick += (_, __) => RefreshList();
Loaded += async (_, __) =>
{
await RevealAfterFirstRenderAsync();
await Dispatcher.InvokeAsync(
() =>
{
EnsureInitialListPrepared();
_refreshTimer.Start();
},
DispatcherPriority.Background);
};
Closed += (_, __) =>
{
_refreshTimer.Stop();
_enrichmentCts?.Cancel();
_enrichmentCts?.Dispose();
_enrichmentCts = null;
App.ThemeChanged -= OnThemeChanged;
};
App.ThemeChanged += OnThemeChanged;
_isReady = true;
UpdateLaunchOptionsUi();
}
private void UpdateLaunchOptionsUi()
{
if (LaunchOptionsPanel == null)
{
return;
}
LaunchOptionsPanel.Visibility = Visibility.Collapsed;
}
private void UseUsermodeHooksCheckBox_Changed(object sender, RoutedEventArgs e)
{
UpdateLaunchOptionsUi();
}
private void Root_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
WindowChromeBehavior.HandleRootDragMove(this, e);
}
public void PrimeForFirstShow()
{
EnsureInitialListPrepared();
}
private void EnsureInitialListPrepared()
{
if (_initialListPrepared)
return;
RefreshList();
_initialListPrepared = true;
}
private async Task RevealAfterFirstRenderAsync()
{
if (_firstRevealComplete)
return;
await Dispatcher.InvokeAsync(() =>
{},
DispatcherPriority.Render);
Opacity = 1;
Activate();
_firstRevealComplete = true;
}
private void Refresh_Click(object sender, RoutedEventArgs e) => RefreshList();
private void QuickSearchBox_TextChanged(object sender, TextChangedEventArgs e) => ApplyFilter();
private void FilterValueBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter)
return;
AddRule_Click(sender, new RoutedEventArgs());
e.Handled = true;
}
private void AddRule_Click(object sender, RoutedEventArgs e)
{
if (!_isReady || FilterColumnBox == null || FilterRelationBox == null || FilterActionBox == null ||
FilterValueBox == null)
return;
string column = (FilterColumnBox.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "Process Name";
string relation = (FilterRelationBox.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "is";
string action = (FilterActionBox.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "Include";
string value = FilterValueBox.Text?.Trim() ?? "";
if (string.IsNullOrWhiteSpace(value))
return;
_rules.Add(new ProcessFilterRule { Enabled = true, Column = column, Relation = relation, Value = value,
Action = action });
FilterValueBox.Text = "";
UpdateRulesPanelVisibility();
ApplyFilter();
}
private void RemoveRule_Click(object sender, RoutedEventArgs e)
{
if (RulesGrid.SelectedItem is not ProcessFilterRule rule)
return;
_rules.Remove(rule);
UpdateRulesPanelVisibility();
ApplyFilter();
}
private void RulesGrid_CurrentCellChanged(object? sender, EventArgs e) => ApplyFilter();
private void RuleEnabled_Click(object sender, RoutedEventArgs e) => ApplyFilter();
private void MoveRuleUp_Click(object sender, RoutedEventArgs e)
{
MoveRule(-1);
}
private void MoveRuleDown_Click(object sender, RoutedEventArgs e)
{
MoveRule(1);
}
private void ResetRules_Click(object sender, RoutedEventArgs e)
{
_rules.Clear();
UpdateRulesPanelVisibility();
if (QuickSearchBox != null)
QuickSearchBox.Text = "";
if (FilterValueBox != null)
FilterValueBox.Text = "";
ApplyFilter();
}
private void RefreshList()
{
int selectedPid = (ProcessGrid.SelectedItem as ProcessItem)?.Pid ?? 0;
int selectedIndex = ProcessGrid.SelectedIndex;
var list = new List<ProcessItem>();
var snapshot = QuerySystemProcessesFast();
EnsureCurrentProcessVisible(snapshot);
var presentPids = new HashSet<int>(snapshot.Select(s => s.Pid));
var now = DateTime.UtcNow;
foreach (var entry in snapshot)
{
ProcessEnrichmentSnapshot? cached = null;
lock (_cacheLock)
{
if (_processEnrichmentCache.TryGetValue(entry.Pid, out var value))
cached = value;
}
bool isChild = entry.ParentPid > 0;
double cpuPct = TryGetCpuPercent(entry.Pid, entry.CpuTime100ns, now);
var row = new ProcessItem { Name = entry.Name,
AppName = cached?.AppName ?? entry.Name,
Company = cached?.Company ?? "",
FileName = cached?.FileName ?? "",
Icon = cached?.Icon ?? _defaultProcessIcon,
Architecture = cached?.Architecture ?? "",
Pid = entry.Pid,
ParentPid = entry.ParentPid,
CpuPercentValue = cpuPct,
CpuPercent = cpuPct.ToString("0.0", CultureInfo.InvariantCulture),
IntegrityLevel = cached?.IntegrityLevel ?? "",
IsAppContainer = cached?.IsAppContainer ?? false,
SandboxStatus = cached?.SandboxStatus ?? "",
IsSigned = cached?.IsSigned ?? false,
IsUnsigned = cached?.IsUnsigned ?? false,
SignedStatus = cached?.SignedStatus ?? "",
SignatureState = cached?.SignatureState ?? SignatureTrustState.Unknown,
IsSystemOrWindows = cached?.IsSystemOrWindows ?? false,
Relation = entry.Pid == _currentPid ? "Self" : (isChild ? "Child" : "Head"),
Path = cached?.Path ?? "" };
ApplyRowTheme(row);
list.Add(row);
}
foreach (var stale in _cpuBaseline.Keys.Where(k => !presentPids.Contains(k)).ToList())
_cpuBaseline.Remove(stale);
lock (_cacheLock)
{
foreach (var stale in _processEnrichmentCache.Keys.Where(k => !presentPids.Contains(k)).ToList())
_processEnrichmentCache.Remove(stale);
}
var byPid = list.ToDictionary(x => x.Pid, x => x.Name);
foreach (var item in list)
{
if (item.ParentPid > 0 && byPid.TryGetValue(item.ParentPid, out var parentName))
item.ParentName = $"{parentName} ({item.ParentPid})";
else
item.ParentName = item.ParentPid > 0 ? item.ParentPid.ToString(CultureInfo.InvariantCulture) : "";
}
_all.Clear();
_all.AddRange(list.OrderBy(x => x.IsUnsigned ? 0 : 1)
.ThenBy(x => x.IsAppContainer ? 0 : 1)
.ThenBy(x => x.IsSystemOrWindows ? 1 : 0)
.ThenByDescending(x => x.CpuPercentValue)
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase));
ApplyFilter(selectedPid, selectedIndex);
StartDeferredEnrichment(++_refreshGeneration);
if (selectedPid > 0)
{
var selected = _view.FirstOrDefault(x => x.Pid == selectedPid);
if (selected != null)
ProcessGrid.SelectedItem = selected;
}
}
private void RefreshIntervalBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_isReady || RefreshIntervalBox.SelectedItem is not ComboBoxItem item)
return;
string label = item.Content?.ToString() ?? "2s";
int sec = label.StartsWith("1", StringComparison.Ordinal) ? 1
: label.StartsWith("5", StringComparison.Ordinal) ? 5
: 2;
_refreshTimer.Interval = TimeSpan.FromSeconds(sec);
}
private void ApplyFilter(int pinnedPid = 0, int pinnedIndex = -1)
{
if (!_isReady || ProcessGrid == null)
return;
if (pinnedPid <= 0 && ProcessGrid.SelectedItem is ProcessItem selected)
{
pinnedPid = selected.Pid;
pinnedIndex = ProcessGrid.SelectedIndex;
}
string q = QuickSearchBox?.Text?.Trim() ?? "";
IEnumerable<ProcessItem> filtered = _all;
if (!string.IsNullOrWhiteSpace(q))
{
filtered = filtered.Where(
x => x.Name.Contains(q, StringComparison.OrdinalIgnoreCase) ||
x.AppName.Contains(q, StringComparison.OrdinalIgnoreCase) ||
x.Company.Contains(q, StringComparison.OrdinalIgnoreCase) ||
x.FileName.Contains(q, StringComparison.OrdinalIgnoreCase) ||
x.Architecture.Contains(q, StringComparison.OrdinalIgnoreCase) ||
x.Pid.ToString(CultureInfo.InvariantCulture).Contains(q, StringComparison.OrdinalIgnoreCase) ||
x.Path.Contains(q, StringComparison.OrdinalIgnoreCase));
}
if (_rules.Count > 0)
filtered = filtered.Where(PassesRules);
if (!string.IsNullOrWhiteSpace(_activeSortProperty))
{
filtered = ApplyActiveSort(filtered);
}
List<ProcessItem> filteredList = filtered.ToList();
if (pinnedPid > 0 && pinnedIndex >= 0)
{
int selectedPos = filteredList.FindIndex(x => x.Pid == pinnedPid);
if (selectedPos >= 0)
{
ProcessItem pinned = filteredList[selectedPos];
filteredList.RemoveAt(selectedPos);
int targetIndex = Math.Min(pinnedIndex, filteredList.Count);
filteredList.Insert(targetIndex, pinned);
}
}
_view.Clear();
foreach (var item in filteredList)
_view.Add(item);
if (pinnedPid > 0)
{
ProcessItem? selectedAfter = _view.FirstOrDefault(x => x.Pid == pinnedPid);
if (selectedAfter != null)
{
ProcessGrid.SelectedItem = selectedAfter;
}
}
}
private IEnumerable<ProcessItem> ApplyActiveSort(IEnumerable<ProcessItem> source)
{
Func < ProcessItem,
object ? > selector =
_activeSortProperty switch { nameof(ProcessItem.Name) => x => x.Name,
nameof(ProcessItem.AppName) => x => x.AppName,
nameof(ProcessItem.Company) => x => x.Company,
nameof(ProcessItem.FileName) => x => x.FileName,
nameof(ProcessItem.Architecture) => x => x.Architecture,
nameof(ProcessItem.Pid) => x => x.Pid,
nameof(ProcessItem.CpuPercentValue) => x => x.CpuPercentValue,
nameof(ProcessItem.IntegrityLevel) => x => x.IntegrityLevel,
nameof(ProcessItem.SignedStatus) => x => x.SignedStatus,
nameof(ProcessItem.SandboxStatus) => x => x.SandboxStatus,
nameof(ProcessItem.Relation) => x => x.Relation,
nameof(ProcessItem.ParentName) => x => x.ParentName,
nameof(ProcessItem.Path) => x => x.Path,
_ => x => x.Name };
return _activeSortDirection == ListSortDirection.Ascending
? source.OrderBy(selector).ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
: source.OrderByDescending(selector).ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase);
}
private bool PassesRules(ProcessItem item)
{
var active = _rules.Where(r => r.Enabled).ToList();
if (active.Count == 0)
return true;
bool hasIncludeRules = active.Any(r => r.Action.Equals("Include", StringComparison.OrdinalIgnoreCase));
bool include = !hasIncludeRules;
foreach (var rule in active)
{
if (!RuleMatches(item, rule))
continue;
include = rule.Action.Equals("Include", StringComparison.OrdinalIgnoreCase);
}
return include;
}
private void MoveRule(int delta)
{
if (RulesGrid.SelectedItem is not ProcessFilterRule rule)
return;
int oldIndex = _rules.IndexOf(rule);
if (oldIndex < 0)
return;
int newIndex = oldIndex + delta;
if (newIndex < 0 || newIndex >= _rules.Count)
return;
_rules.Move(oldIndex, newIndex);
RulesGrid.SelectedItem = rule;
RulesGrid.ScrollIntoView(rule);
ApplyFilter();
}
private static bool RuleMatches(ProcessItem item, ProcessFilterRule rule)
{
string left = GetRuleField(item, rule.Column);
string right = rule.Value ?? "";
string relation = rule.Relation?.Trim().ToLowerInvariant() ?? "is";
return relation switch { "is" => string.Equals(left, right, StringComparison.OrdinalIgnoreCase),
"contains" => left.Contains(right, StringComparison.OrdinalIgnoreCase),
"begins with" => left.StartsWith(right, StringComparison.OrdinalIgnoreCase),
"ends with" => left.EndsWith(right, StringComparison.OrdinalIgnoreCase),
"greater than" => TryCompareNumeric(left, right, out var gt) && gt > 0,
"less than" => TryCompareNumeric(left, right, out var lt) && lt < 0,
_ => string.Equals(left, right, StringComparison.OrdinalIgnoreCase) };
}
private static string GetRuleField(ProcessItem item, string column)
{
return column switch { "Architecture" => item.Architecture,
"Process Name" => item.Name,
"App Name" => item.AppName,
"Company" => item.Company,
"File Name" => item.FileName,
"PID" => item.Pid.ToString(CultureInfo.InvariantCulture),
"CPU %" => item.CpuPercent,
"Integrity" => item.IntegrityLevel,
"Signed" => item.SignedStatus,
"Sandbox" => item.SandboxStatus,
"Relation" => item.Relation,
"Parent" => item.ParentName,
"Path" => item.Path,
_ => item.Name };
}
private static bool TryCompareNumeric(string left, string right, out int compareResult)
{
compareResult = 0;
if (!double.TryParse(left, NumberStyles.Float, CultureInfo.InvariantCulture, out var a))
return false;
if (!double.TryParse(right, NumberStyles.Float, CultureInfo.InvariantCulture, out var b))
return false;
compareResult = a.CompareTo(b);
return true;
}
private bool HasCachedEnrichment(int pid)
{
lock (_cacheLock)
{
return _processEnrichmentCache.ContainsKey(pid);
}
}
private double TryGetCpuPercent(int pid, long cpuNow100ns, DateTime now)
{
try
{
if (_cpuBaseline.TryGetValue(pid, out var prev))
{
double sec = Math.Max(0.25, (now - prev.ts).TotalSeconds);
long delta100ns = cpuNow100ns - prev.cpu100ns;
if (delta100ns < 0)
delta100ns = 0;
double cpuSeconds = delta100ns / 10_000_000.0;
double cpuPct = cpuSeconds / sec / Math.Max(1, Environment.ProcessorCount) * 100.0;
if (cpuPct < 0)
cpuPct = 0;
_cpuBaseline[pid] = (cpuNow100ns, now);
return cpuPct;
}
_cpuBaseline[pid] = (cpuNow100ns, now);
}
catch
{
}
return 0;
}
private bool TryBuildProcessEnrichment(int pid, string fallbackName, out ProcessEnrichmentSnapshot enrichment)
{
enrichment = new ProcessEnrichmentSnapshot { Path = "",
FileName = "",
AppName = fallbackName,
Company = "",
Icon = _defaultProcessIcon,
Architecture = "",
IntegrityLevel = "",
IsAppContainer = false,
SandboxStatus = "",
IsSigned = false,
IsUnsigned = false,
SignedStatus = "",
SignatureState = SignatureTrustState.Unknown,
IsSystemOrWindows = false };
string windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
try
{
using var process = Process.GetProcessById(pid);
string path = "";
try
{
path = process.MainModule?.FileName ?? "";
}
catch
{
}
string architecture = TryGetArchitecture(process);
string integrity = "";
bool isAppContainer = false;
if (TryGetIntegrityInfo(process, out var integrityLevel, out var appContainer))
{
integrity = integrityLevel;
isAppContainer = appContainer;
}
bool isSystemOrWindows = process.SessionId == 0 ||
fallbackName.Equals("System", StringComparison.OrdinalIgnoreCase) ||
(!string.IsNullOrWhiteSpace(path) &&
path.StartsWith(windowsDir, StringComparison.OrdinalIgnoreCase));
string fileName = "";
string appName = fallbackName;
string company = "";
bool isSigned = false;
bool isUnsigned = false;
string signedStatus = "";
SignatureTrustState signatureState = SignatureTrustState.Unknown;
ImageSource? icon = _defaultProcessIcon;
if (!string.IsNullOrWhiteSpace(path))
{
var meta = GetPathMetadata(path, fallbackName);
fileName = meta.FileName;
appName = meta.AppName;
company = meta.Company;
signatureState = meta.SignatureState;
isSigned = signatureState == SignatureTrustState.Trusted;
isUnsigned = signatureState == SignatureTrustState.Unsigned ||
signatureState == SignatureTrustState.Invalid ||
signatureState == SignatureTrustState.Expired;
signedStatus = signatureState switch { SignatureTrustState.Trusted => "Yes",
SignatureTrustState.Unsigned => "Unsigned",
SignatureTrustState.Invalid => "Invalid",
SignatureTrustState.Expired => "Expired",
_ => "" };
icon = GetProcessIcon(path);
}
enrichment = new ProcessEnrichmentSnapshot { Path = path,
FileName = fileName,
AppName = appName,
Company = company,
Icon = icon ?? _defaultProcessIcon,
Architecture = architecture,
IntegrityLevel = integrity,
IsAppContainer = isAppContainer,
SandboxStatus = isAppContainer ? "AppContainer" : "",
IsSigned = isSigned,
IsUnsigned = isUnsigned,
SignedStatus = signedStatus,
SignatureState = signatureState,
IsSystemOrWindows = isSystemOrWindows };
return true;
}
catch
{
return false;
}
}
private static List<SystemProcessSnapshot> QuerySystemProcessesFast()
{
const int systemProcessInformation = 5;
const int statusInfoLengthMismatch = unchecked((int)0xC0000004);
int length = 1 << 20;
IntPtr buffer = IntPtr.Zero;
try
{
while (true)
{
buffer = Marshal.AllocHGlobal(length);
int status =
NtQuerySystemInformation(systemProcessInformation, buffer, length, out int returnLength);
if (status == statusInfoLengthMismatch)
{
Marshal.FreeHGlobal(buffer);
buffer = IntPtr.Zero;
length = Math.Max(length * 2, returnLength + 65536);
continue;
}
if (status < 0)
return new List<SystemProcessSnapshot>();
var list = new List<SystemProcessSnapshot>(512);
var seenPids = new HashSet<int>();
IntPtr current = buffer;
while (true)
{
var spi = Marshal.PtrToStructure<SYSTEM_PROCESS_INFORMATION>(current);
long pid64 = spi.UniqueProcessId.ToInt64();
long parent64 = spi.InheritedFromUniqueProcessId.ToInt64();
if (pid64 > 0 && pid64 <= int.MaxValue)
{
int pid = (int)pid64;
if (!seenPids.Add(pid))
{
if (spi.NextEntryOffset == 0)
break;
current = IntPtr.Add(current, (int)spi.NextEntryOffset);
continue;
}
string name = "";
if (spi.ImageName.Buffer != IntPtr.Zero && spi.ImageName.Length > 0)
{
name = Marshal.PtrToStringUni(spi.ImageName.Buffer, spi.ImageName.Length / 2) ?? "";
}
if (string.IsNullOrWhiteSpace(name))
{
name = pid64 == 4 ? "System" : $"pid-{pid64}";
}
int parentPid = (parent64 > 0 && parent64 <= int.MaxValue) ? (int)parent64 : 0;
long cpu100ns = spi.UserTime + spi.KernelTime;
if (cpu100ns < 0)
cpu100ns = 0;
list.Add(new SystemProcessSnapshot(pid, parentPid, name, cpu100ns));
}
if (spi.NextEntryOffset == 0)
break;
current = IntPtr.Add(current, (int)spi.NextEntryOffset);
}
return list;
}
}
finally
{
if (buffer != IntPtr.Zero)
Marshal.FreeHGlobal(buffer);
}
}
private void EnsureCurrentProcessVisible(List<SystemProcessSnapshot> snapshot)
{
if (snapshot.Any(entry => entry.Pid == _currentPid))
{
return;
}
try
{
using Process current = Process.GetCurrentProcess();
int parentPid = 0;
try
{
Dictionary<int, int> parentMap = BuildParentPidMap();
if (parentMap.TryGetValue(_currentPid, out int resolvedParentPid))
{
parentPid = resolvedParentPid;
}
}
catch
{
}
snapshot.Add(new SystemProcessSnapshot(_currentPid, parentPid, current.ProcessName,
current.TotalProcessorTime.Ticks));
}
catch
{
}
}
private void UpdateRulesPanelVisibility()
{
bool hasRules = _rules.Count > 0;
RuleActionsBar.Visibility = hasRules ? Visibility.Visible : Visibility.Collapsed;
RulesGrid.Visibility = hasRules ? Visibility.Visible : Visibility.Collapsed;
RulesRow.Height = hasRules ? new GridLength(146) : new GridLength(0);
RulesSpacerRow.Height = hasRules ? new GridLength(8) : new GridLength(0);
}
private void StartDeferredEnrichment(int generation)
{
_enrichmentCts?.Cancel();
_enrichmentCts?.Dispose();
_enrichmentCts = new CancellationTokenSource();
var token = _enrichmentCts.Token;
var targets = _all.Where(x => !HasCachedEnrichment(x.Pid)).ToList();
if (targets.Count == 0)
return;
_ = Task.Run(
async () =>
{
int updates = 0;
foreach (var target in targets)
{
if (token.IsCancellationRequested || generation != _refreshGeneration)
break;
if (!TryBuildProcessEnrichment(target.Pid, target.Name, out var enrichment))
continue;
lock (_cacheLock)
{
_processEnrichmentCache[target.Pid] = enrichment;
}
await Dispatcher.InvokeAsync(
() =>
{
if (token.IsCancellationRequested || generation != _refreshGeneration)
return;
ProcessItem? current = _all.FirstOrDefault(
x => x.Pid == target.Pid &&
string.Equals(x.Path, target.Path, StringComparison.OrdinalIgnoreCase));
if (current == null)
return;
current.FileName = enrichment.FileName;
current.AppName = enrichment.AppName;
current.Company = enrichment.Company;
current.Icon = enrichment.Icon ?? _defaultProcessIcon;
current.IsSigned = enrichment.IsSigned;
current.IsUnsigned = enrichment.IsUnsigned;
current.SignedStatus = enrichment.SignedStatus;
current.SignatureState = enrichment.SignatureState;
current.Architecture = enrichment.Architecture;
current.IntegrityLevel = enrichment.IntegrityLevel;
current.IsAppContainer = enrichment.IsAppContainer;
current.SandboxStatus = enrichment.SandboxStatus;
current.IsSystemOrWindows = enrichment.IsSystemOrWindows;
current.Path = enrichment.Path;
ApplyRowTheme(current);
updates++;
if ((updates % 8) == 0)
{
ResortAllForDisplay();
ProcessGrid.Items.Refresh();
RulesGrid.Items.Refresh();
}
},
DispatcherPriority.Background);
}
await Dispatcher.InvokeAsync(
() =>
{
if (generation != _refreshGeneration)
return;
ResortAllForDisplay();
ProcessGrid.Items.Refresh();
RulesGrid.Items.Refresh();
},
DispatcherPriority.Background);
},
token);
}
private void ResortAllForDisplay()
{
var sorted = _all.OrderBy(x => x.IsUnsigned ? 0 : 1)
.ThenBy(x => x.IsAppContainer ? 0 : 1)
.ThenBy(x => x.IsSystemOrWindows ? 1 : 0)
.ThenByDescending(x => x.CpuPercentValue)
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
_all.Clear();
_all.AddRange(sorted);
ApplyFilter();
}
private void ApplyRowTheme(ProcessItem item)
{
item.RowForeground = _rowDefaultForeground;
if (item.Pid == _currentPid)
{
item.RowBackground = BuildFrozenBrush(Color.FromArgb(0x22, 0x3F, 0x86, 0xFF));
item.RowBorderBrush = BuildFrozenBrush(Color.FromArgb(0x88, 0x68, 0xA4, 0xFF));
item.RowForeground = BuildFrozenBrush(Color.FromRgb(0xF2, 0xF7, 0xFF));
return;
}
if (item.IsUnsigned)
{
double severity = item.SignatureState switch { SignatureTrustState.Unsigned => 1.00,
SignatureTrustState.Invalid => 0.82,
SignatureTrustState.Expired => 0.60,
_ => 0.45 };
double cpuBoost = Math.Clamp(item.CpuPercentValue / 45.0, 0.0, 1.0);
double intensity = Math.Clamp(severity + (cpuBoost * 0.55), 0.20, 1.45);
item.RowBackground = BuildIntensityBrush(_rowUnsignedBackground, intensity, 0.28);
item.RowBorderBrush = BuildIntensityBrush(_rowUnsignedBorder, intensity, 0.20);
item.RowForeground = _rowUnsignedForeground;
return;
}
if (item.IsAppContainer)
{
item.RowBackground = _rowAppContainerBackground;
item.RowBorderBrush = _rowAppContainerBorder;
item.RowForeground = _rowAppContainerForeground;
return;
}
if (item.IntegrityLevel.Equals("Low", StringComparison.OrdinalIgnoreCase))
{
item.RowBackground = _rowLowIntegrityBackground;
item.RowBorderBrush = _rowLowIntegrityBorder;
return;
}
if (item.IntegrityLevel.Equals("High", StringComparison.OrdinalIgnoreCase) ||
item.IntegrityLevel.Equals("System", StringComparison.OrdinalIgnoreCase))
{
item.RowBackground = _rowElevatedBackground;
item.RowBorderBrush = _rowElevatedBorder;
return;
}
if (item.IsSystemOrWindows)
{
item.RowBackground = _rowSystemBackground;
item.RowBorderBrush = _rowSystemBorder;
item.RowForeground = _rowSystemForeground;
return;
}
item.RowBackground = Brushes.Transparent;
item.RowBorderBrush = Brushes.Transparent;
item.RowForeground = _rowDefaultForeground;
}
private static Brush BuildIntensityBrush(Brush templateBrush, double intensity, double brightenCap)
{
if (templateBrush is not SolidColorBrush solid)
return templateBrush;
Color source = solid.Color;
double alpha = Math.Clamp((source.A / 255.0) * intensity, 0.0, 1.0);
double brighten = Math.Clamp((intensity - 1.0) * brightenCap, 0.0, brightenCap);
byte r = (byte)Math.Clamp(Math.Round(source.R + ((255 - source.R) * brighten)), 0, 255);
byte g = (byte)Math.Clamp(Math.Round(source.G + ((255 - source.G) * brighten)), 0, 255);
byte b = (byte)Math.Clamp(Math.Round(source.B + ((255 - source.B) * brighten)), 0, 255);
byte a = (byte)Math.Clamp(Math.Round(alpha * 255.0), 0, 255);
var result = new SolidColorBrush(Color.FromArgb(a, r, g, b));
result.Freeze();
return result;
}
private static SolidColorBrush BuildFrozenBrush(Color color)
{
var brush = new SolidColorBrush(color);
brush.Freeze();
return brush;
}
private void OnThemeChanged(bool _)
{
Dispatcher.BeginInvoke(new Action(() =>
{
RefreshThemePalette();
ReapplyRowThemes();
}),
DispatcherPriority.Background);
}
private void RefreshThemePalette()
{
_rowDefaultForeground = ResolveBrush("ProcessRowDefaultForegroundBrush", Color.FromRgb(0xD8, 0xD8, 0xD8));
_rowUnsignedBackground =
ResolveBrush("ProcessRowUnsignedBackgroundBrush", Color.FromArgb(0x33, 0x50, 0x1F, 0x1F));
_rowUnsignedBorder = ResolveBrush("ProcessRowUnsignedBorderBrush", Color.FromArgb(0xAA, 0xE3, 0x6B, 0x6B));
_rowUnsignedForeground = ResolveBrush("ProcessRowUnsignedForegroundBrush", Color.FromRgb(0xF4, 0xD7, 0xD7));
_rowAppContainerBackground =
ResolveBrush("ProcessRowAppContainerBackgroundBrush", Color.FromArgb(0x33, 0x3A, 0x2A, 0x58));
_rowAppContainerBorder =
ResolveBrush("ProcessRowAppContainerBorderBrush", Color.FromArgb(0x99, 0xB0, 0x7A, 0xE8));
_rowAppContainerForeground = ResolveBrush("ProcessRowAppContainerForegroundBrush", Colors.WhiteSmoke);
_rowLowIntegrityBackground =
ResolveBrush("ProcessRowLowIntegrityBackgroundBrush", Color.FromArgb(0x2D, 0x4C, 0x41, 0x1D));
_rowLowIntegrityBorder =
ResolveBrush("ProcessRowLowIntegrityBorderBrush", Color.FromArgb(0x88, 0xD1, 0xB0, 0x5C));
_rowElevatedBackground =
ResolveBrush("ProcessRowElevatedBackgroundBrush", Color.FromArgb(0x24, 0x2D, 0x3C, 0x52));
_rowElevatedBorder = ResolveBrush("ProcessRowElevatedBorderBrush", Color.FromArgb(0x70, 0x8A, 0xA8, 0xC7));
_rowSystemBackground =
ResolveBrush("ProcessRowSystemBackgroundBrush", Color.FromArgb(0x22, 0x23, 0x23, 0x23));
_rowSystemBorder = ResolveBrush("ProcessRowSystemBorderBrush", Color.FromArgb(0x55, 0x5A, 0x5A, 0x5A));
_rowSystemForeground = ResolveBrush("ProcessRowSystemForegroundBrush", Color.FromRgb(0xB8, 0xB8, 0xB8));
}
private void ReapplyRowThemes()
{
foreach (var item in _all)
{
ApplyRowTheme(item);
}
ProcessGrid.Items.Refresh();
}
private static Brush ResolveBrush(string key, Color fallback)
{
if (Application.Current?.TryFindResource(key) is Brush brush)
{
return brush;
}
var solid = new SolidColorBrush(fallback);
solid.Freeze();
return solid;
}
private SignatureTrustState ClassifySignature(string path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
return SignatureTrustState.Unknown;
lock (_cacheLock)
{
if (_signatureCache.TryGetValue(path, out var stateCached))
return stateCached;
}
SignatureTrustState state;
try
{
#pragma warning disable SYSLIB0057
using var certificate = new X509Certificate2(X509Certificate.CreateFromSignedFile(path));
#pragma warning restore SYSLIB0057
DateTime utcNow = DateTime.UtcNow;
bool timeValid = utcNow >= certificate.NotBefore.ToUniversalTime() &&
utcNow <= certificate.NotAfter.ToUniversalTime();
using var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.VerificationTime = utcNow;
bool chainValid =
chain.Build(certificate) && chain.ChainStatus.All(x => x.Status == X509ChainStatusFlags.NoError);
if (!timeValid)
state = SignatureTrustState.Expired;
else if (!chainValid)
state = SignatureTrustState.Invalid;
else
state = SignatureTrustState.Trusted;
}
catch
{
state = SignatureTrustState.Unsigned;
}
lock (_cacheLock)
{
_signatureCache[path] = state;
}
return state;
}
private ProcessPathMetadata GetPathMetadata(string path, string fallbackProcessName)
{
lock (_cacheLock)
{
if (_pathMetadataCache.TryGetValue(path, out var cached))
return cached;
}
var result =
new ProcessPathMetadata { FileName = System.IO.Path.GetFileName(path), AppName = fallbackProcessName,
Company = "", SignatureState = ClassifySignature(path) };
try
{
var ver = FileVersionInfo.GetVersionInfo(path);
if (!string.IsNullOrWhiteSpace(ver.ProductName))
result.AppName = ver.ProductName;
result.Company = ver.CompanyName ?? "";
}
catch
{
}
lock (_cacheLock)
{
_pathMetadataCache[path] = result;
}
return result;
}
private ImageSource? GetProcessIcon(string path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{
return _defaultProcessIcon;
}
lock (_cacheLock)
{
if (_iconCache.TryGetValue(path, out ImageSource? cached))
return cached;
}
try
{
uint cbFileInfo = (uint)Marshal.SizeOf<SHFILEINFO>();
const uint flags = SHGFI_ICON | SHGFI_SMALLICON;
IntPtr result = SHGetFileInfo(path, 0, out SHFILEINFO info, cbFileInfo, flags);
if (result == IntPtr.Zero || info.hIcon == IntPtr.Zero)
{
lock (_cacheLock)
{
_iconCache[path] = _defaultProcessIcon;
}
return _defaultProcessIcon;
}
try
{
BitmapSource bitmap = Imaging.CreateBitmapSourceFromHIcon(
info.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(16, 16));
bitmap.Freeze();
lock (_cacheLock)
{
_iconCache[path] = bitmap;
}
return bitmap;
}
finally
{
DestroyIcon(info.hIcon);
}
}
catch
{
lock (_cacheLock)
{
_iconCache[path] = _defaultProcessIcon;
}
return _defaultProcessIcon;
}
}
private ImageSource? GetDefaultProcessIcon()
{
try
{
uint cbFileInfo = (uint)Marshal.SizeOf<SHFILEINFO>();
const uint flags = SHGFI_ICON | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES;
IntPtr result =
SHGetFileInfo("dummy.exe", FILE_ATTRIBUTE_NORMAL, out SHFILEINFO info, cbFileInfo, flags);
if (result == IntPtr.Zero || info.hIcon == IntPtr.Zero)
{
return null;
}
try
{
BitmapSource bitmap = Imaging.CreateBitmapSourceFromHIcon(
info.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(16, 16));
bitmap.Freeze();
return bitmap;
}
finally
{
DestroyIcon(info.hIcon);
}
}
catch
{
return null;
}
}
private static string TryGetArchitecture(Process process)
{
try
{
if (!Environment.Is64BitOperatingSystem)
return "x86";
if (IsWow64Process2(process.Handle, out ushort processMachine, out ushort nativeMachine))
{
if (processMachine == IMAGE_FILE_MACHINE_UNKNOWN)
return MachineToArch(nativeMachine);
return MachineToArch(processMachine);
}
}
catch
{
}
try
{
if (IsWow64Process(process.Handle, out bool isWow64))
return isWow64 ? "x86" : "x64";
}
catch
{
}
return "";
}
private static string MachineToArch(ushort machine)
{
return machine switch { IMAGE_FILE_MACHINE_I386 => "x86", IMAGE_FILE_MACHINE_AMD64 => "x64",
IMAGE_FILE_MACHINE_ARM64 => "arm64", IMAGE_FILE_MACHINE_ARM => "arm",
_ => "" };
}
private static Dictionary<int, int> BuildParentPidMap()
{
var map = new Dictionary<int, int>();
IntPtr snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE)
return map;
try
{
var pe = new PROCESSENTRY32();
pe.dwSize = (uint)Marshal.SizeOf<PROCESSENTRY32>();
if (!Process32First(snap, ref pe))
return map;
do
{
map[(int)pe.th32ProcessID] = (int)pe.th32ParentProcessID;
} while (Process32Next(snap, ref pe));
}
finally
{
_ = Kernel32Native.CloseHandle(snap);
}
return map;
}
private static bool TryGetIntegrityInfo(Process p, out string integrity, out bool isAppContainer)
{
integrity = "Unknown";
isAppContainer = false;
IntPtr token = IntPtr.Zero;
try
{
if (!OpenProcessToken(p.Handle, TOKEN_QUERY, out token))
return false;
if (!TryGetIntegrityLevel(token, out integrity))
integrity = "Unknown";
_ = TryGetTokenIsAppContainer(token, out isAppContainer);
return true;
}
catch
{
return false;
}
finally
{
if (token != IntPtr.Zero)
_ = Kernel32Native.CloseHandle(token);
}
}
private static bool TryGetIntegrityLevel(IntPtr token, out string integrity)
{
integrity = "Unknown";
if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, IntPtr.Zero, 0, out int cb))
{
if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
return false;
}
IntPtr pTIL = Marshal.AllocHGlobal(cb);
try
{
if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pTIL, cb, out _))
return false;
var til = Marshal.PtrToStructure<TOKEN_MANDATORY_LABEL>(pTIL);
IntPtr pSid = til.Label.Sid;
int subAuthCount = Marshal.ReadByte(GetSidSubAuthorityCount(pSid));
int rid = Marshal.ReadInt32(GetSidSubAuthority(pSid, subAuthCount - 1));
integrity = rid switch { >= SECURITY_MANDATORY_PROTECTED_PROCESS_RID => "Protected",
>= SECURITY_MANDATORY_SYSTEM_RID => "System",
>= SECURITY_MANDATORY_HIGH_RID => "High",
>= SECURITY_MANDATORY_MEDIUM_RID => "Medium",
>= SECURITY_MANDATORY_LOW_RID => "Low",
_ => "Untrusted" };
return true;
}
finally
{
Marshal.FreeHGlobal(pTIL);
}
}
private static bool TryGetTokenIsAppContainer(IntPtr token, out bool isAppContainer)
{
isAppContainer = false;
int size = sizeof(int);
IntPtr p = Marshal.AllocHGlobal(size);
try
{
if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenIsAppContainer, p, size, out _))
return false;
isAppContainer = Marshal.ReadInt32(p) != 0;
return true;
}
finally
{
Marshal.FreeHGlobal(p);
}
}
private void Select_Click(object sender, RoutedEventArgs e)
{
if (ProcessGrid.SelectedItem is not ProcessItem item)
return;
LaunchSelectedImage = false;
LaunchImagePath = string.Empty;
SelectedLaunchTargetKind = LaunchTargetKind.Executable;
SelectedPid = item.Pid;
ApplyLaunchHookOptions(allowEarlyBird: true);
DialogResult = true;
Close();
}
private void ProcessGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ProcessItem? item = GetProcessItemFromSource(e.OriginalSource as DependencyObject);
if (item == null)
{
return;
}
if (!ReferenceEquals(ProcessGrid.SelectedItem, item))
{
ProcessGrid.SelectedItem = item;
}
}
private void ProcessGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ProcessItem? item = GetProcessItemFromSource(e.OriginalSource as DependencyObject) ??
ProcessGrid.SelectedItem as ProcessItem;
if (item == null)
{
return;
}
if (!ReferenceEquals(ProcessGrid.SelectedItem, item))
{
ProcessGrid.SelectedItem = item;
}
Select_Click(sender, e);
}
private static ProcessItem? GetProcessItemFromSource(DependencyObject? source)
{
while (source != null)
{
if (source is DataGridRow row && row.Item is ProcessItem item)
{
return item;
}
if (source is DataGridCell cell && cell.DataContext is ProcessItem cellItem)
{
return cellItem;
}
source = VisualTreeHelper.GetParent(source);
}
return null;
}
private void ProcessGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
e.Handled = true;
string property = e.Column.SortMemberPath;
if (string.IsNullOrWhiteSpace(property) && e.Column is DataGridBoundColumn bc &&
bc.Binding is System.Windows.Data.Binding b)
{
property = b.Path?.Path ?? "";
}
if (string.IsNullOrWhiteSpace(property))
return;
if (string.Equals(_activeSortProperty, property, StringComparison.Ordinal))
{
_activeSortDirection = _activeSortDirection == ListSortDirection.Ascending
? ListSortDirection.Descending
: ListSortDirection.Ascending;
}
else
{
_activeSortProperty = property;
_activeSortDirection = ListSortDirection.Ascending;
}
foreach (var column in ProcessGrid.Columns)
{
column.SortDirection = null;
}
e.Column.SortDirection = _activeSortDirection;
ApplyFilter();
}
private void OpenExe_Click(object sender, RoutedEventArgs e)
{
OpenLaunchImage(LaunchTargetKind.Executable);
}
private void OpenDll_Click(object sender, RoutedEventArgs e)
{
OpenLaunchImage(LaunchTargetKind.Dll);
}
private void OpenLaunchImage(LaunchTargetKind targetKind)
{
var dlg = new OpenFileDialog { Filter = targetKind == LaunchTargetKind.Dll ? "DLL (*.dll)|*.dll"
: "Executable (*.exe)|*.exe",
CheckFileExists = true, Multiselect = false };
if (dlg.ShowDialog(this) != true)
return;
if (ShowLaunchOptions)
{
LaunchSelectedImage = true;
LaunchImagePath = dlg.FileName;
SelectedLaunchTargetKind = targetKind;
SelectedPid = 0;
ApplyLaunchHookOptions(allowEarlyBird: true);
DialogResult = true;
Close();
return;
}
if (targetKind == LaunchTargetKind.Dll)
{
ThemedMessageBox.Show(this, "DLL targets require launch options so Blackbird can stage the DLL host.",
"DLL launch", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
try
{
var started = Process.Start(new ProcessStartInfo(dlg.FileName) { UseShellExecute = true });
if (started == null)
{
return;
}
LaunchSelectedImage = false;
LaunchImagePath = string.Empty;
SelectedLaunchTargetKind = LaunchTargetKind.Executable;
SelectedPid = started.Id;
UseUsermodeHooks = false;
AutoOpenApiGraphWindow = false;
UseEarlyBirdApcLaunch = false;
DialogResult = true;
Close();
}
catch (Exception ex)
{
ThemedMessageBox.Show(this, ex.Message, "Launch failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ApplyLaunchHookOptions(bool allowEarlyBird)
{
LaunchHookOptions state =
LaunchHookOptions.Capture(UseUsermodeHooksCheckBox?.IsChecked, AutoOpenApiGraphCheckBox?.IsChecked,
EarlyBirdApcCheckBox?.IsChecked, allowEarlyBird);
UseUsermodeHooks = state.UseUsermodeHooks;
AutoOpenApiGraphWindow = state.AutoOpenApiGraphWindow;
UseEarlyBirdApcLaunch = state.UseEarlyBirdApcLaunch;
}
private static readonly IntPtr INVALID_HANDLE_VALUE = new(-1);
private const uint TH32CS_SNAPPROCESS = 0x00000002;
private const uint TOKEN_QUERY = 0x0008;
private const int ERROR_INSUFFICIENT_BUFFER = 122;
private const ushort IMAGE_FILE_MACHINE_UNKNOWN = 0x0000;
private const ushort IMAGE_FILE_MACHINE_I386 = 0x014c;
private const ushort IMAGE_FILE_MACHINE_AMD64 = 0x8664;
private const ushort IMAGE_FILE_MACHINE_ARM = 0x01c0;
private const ushort IMAGE_FILE_MACHINE_ARM64 = 0xAA64;
private const int SECURITY_MANDATORY_LOW_RID = 0x1000;
private const int SECURITY_MANDATORY_MEDIUM_RID = 0x2000;
private const int SECURITY_MANDATORY_HIGH_RID = 0x3000;
private const int SECURITY_MANDATORY_SYSTEM_RID = 0x4000;
private const int SECURITY_MANDATORY_PROTECTED_PROCESS_RID = 0x5000;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct PROCESSENTRY32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szExeFile;
}
[StructLayout(LayoutKind.Sequential)]
private struct SID_AND_ATTRIBUTES
{
public IntPtr Sid;
public int Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_MANDATORY_LABEL
{
public SID_AND_ATTRIBUTES Label;
}
[StructLayout(LayoutKind.Sequential)]
private struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_PROCESS_INFORMATION
{
public uint NextEntryOffset;
public uint NumberOfThreads;
public long WorkingSetPrivateSize;
public uint HardFaultCount;
public uint NumberOfThreadsHighWatermark;
public ulong CycleTime;
public long CreateTime;
public long UserTime;
public long KernelTime;
public UNICODE_STRING ImageName;
public int BasePriority;
public IntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
}
private enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
TokenIsAppContainer = 29
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool GetTokenInformation(IntPtr TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
IntPtr TokenInformation, int TokenInformationLength,
out int ReturnLength);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern IntPtr GetSidSubAuthority(IntPtr pSid, int nSubAuthority);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern IntPtr GetSidSubAuthorityCount(IntPtr pSid);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool IsWow64Process2(IntPtr hProcess, out ushort processMachine,
out ushort nativeMachine);
[DllImport("ntdll.dll")]
private static extern int NtQuerySystemInformation(int systemInformationClass, IntPtr systemInformation,
int systemInformationLength, out int returnLength);
private const uint SHGFI_ICON = 0x000000100;
private const uint SHGFI_SMALLICON = 0x000000001;
private const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi,
uint cbFileInfo, uint uFlags);
[DllImport("user32.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyIcon(IntPtr hIcon);
}
public sealed class ProcessFilterRule
{
public bool Enabled { get; set; } = true;
public string Column { get; set; } = "";
public string Relation { get; set; } = "is";
public string Value { get; set; } = "";
public string Action { get; set; } = "Include";
}
internal sealed class ProcessPathMetadata
{
public string FileName { get; init; } = "";
public string AppName { get; set; } = "";
public string Company { get; set; } = "";
public SignatureTrustState SignatureState { get; init; } = SignatureTrustState.Unknown;
}
internal readonly record struct SystemProcessSnapshot(int Pid, int ParentPid, string Name, long CpuTime100ns);
internal sealed class ProcessEnrichmentSnapshot
{
public string Path { get; init; } = "";
public string FileName { get; init; } = "";
public string AppName { get; init; } = "";
public string Company { get; init; } = "";
public ImageSource? Icon { get; init; }
public string Architecture { get; init; } = "";
public string IntegrityLevel { get; init; } = "";
public bool IsAppContainer { get; init; }
public string SandboxStatus { get; init; } = "";
public bool IsSigned { get; init; }
public bool IsUnsigned { get; init; }
public string SignedStatus { get; init; } = "";
public SignatureTrustState SignatureState { get; init; } = SignatureTrustState.Unknown;
public bool IsSystemOrWindows { get; init; }
}
public sealed class ProcessItem
{
public string Name { get; init; } = "";
public string AppName { get; set; } = "";
public string Company { get; set; } = "";
public string FileName { get; set; } = "";
public ImageSource? Icon { get; set; }
public string Architecture { get; set; } = "";
public int Pid { get; init; }
public int ParentPid { get; init; }
public string ParentName { get; set; } = "";
public string CpuPercent { get; init; } = "0.0";
public double CpuPercentValue { get; init; }
public string IntegrityLevel { get; set; } = "";
public bool IsAppContainer { get; set; }
public string SandboxStatus { get; set; } = "";
public bool IsSigned { get; set; }
public bool IsUnsigned { get; set; }
public string SignedStatus { get; set; } = "";
public SignatureTrustState SignatureState { get; set; } = SignatureTrustState.Unknown;
public bool IsSystemOrWindows { get; set; }
public string Relation { get; init; } = "Head";
public string Path { get; set; } = "";
public Brush RowBackground { get; set; } = Brushes.Transparent;
public Brush RowBorderBrush { get; set; } = Brushes.Transparent;
public Brush RowForeground { get; set; } = Brushes.Black;
}
}