ui/session: update session storage and export flow

This commit is contained in:
8damon
2026-05-06 09:11:23 +10:00
parent bad6149548
commit d726fc1bd0
4 changed files with 807 additions and 356 deletions
+270 -79
View File
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace BlackbirdInterface
{
@@ -13,17 +14,19 @@ namespace BlackbirdInterface
JsonLines,
Csv,
Cef,
AttackCsv
AttackCsv,
DetectionJsonLines,
DetectionCsv,
DetectionCef,
SplunkHecJson,
ElasticEcsJsonLines
}
internal static class SessionExportService
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false
};
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = false };
internal static void Export(string path, SessionFileArchive archive, SessionExportFormat format)
internal static int Export(string path, SessionFileArchive archive, SessionExportFormat format)
{
if (string.IsNullOrWhiteSpace(path))
{
@@ -41,11 +44,15 @@ namespace BlackbirdInterface
Directory.CreateDirectory(directory);
}
List<SessionExportRecord> records = FlattenArchive(archive).ToList();
List<SessionExportRecord> records =
FlattenArchive(archive)
.Where(record => !IsDetectionOnlyFormat(format) || IsDetectionRecord(record))
.ToList();
string tempPath = path + ".tmp";
try
{
using var writer = new StreamWriter(tempPath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
using var writer =
new StreamWriter(tempPath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
switch (format)
{
case SessionExportFormat.JsonLines:
@@ -60,6 +67,21 @@ namespace BlackbirdInterface
case SessionExportFormat.AttackCsv:
WriteCsv(writer, records, includeAttackColumns: true);
break;
case SessionExportFormat.DetectionJsonLines:
WriteSiemJsonLines(writer, records);
break;
case SessionExportFormat.DetectionCsv:
WriteCsv(writer, records, includeAttackColumns: true);
break;
case SessionExportFormat.DetectionCef:
WriteCef(writer, records);
break;
case SessionExportFormat.SplunkHecJson:
WriteSplunkHecJson(writer, records);
break;
case SessionExportFormat.ElasticEcsJsonLines:
WriteElasticEcsJsonLines(writer, records);
break;
default:
throw new InvalidOperationException($"Unsupported export format: {format}");
}
@@ -74,6 +96,8 @@ namespace BlackbirdInterface
{
File.Move(tempPath, path);
}
return records.Count;
}
finally
{
@@ -92,18 +116,17 @@ namespace BlackbirdInterface
foreach (TelemetryEvent ev in tab.Events.OrderBy(x => x.TimestampUtc))
{
yield return new SessionExportRecord
{
TimestampUtc = ev.TimestampUtc,
Tab = tabTitle,
Pid = ev.PID != 0 ? ev.PID : tab.Pid,
Tid = ev.TID,
Stream = "timeline",
EventName = string.IsNullOrWhiteSpace(ev.SubType) ? ev.Group : $"{ev.Group}/{ev.SubType}",
Source = ev.ProcessName,
Summary = ev.Summary,
Details = ev.Details
};
yield return new SessionExportRecord { TimestampUtc = ev.TimestampUtc,
Tab = tabTitle,
Pid = ev.PID != 0 ? ev.PID : tab.Pid,
Tid = ev.TID,
Stream = "timeline",
EventName = string.IsNullOrWhiteSpace(ev.SubType)
? ev.Group
: $"{ev.Group}/{ev.SubType}",
Source = ev.ProcessName,
Summary = ev.Summary,
Details = ev.Details };
}
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "etw", tab.EtwGroups))
@@ -111,51 +134,71 @@ namespace BlackbirdInterface
yield return record;
}
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "heuristics", tab.HeuristicsGroups))
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "heuristics",
tab.HeuristicsGroups))
{
yield return record;
}
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "filesystem", tab.FilesystemGroups))
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "filesystem",
tab.FilesystemGroups))
{
yield return record;
}
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "relations", tab.ProcessRelationsGroups))
foreach (SessionExportRecord record in FlattenGroupedRows(tabTitle, tab.Pid, "relations",
tab.ProcessRelationsGroups))
{
yield return record;
}
}
}
private static IEnumerable<SessionExportRecord> FlattenGroupedRows(
string tabTitle,
int defaultPid,
string stream,
IEnumerable<GroupedEventRow> rows)
private static bool IsDetectionOnlyFormat(SessionExportFormat format) =>
format is SessionExportFormat.DetectionJsonLines or SessionExportFormat.DetectionCsv or SessionExportFormat
.DetectionCef or SessionExportFormat.SplunkHecJson or SessionExportFormat.ElasticEcsJsonLines;
private static bool IsDetectionRecord(SessionExportRecord record)
{
if (record.Stream.Equals("heuristics", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!record.Stream.Equals("etw", StringComparison.OrdinalIgnoreCase))
{
return false;
}
string combined = $"{record.EventName} {record.Detection} {record.Summary} {record.Details}";
return combined.Contains("Detection", StringComparison.OrdinalIgnoreCase) ||
combined.Contains("detection=", StringComparison.OrdinalIgnoreCase) ||
combined.Contains("engine=sigma", StringComparison.OrdinalIgnoreCase) ||
combined.Contains("engine=yara", StringComparison.OrdinalIgnoreCase);
}
private static IEnumerable<SessionExportRecord>
FlattenGroupedRows(string tabTitle, int defaultPid, string stream, IEnumerable<GroupedEventRow> rows)
{
foreach (GroupedEventRow row in rows.OrderBy(x => x.LastSeenUtc))
{
if (row.Details.Count == 0)
{
yield return new SessionExportRecord
{
TimestampUtc = row.LastSeenUtc,
Tab = tabTitle,
Pid = defaultPid,
Stream = stream,
EventName = row.Event,
Severity = row.Severity,
Detection = row.Detection,
Summary = row.GroupKey
};
yield return new SessionExportRecord { TimestampUtc = row.LastSeenUtc,
Tab = tabTitle,
Pid = defaultPid,
Stream = stream,
EventName = row.Event,
Severity = row.Severity,
Detection = row.Detection,
Summary = row.GroupKey,
Hits = Math.Max(1, row.Hits) };
continue;
}
foreach (GroupedEventDetailRow detail in row.Details.OrderBy(x => x.TimestampUtc))
{
yield return new SessionExportRecord
{
yield return new SessionExportRecord {
TimestampUtc = detail.TimestampUtc,
Tab = tabTitle,
Pid = detail.ActorPid != 0 ? unchecked((int)detail.ActorPid) : defaultPid,
@@ -170,7 +213,8 @@ namespace BlackbirdInterface
ActorPid = detail.ActorPid,
TargetPid = detail.TargetPid,
Summary = $"{detail.Event} {detail.Detection}".Trim(),
Details = detail.Details
Details = detail.Details,
Hits = Math.Max(1, detail.HitCount)
};
}
}
@@ -184,27 +228,50 @@ namespace BlackbirdInterface
}
}
private static void WriteCsv(TextWriter writer, IEnumerable<SessionExportRecord> records, bool includeAttackColumns)
private static void WriteSiemJsonLines(TextWriter writer, IEnumerable<SessionExportRecord> records)
{
var headers = new List<string>
foreach (SessionExportRecord record in records)
{
"timestamp_utc", "tab", "pid", "tid", "stream", "event", "severity", "detection",
"source", "actor", "actor_pid", "target", "target_pid", "summary", "details"
};
writer.WriteLine(JsonSerializer.Serialize(BuildSiemEvent(record), JsonOptions));
}
}
private static void WriteSplunkHecJson(TextWriter writer, IEnumerable<SessionExportRecord> records)
{
foreach (SessionExportRecord record in records)
{
var hec = new Dictionary < string, object
?> { ["time"] = ToUnixTimeSeconds(record.TimestampUtc), ["host"] = Environment.MachineName,
["source"] = "BK", ["sourcetype"] = "BK:detection", ["event"] = BuildSiemEvent(record) };
writer.WriteLine(JsonSerializer.Serialize(hec, JsonOptions));
}
}
private static void WriteElasticEcsJsonLines(TextWriter writer, IEnumerable<SessionExportRecord> records)
{
foreach (SessionExportRecord record in records)
{
writer.WriteLine(JsonSerializer.Serialize(BuildElasticEcsEvent(record), JsonOptions));
}
}
private static void WriteCsv(TextWriter writer, IEnumerable<SessionExportRecord> records,
bool includeAttackColumns)
{
var headers = new List<string> { "timestamp_utc", "tab", "pid", "tid", "stream",
"event", "severity", "detection", "source", "actor",
"actor_pid", "target", "target_pid", "summary", "details" };
if (includeAttackColumns)
{
headers.AddRange(new[]
{
"mitre_tactic", "mitre_technique_id", "mitre_technique", "mitre_subtechnique_id", "mitre_subtechnique"
});
headers.AddRange(new[] { "mitre_tactic", "mitre_technique_id", "mitre_technique",
"mitre_subtechnique_id", "mitre_subtechnique" });
}
writer.WriteLine(string.Join(",", headers.Select(EscapeCsv)));
foreach (SessionExportRecord record in records)
{
var values = new List<string>
{
var values = new List<string> {
record.TimestampUtc.ToString("O", CultureInfo.InvariantCulture),
record.Tab,
record.Pid.ToString(CultureInfo.InvariantCulture),
@@ -224,7 +291,9 @@ namespace BlackbirdInterface
if (includeAttackColumns)
{
values.AddRange(new[] { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty });
DetectionMetadata metadata = ExtractDetectionMetadata(record);
values.AddRange(
new[] { string.Empty, metadata.MitreTechniqueId, string.Empty, string.Empty, string.Empty });
}
writer.WriteLine(string.Join(",", values.Select(EscapeCsv)));
@@ -235,39 +304,162 @@ namespace BlackbirdInterface
{
foreach (SessionExportRecord record in records)
{
DetectionMetadata metadata = ExtractDetectionMetadata(record);
string signature = string.IsNullOrWhiteSpace(record.Detection) ? record.EventName : record.Detection;
string name = string.IsNullOrWhiteSpace(record.Summary) ? signature : record.Summary;
int severity = MapSeverity(record.Severity);
string extension =
$"rt={EscapeCefExtension(record.TimestampUtc.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture))} " +
$"cs1Label=stream cs1={EscapeCefExtension(record.Stream)} " +
$"sproc={EscapeCefExtension(record.Source)} " +
$"dproc={EscapeCefExtension(record.Target)} " +
$"src={EscapeCefExtension(record.Actor)} " +
$"dst={EscapeCefExtension(record.Target)} " +
$"cs2Label=rule_engine cs2={EscapeCefExtension(metadata.Engine)} " +
$"cs3Label=mitre_technique_id cs3={EscapeCefExtension(metadata.MitreTechniqueId)} " +
$"cs4Label=rule_id cs4={EscapeCefExtension(metadata.RuleId)} " +
$"cn1Label=actor_pid cn1={record.ActorPid} " + $"cn2Label=target_pid cn2={record.TargetPid} " +
$"sproc={EscapeCefExtension(record.Source)} " + $"dproc={EscapeCefExtension(record.Target)} " +
$"src={EscapeCefExtension(record.Actor)} " + $"dst={EscapeCefExtension(record.Target)} " +
$"msg={EscapeCefExtension(record.Details)} " +
$"deviceProcessName={EscapeCefExtension(record.Tab)} " +
$"externalId={EscapeCefExtension(record.Pid.ToString(CultureInfo.InvariantCulture))}";
writer.WriteLine(
$"CEF:0|Blackbird|Platform|1.0|{EscapeCefHeader(signature)}|{EscapeCefHeader(name)}|{severity}|{extension}");
$"CEF:0|BK|Platform|1.0|{EscapeCefHeader(signature)}|{EscapeCefHeader(name)}|{severity}|{extension}");
}
}
private static int TryParseInt(string value)
=> int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed) ? parsed : 0;
private static Dictionary<string, object?> BuildSiemEvent(SessionExportRecord record)
{
DetectionMetadata metadata = ExtractDetectionMetadata(record);
int severity = MapSeverity(record.Severity);
return new Dictionary < string,
object ?> { ["@timestamp"] = record.TimestampUtc.ToString("O", CultureInfo.InvariantCulture),
["host.name"] = Environment.MachineName,
["event.kind"] = "alert",
["event.module"] = "BK",
["event.dataset"] = "BK.detections",
["event.action"] = record.EventName,
["event.severity"] = severity,
["event.count"] = Math.Max(1, record.Hits),
["rule.id"] = metadata.RuleId,
["rule.name"] = metadata.RuleName,
["rule.ruleset"] = metadata.Engine,
["rule.category"] = metadata.Category,
["threat.technique.id"] = metadata.MitreTechniqueId,
["observer.vendor"] = "BK",
["observer.product"] = "BK Analysis Interface",
["BK.tab"] = record.Tab,
["BK.stream"] = record.Stream,
["BK.summary"] = record.Summary,
["BK.details"] = record.Details,
["detection.name"] = record.Detection,
["severity.label"] = record.Severity,
["source.process.name"] = record.Actor,
["source.process.pid"] = OptionalNumber(record.ActorPid),
["target.process.name"] = record.Target,
["target.process.pid"] = OptionalNumber(record.TargetPid),
["process.name"] = record.Source,
["process.pid"] = OptionalNumber(record.Pid),
["process.thread.id"] = OptionalNumber(record.Tid) };
}
private static Dictionary<string, object?> BuildElasticEcsEvent(SessionExportRecord record)
{
DetectionMetadata metadata = ExtractDetectionMetadata(record);
int severity = MapSeverity(record.Severity);
var threat = string.IsNullOrWhiteSpace(metadata.MitreTechniqueId) ? null : new Dictionary < string,
object ?> { ["technique"] =
new[] { new Dictionary < string, object ?> { ["id"] = metadata.MitreTechniqueId } } };
return new Dictionary < string,
object ?> { ["@timestamp"] = record.TimestampUtc.ToString("O", CultureInfo.InvariantCulture),
["ecs"] = new Dictionary < string, object ?> { ["version"] = "8.11.0" },
["host"] = new Dictionary < string, object ?> { ["name"] = Environment.MachineName },
["event"] = new Dictionary < string,
object ?> { ["kind"] = "alert", ["category"] = new[] { "malware" }, ["type"] = new[] { "indicator" },
["module"] = "BK", ["dataset"] = "BK.detections", ["action"] = record.EventName,
["severity"] = severity, ["risk_score"] = severity, ["reason"] = record.Summary,
["count"] = Math.Max(1, record.Hits) },
["rule"] = new Dictionary < string,
object ?> { ["id"] = metadata.RuleId, ["name"] = metadata.RuleName, ["ruleset"] = metadata.Engine,
["category"] = metadata.Category },
["observer"] = new Dictionary < string,
object ?> { ["vendor"] = "BK", ["product"] = "BK Analysis Interface", ["type"] = "sensor" },
["process"] = new Dictionary < string,
object ?> { ["name"] = record.Source, ["pid"] = OptionalNumber(record.Pid),
["thread"] = new Dictionary < string, object ?> { ["id"] = OptionalNumber(record.Tid) } },
["source"] = new Dictionary < string,
object ?> { ["process"] = new Dictionary < string,
object ?> { ["name"] = record.Actor, ["pid"] = OptionalNumber(record.ActorPid) } },
["target"] = new Dictionary < string,
object ?> { ["process"] = new Dictionary < string,
object ?> { ["name"] = record.Target, ["pid"] = OptionalNumber(record.TargetPid) } },
["threat"] = threat, ["BK"] = new Dictionary < string,
object ?> { ["tab"] = record.Tab, ["stream"] = record.Stream, ["detection"] = record.Detection,
["severity_label"] = record.Severity, ["details"] = record.Details } };
}
private static DetectionMetadata ExtractDetectionMetadata(SessionExportRecord record)
{
string combined = $"{record.Details} {record.Summary} {record.Detection}";
string engine =
FirstNonEmpty(ExtractKeyValue(combined, "engine"),
record.Detection.StartsWith("SIGMA_", StringComparison.OrdinalIgnoreCase) ? "sigma"
: record.Detection.StartsWith("YARA_", StringComparison.OrdinalIgnoreCase) ? "yara"
: "BK");
string ruleId = FirstNonEmpty(ExtractKeyValue(combined, "sigma_id"), ExtractKeyValue(combined, "rule"),
record.Detection);
string ruleName = FirstNonEmpty(DecodeEvidenceToken(ExtractKeyValue(combined, "rule")), record.Detection,
record.EventName);
string category = FirstNonEmpty(ExtractKeyValue(combined, "category"), record.Stream);
string mitreTechniqueId = ExtractMitreTechniqueId(combined);
return new DetectionMetadata(engine, ruleId, ruleName, category, mitreTechniqueId);
}
private static string ExtractKeyValue(string text, string key)
{
Match match = Regex.Match(text ?? string.Empty, $@"(?:^|\s){Regex.Escape(key)}=(?<value>[^\s]+)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return match.Success ? match.Groups["value"].Value.Trim() : string.Empty;
}
private static string DecodeEvidenceToken(string value) => (value ?? string.Empty).Trim().Replace('_', ' ');
private static string ExtractMitreTechniqueId(string text)
{
Match match = Regex.Match(text ?? string.Empty, @"\bT\d{4}(?:\.\d{3})?\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return match.Success ? match.Value.ToUpperInvariant() : string.Empty;
}
private static string FirstNonEmpty(params string[] values) =>
values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? string.Empty;
private static double ToUnixTimeSeconds(DateTime timestampUtc)
{
DateTime utc = timestampUtc.Kind == DateTimeKind.Utc ? timestampUtc
: DateTime.SpecifyKind(timestampUtc, DateTimeKind.Utc);
return new DateTimeOffset(utc).ToUnixTimeMilliseconds() / 1000.0;
}
private static object? OptionalNumber(int value) => value == 0 ? null : value;
private static object? OptionalNumber(uint value) => value == 0 ? null : value;
private static int TryParseInt(string value) => int.TryParse(value, NumberStyles.Integer,
CultureInfo.InvariantCulture, out int parsed)
? parsed
: 0;
private static int MapSeverity(string severity)
{
return (severity ?? string.Empty).Trim().ToLowerInvariant() switch
{
"critical" => 10,
"high" => 8,
"medium" => 5,
"low" => 3,
"info" => 1,
_ => 4
};
return (severity ?? string.Empty)
.Trim()
.ToLowerInvariant() switch { "critical" => 10,
"high" => 8,
"medium" => 5,
"low" => 3,
"informational" => 1,
"info" => 1,
_ => 4 };
}
private static string EscapeCsv(string value)
@@ -281,15 +473,11 @@ namespace BlackbirdInterface
return "\"" + text.Replace("\"", "\"\"") + "\"";
}
private static string EscapeCefHeader(string value)
=> (value ?? string.Empty).Replace("\\", "\\\\").Replace("|", "\\|");
private static string
EscapeCefHeader(string value) => (value ?? string.Empty).Replace("\\", "\\\\").Replace("|", "\\|");
private static string EscapeCefExtension(string value)
=> (value ?? string.Empty)
.Replace("\\", "\\\\")
.Replace("=", "\\=")
.Replace("\r", " ")
.Replace("\n", " ");
private static string EscapeCefExtension(string value) =>
(value ?? string.Empty).Replace("\\", "\\\\").Replace("=", "\\=").Replace("\r", " ").Replace("\n", " ");
private sealed class SessionExportRecord
{
@@ -308,7 +496,10 @@ namespace BlackbirdInterface
public uint TargetPid { get; init; }
public string Summary { get; init; } = string.Empty;
public string Details { get; init; } = string.Empty;
public int Hits { get; init; } = 1;
}
private sealed record DetectionMetadata(string Engine, string RuleId, string RuleName, string Category,
string MitreTechniqueId);
}
}
+27 -13
View File
@@ -26,8 +26,16 @@ namespace BlackbirdInterface
public double ViewStartSeconds { get; set; }
public string? LaneFocusKey { get; set; }
public bool UseUsermodeHooks { get; set; }
public bool KernelHooksEnabled { get; set; } = true;
public bool SignatureIntelEnabled { get; set; } = true;
public bool SignatureIntelMemoryScanEnabled { get; set; }
public bool SignatureIntelPageScanEnabled { get; set; }
public bool TargetExited { get; set; }
public string TargetExitReason { get; set; } = "";
public bool OfflineSnapshot { get; set; } = true;
public LaunchTargetKind AnalysisSubjectKind { get; set; } = LaunchTargetKind.Executable;
public string AnalysisSubjectPath { get; set; } = "";
public string AnalysisHostPath { get; set; } = "";
[JsonIgnore]
public string? CaptureStorePath { get; set; }
@@ -39,8 +47,10 @@ namespace BlackbirdInterface
public List<GroupedEventRow> EtwGroups { get; set; } = new();
public List<GroupedEventRow> HeuristicsGroups { get; set; } = new();
public List<GroupedEventRow> FilesystemGroups { get; set; } = new();
public List<GroupedEventRow> RegistryGroups { get; set; } = new();
public List<GroupedEventRow> ProcessRelationsGroups { get; set; } = new();
public List<ApiCallGraphRowSnapshot> ApiGraphRows { get; set; } = new();
public List<ExtendedActivityRowSnapshot> ExtendedActivityRows { get; set; } = new();
public List<ThreadStackHistoryArchiveEntry> ThreadStackHistories { get; set; } = new();
}
@@ -57,14 +67,11 @@ namespace BlackbirdInterface
private const int MaxThreadStackHistoriesPerTab = 8_192;
private const int MaxThreadStackSnapshotsPerHistory = 2_048;
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
WriteIndented = false
};
private static readonly JsonSerializerOptions JsonOptions =
new() { PropertyNameCaseInsensitive = true, WriteIndented = false };
internal static bool Exists(string? path)
=> !string.IsNullOrWhiteSpace(path) && CaptureArchiveStorage.Exists(path);
internal static bool
Exists(string? path) => !string.IsNullOrWhiteSpace(path) && CaptureArchiveStorage.Exists(path);
internal static void DeletePath(string? path)
{
@@ -121,8 +128,7 @@ namespace BlackbirdInterface
SessionFileArchive archive = workspace.Archive;
if (archive.Version <= 0 || archive.Version > CurrentVersion)
{
throw new InvalidDataException(
$"Unsupported session archive version ({archive.Version}).");
throw new InvalidDataException($"Unsupported session archive version ({archive.Version}).");
}
NormalizeArchive(archive);
@@ -162,7 +168,8 @@ namespace BlackbirdInterface
$"Session archive exceeds compressed size limit ({MaxCompressedArchiveBytes / (1024 * 1024)} MB).");
}
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 64 * 1024, FileOptions.SequentialScan);
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 64 * 1024,
FileOptions.SequentialScan);
using var gzip = new GZipStream(stream, CompressionMode.Decompress);
using var bounded = new BoundedReadStream(gzip, MaxUncompressedArchiveBytes);
@@ -183,6 +190,9 @@ namespace BlackbirdInterface
{
tab.Title ??= string.Empty;
tab.LaneFocusKey ??= null;
tab.TargetExitReason ??= string.Empty;
tab.AnalysisSubjectPath ??= string.Empty;
tab.AnalysisHostPath ??= string.Empty;
tab.Events ??= new List<TelemetryEvent>();
tab.PerformanceHistory ??= new List<PerformanceSample>();
tab.MemoryRegionAttributionHistory ??= new List<MemoryRegionAttributionSample>();
@@ -190,8 +200,10 @@ namespace BlackbirdInterface
tab.EtwGroups ??= new List<GroupedEventRow>();
tab.HeuristicsGroups ??= new List<GroupedEventRow>();
tab.FilesystemGroups ??= new List<GroupedEventRow>();
tab.RegistryGroups ??= new List<GroupedEventRow>();
tab.ProcessRelationsGroups ??= new List<GroupedEventRow>();
tab.ApiGraphRows ??= new List<ApiCallGraphRowSnapshot>();
tab.ExtendedActivityRows ??= new List<ExtendedActivityRowSnapshot>();
tab.ThreadStackHistories ??= new List<ThreadStackHistoryArchiveEntry>();
tab.CaptureStorePath ??= null;
}
@@ -223,8 +235,10 @@ namespace BlackbirdInterface
if (tab.EtwGroups.Count > MaxGroupedRowsPerCategory ||
tab.HeuristicsGroups.Count > MaxGroupedRowsPerCategory ||
tab.FilesystemGroups.Count > MaxGroupedRowsPerCategory ||
tab.RegistryGroups.Count > MaxGroupedRowsPerCategory ||
tab.ProcessRelationsGroups.Count > MaxGroupedRowsPerCategory ||
tab.ApiGraphRows.Count > MaxGroupedRowsPerCategory)
tab.ApiGraphRows.Count > MaxGroupedRowsPerCategory ||
tab.ExtendedActivityRows.Count > MaxGroupedRowsPerCategory)
{
throw new InvalidDataException($"PID {tab.Pid} has too many grouped intel rows.");
}
@@ -237,7 +251,8 @@ namespace BlackbirdInterface
{
if (history.Snapshots.Count > MaxThreadStackSnapshotsPerHistory)
{
throw new InvalidDataException($"PID {tab.Pid} TID {history.Tid} has too many thread stack snapshots.");
throw new InvalidDataException(
$"PID {tab.Pid} TID {history.Tid} has too many thread stack snapshots.");
}
}
}
@@ -331,4 +346,3 @@ namespace BlackbirdInterface
}
}
}
@@ -18,9 +18,8 @@ namespace BlackbirdInterface
string rootPath;
if (_currentSession != null && _currentSession.Pid == pid)
{
rootPath = IsSessionCachePath(_currentSession.BackingStorePath)
? _currentSession.BackingStorePath!
: AllocateSessionCachePath(pid);
rootPath = IsSessionCachePath(_currentSession.BackingStorePath) ? _currentSession.BackingStorePath!
: AllocateSessionCachePath(pid);
_currentSession.BackingStorePath = rootPath;
}
else
File diff suppressed because it is too large Load Diff