/// <summary> /// Draw a chart node /// </summary> /// <param name="node">The node</param> /// <param name="position">Where on the chart to draw it</param> /// <param name="font">The font to use for text</param> /// <returns>If the node was clicked</returns> private bool DrawNode(ItemNode node, Vector2 position, ImFontPtr font) { Vector2 cursor = ImGui.GetCursorScreenPos(); if (!InFrame(position - cursor)) { return(false); } var DrawList = ImGui.GetWindowDrawList(); bool isSelected = node == _selectedNode; switch (node.TLtype) { case Logging.eTimelineEvent.ProcessStart: case Logging.eTimelineEvent.ProcessEnd: { TraceRecord trace = (TraceRecord)node.reference; switch (trace.TraceState) { case TraceRecord.ProcessState.eTerminated: DrawList.AddCircleFilled(position, 18, isSelected ? 0xffDDDDDD : 0xFFFFFFFF); DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff0000ff, $"{ImGuiController.FA_ICON_COGS}"); DrawList.AddText(position + new Vector2(20, -14), 0xff000000, $"Process {trace.PID} (Exited)"); break; case TraceRecord.ProcessState.eRunning: DrawList.AddCircleFilled(position, 18, isSelected ? 0xffDDDDDD : 0xFFFFFFFF); DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff00ff00, $"{ImGuiController.FA_ICON_COGS}"); DrawList.AddText(position + new Vector2(20, -14), 0xff000000, $"Process {trace.PID} (Running)"); break; case TraceRecord.ProcessState.eSuspended: DrawList.AddCircleFilled(position, 18, isSelected ? 0xffDDDDDD : 0xFFFFFFFF); DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff00ffff, $"{ImGuiController.FA_ICON_COGS}"); DrawList.AddText(position + new Vector2(20, -14), 0xff000000, $"Process {trace.PID} (Suspended)"); break; default: Debug.Assert(false, "Bad trace state"); break; } } break; case Logging.eTimelineEvent.ThreadStart: case Logging.eTimelineEvent.ThreadEnd: { ProtoGraph graph = (ProtoGraph)node.reference; if (graph.Terminated) { DrawList.AddCircleFilled(position, 18, isSelected ? 0xffDDDDDD : 0xFFFFFFFF); DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff0000ff, $"{ImGuiController.FA_ICON_COG}"); DrawList.AddText(position + new Vector2(20, -14), 0xff000000, $"Thread {graph.ThreadID} (Exited)"); } else { DrawList.AddCircleFilled(position, 18, isSelected ? 0xffDDDDDD : 0xFFFFFFFF); DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff00ff00, $"{ImGuiController.FA_ICON_COG}"); DrawList.AddText(position + new Vector2(20, -14), 0xff000000, $"Thread {graph.ThreadID} (Active)"); } } break; case Logging.eTimelineEvent.APICall: Logging.TIMELINE_EVENT apiEvent = (Logging.TIMELINE_EVENT)node.reference; Logging.APICALL apicall = (Logging.APICALL)apiEvent.Item; if (!apicall.APIDetails.HasValue) { return(false); } APIDetailsWin.API_ENTRY details = apicall.APIDetails.Value; DrawList.AddCircleFilled(position, 18, isSelected ? 0xffDDDDDD : 0xFFFFFFFF); switch (details.FilterType) { case "File": DrawList.AddText(font, 20, position - new Vector2(10f, 10f), 0xff000000, $"{ImGuiController.FA_ICON_FILECODE}"); DrawList.AddText(position + new Vector2(20, -15), 0xff000000, "File Interaction"); DrawList.AddText(position + new Vector2(20, 5), 0xff000000, node.label); break; case "Registry": DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff000000, $"{ImGuiController.FA_ICON_SQUAREGRID}"); DrawList.AddText(position + new Vector2(20, -15), 0xff000000, "Registry Interaction"); DrawList.AddText(position + new Vector2(20, 5), 0xff000000, node.label); break; case "Process": DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff000000, $"{ImGuiController.FA_ICON_COGS}"); DrawList.AddText(position + new Vector2(20, -15), 0xff000000, "Process Interaction"); DrawList.AddText(position + new Vector2(20, 5), 0xff000000, node.label); break; case "Network": DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff000000, $"{ImGuiController.FA_ICON_NETWORK}"); DrawList.AddText(position + new Vector2(20, -15), 0xff000000, "Network Interaction"); DrawList.AddText(position + new Vector2(20, 5), 0xff000000, node.label); break; default: DrawList.AddText(font, 25, position - new Vector2(12.5f, 12.5f), 0xff000000, $"{ImGuiController.FA_ICON_UP}"); DrawList.AddText(position + new Vector2(20, -15), 0xff000000, details.FilterType); DrawList.AddText(position + new Vector2(20, 5), 0xff000000, node.label); break; } break; default: DrawList.AddCircleFilled(position, nodeSize, 0xff000000); break; } if (node == _selectedNode) { DrawList.AddCircle(position, 18, 0xff222222); } ImGui.SetCursorScreenPos(position - new Vector2(12, 12)); ImGui.InvisibleButton($"##{position.X}-{position.Y}", new Vector2(25, 25)); bool clicked = false; if (ImGui.IsItemClicked()) { clicked = true; _selectedNode = node; if (_selectedNode.TLtype == Logging.eTimelineEvent.APICall) { SelectedEntity = node; SelectedAPIEvent = (Logging.TIMELINE_EVENT)node.reference; } else { SelectedEntity = null; SelectedAPIEvent = null; } } //Vector2 labelSize = ImGui.CalcTextSize(node.label); //DrawList.AddRectFilled(position, position + labelSize, 0xddffffff); ImGui.SetCursorScreenPos(cursor); return(clicked); }
private void AddThreadItems(ItemNode?parentProcess, TraceRecord trace) { string nodeName = $"PROCNODE_{trace.PID}_{trace.LaunchedTime}"; ItemNode?startProcess = null; lock (_lock) { if (!addedNodes.TryGetValue(nodeName, out startProcess)) { startProcess = new ItemNode(nodeName, Logging.eTimelineEvent.ProcessStart, trace); sbgraph.AddVertex(startProcess); addedNodes[nodeName] = startProcess; if (parentProcess != null) { sbgraph.AddEdge(new Edge <ItemNode>(parentProcess, startProcess)); } } var threads = trace.ProtoGraphs; foreach (var thread in threads) { string threadName = $"THREADNODE_{thread.TraceData.randID}_{thread.ThreadID}"; if (!addedNodes.ContainsKey(threadName)) { ItemNode threadNode = new ItemNode(threadName, Logging.eTimelineEvent.ThreadStart, thread); sbgraph.AddVertex(threadNode); sbgraph.AddEdge(new Edge <ItemNode>(startProcess, threadNode)); addedNodes[threadName] = threadNode; } } var timelineEntries = trace.GetTimeLineEntries(); foreach (Logging.TIMELINE_EVENT timelineEvent in timelineEntries) { if (timelineEvent.TimelineEventType == Logging.eTimelineEvent.APICall) { var call = (Logging.APICALL)(timelineEvent.Item); if (call.APIDetails != null) { APIDetailsWin.API_ENTRY apiinfo = call.APIDetails.Value; if (apiinfo.Effects != null) { ProtoGraph?caller = call.Graph; if (caller is null || call.Node is null || call.Index >= call.Node.callRecordsIndexs.Count) { Logging.RecordLogEvent($"Warning: Call {call.APIDetails.Value.ModuleName}:{call.APIDetails.Value.Symbol} tried to place call {call.Index} on timeline, but only {call.Node?.callRecordsIndexs.Count} recorded"); continue; } int recordsIndex = (int)call.Node.callRecordsIndexs[call.Index]; if (recordsIndex >= caller.SymbolCallRecords.Count) { Logging.RecordLogEvent($"Warning: Call {call.APIDetails.Value.ModuleName}:{call.APIDetails.Value.Symbol} tried to place record {recordsIndex} on timeline, but caller has only {caller.SymbolCallRecords} recorded"); continue; } APICALLDATA APICallRecord = caller.SymbolCallRecords[recordsIndex]; string threadName = $"THREADNODE_{caller.TraceData.randID}_{caller.ThreadID}"; ItemNode threadNode = addedNodes[threadName]; foreach (APIDetailsWin.InteractionEffect effectBase in apiinfo.Effects) { switch (effectBase) { case APIDetailsWin.LinkReferenceEffect linkEffect: { APIDetailsWin.API_PARAM_ENTRY entityParamRecord = apiinfo.LoggedParams[linkEffect.EntityIndex]; APIDetailsWin.API_PARAM_ENTRY referenceParamRecord = apiinfo.LoggedParams[linkEffect.ReferenceIndex]; int entityParamLoggedIndex = APICallRecord.argList.FindIndex(x => x.Item1 == entityParamRecord.Index); int referenceParamLoggedIndex = APICallRecord.argList.FindIndex(x => x.Item1 == referenceParamRecord.Index); if (entityParamLoggedIndex == -1 || referenceParamLoggedIndex == -1) { string error = $"API call record for {apiinfo.ModuleName}:{apiinfo.Symbol} [LinkReference] didn't have correct parameters. The instrumentation library or apidata file may not match."; timelineEvent.MetaError = error; Logging.RecordLogEvent(error, Logging.LogFilterType.Debug); break; } if (!_interactionEntities.TryGetValue(entityParamRecord.EntityType, out Dictionary <string, ItemNode>?entityDict)) { entityDict = new Dictionary <string, ItemNode>(); _interactionEntities.Add(entityParamRecord.EntityType, entityDict); } string entityString = APICallRecord.argList[entityParamLoggedIndex].Item2; ItemNode entityNode; if (!entityDict.ContainsKey(entityString)) { entityNode = new ItemNode(entityString, Logging.eTimelineEvent.APICall, timelineEvent); entityDict.Add(entityString, entityNode); sbgraph.AddVertex(entityNode); addedNodes[entityString] = entityNode; } else { entityNode = addedNodes[entityString]; } AddAPIEdge(threadNode, entityNode, apiinfo.Label); if (!_timelineEventEntities.TryGetValue(timelineEvent, out ItemNode? existingEntity)) { _timelineEventEntities.Add(timelineEvent, entityNode); } Debug.Assert(existingEntity == null || existingEntity == entityNode); //link this entity to the reference the api all created (eg associate a file path with the file handle that 'CreateFile' created) string referenceString = APICallRecord.argList[referenceParamLoggedIndex].Item2; if (referenceParamRecord.NoCase) { referenceString = referenceString.ToLower(); } if (!_interactionEntityReferences.ContainsKey(referenceParamRecord.RawType)) { _interactionEntityReferences.Add(referenceParamRecord.RawType, new Dictionary <string, ItemNode>()); } if (!_interactionEntityReferences[referenceParamRecord.RawType].ContainsKey(referenceString)) { _interactionEntityReferences[referenceParamRecord.RawType].Add(referenceString, entityNode); } break; } // this api performs some action on a refererence to an entity // record this as a label on the edge and link this event to the entity case APIDetailsWin.UseReferenceEffect useEffect: { APIDetailsWin.API_PARAM_ENTRY referenceParamRecord = apiinfo.LoggedParams[useEffect.ReferenceIndex]; int referenceParamLoggedIndex = APICallRecord.argList.FindIndex(x => x.Item1 == referenceParamRecord.Index); if (referenceParamLoggedIndex == -1) { timelineEvent.MetaError = $"API call record for {apiinfo.ModuleName}:{apiinfo.Symbol} [UseReference] didn't have correct parameters"; Logging.RecordLogEvent(timelineEvent.MetaError, Logging.LogFilterType.Debug); break; } string referenceString = APICallRecord.argList[referenceParamLoggedIndex].Item2; if (referenceParamRecord.NoCase) { referenceString = referenceString.ToLower(); } bool resolvedReference = false; if (_interactionEntityReferences.TryGetValue(referenceParamRecord.RawType, out Dictionary <string, ItemNode>?typeEntityList)) { if (typeEntityList.TryGetValue(referenceString, out ItemNode? entityNode)) { resolvedReference = true; if (!_timelineEventEntities.ContainsKey(timelineEvent)) { _timelineEventEntities.Add(timelineEvent, entityNode); } AddAPIEdge(threadNode, entityNode, apiinfo.Label); } } if (!resolvedReference) { timelineEvent.MetaError = $"API call record for {apiinfo.ModuleName}:{apiinfo.Symbol} [UseReference] reference was not linked to an entity ({referenceString})"; Logging.RecordLogEvent(timelineEvent.MetaError, Logging.LogFilterType.Debug); } break; } // this api invalidates a reference // remove the link between the reference and the entity it references case APIDetailsWin.DestroyReferenceEffect destroyEffect: { APIDetailsWin.API_PARAM_ENTRY referenceParamRecord = apiinfo.LoggedParams[destroyEffect.ReferenceIndex]; int referenceParamLoggedIndex = APICallRecord.argList.FindIndex(x => x.Item1 == referenceParamRecord.Index); if (referenceParamLoggedIndex == -1) { timelineEvent.MetaError = $"API call record for {apiinfo.ModuleName}:{apiinfo.Symbol} [DestroyReference] didn't have correct parameters"; Logging.RecordLogEvent(timelineEvent.MetaError, Logging.LogFilterType.Debug); break; } string referenceString = APICallRecord.argList[referenceParamLoggedIndex].Item2; if (referenceParamRecord.NoCase) { referenceString = referenceString.ToLower(); } bool resolvedReference = false; if (_interactionEntityReferences.TryGetValue(referenceParamRecord.RawType, out Dictionary <string, ItemNode>?typeEntityList)) { if (typeEntityList.TryGetValue(referenceString, out ItemNode? entityNode)) { resolvedReference = true; if (!_timelineEventEntities.ContainsKey(timelineEvent)) { _timelineEventEntities.Add(timelineEvent, entityNode); } AddAPIEdge(threadNode, entityNode, apiinfo.Label); typeEntityList.Remove(referenceString); } } if (!resolvedReference) { timelineEvent.MetaError = $"API call record for {apiinfo.ModuleName}:{apiinfo.Symbol} [DestroyReference] reference was not linked to an entity ({referenceString})"; Logging.RecordLogEvent(timelineEvent.MetaError, Logging.LogFilterType.Debug); } break; } default: timelineEvent.MetaError = $"API call record for {apiinfo.ModuleName}:{apiinfo.Symbol}: had invalid effect {effectBase}"; Logging.RecordLogEvent(timelineEvent.MetaError, Logging.LogFilterType.Debug); break; } } } } } } if (startProcess is not null) { foreach (var child in trace.GetChildren()) { string childName = $"PROCNODE_{child.PID}_{child.LaunchedTime}"; if (!addedNodes.TryGetValue(childName, out ItemNode? childProcess) || childProcess is null) { childProcess = new ItemNode(childName, Logging.eTimelineEvent.ProcessStart, child); sbgraph.AddVertex(childProcess); addedNodes[childName] = childProcess; sbgraph.AddEdge(new Edge <ItemNode>(startProcess, childProcess)); } AddThreadItems(parentProcess, child); } } } }