361 lines
12 KiB
C#
361 lines
12 KiB
C#
|
//
|
|||
|
// Copyright (c) 2022-present, Trail of Bits, Inc.
|
|||
|
// All rights reserved.
|
|||
|
//
|
|||
|
// This source code is licensed in accordance with the terms specified in
|
|||
|
// the LICENSE file found in the root directory of this source tree.
|
|||
|
//
|
|||
|
using BrightIdeasSoftware;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using System.Drawing;
|
|||
|
using RpcInvestigator.Util;
|
|||
|
using System.Windows.Forms;
|
|||
|
using System.Diagnostics;
|
|||
|
using System.IO;
|
|||
|
using System.Reflection;
|
|||
|
using RpcInvestigator.TabPages;
|
|||
|
|
|||
|
namespace RpcInvestigator.Windows.Controls
|
|||
|
{
|
|||
|
|
|||
|
internal class SnifferListview : FastObjectListView
|
|||
|
{
|
|||
|
private List<string> m_ChosenColumns;
|
|||
|
private readonly SnifferCallbackTable m_Callbacks;
|
|||
|
private Settings m_Settings;
|
|||
|
|
|||
|
public SnifferListview(
|
|||
|
SnifferCallbackTable Callbacks,
|
|||
|
Settings Settings
|
|||
|
)
|
|||
|
{
|
|||
|
m_Settings = Settings;
|
|||
|
m_Callbacks = Callbacks;
|
|||
|
DoubleBuffered = true;
|
|||
|
Visible = false;
|
|||
|
BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
|||
|
CellEditUseWholeCell = false;
|
|||
|
Dock = System.Windows.Forms.DockStyle.Fill;
|
|||
|
FullRowSelect = true;
|
|||
|
GridLines = true;
|
|||
|
HideSelection = false;
|
|||
|
Location = new System.Drawing.Point(6, 130);
|
|||
|
Margin = new System.Windows.Forms.Padding(6, 6, 6, 6);
|
|||
|
Name = "snifferListview";
|
|||
|
ShowGroups = false;
|
|||
|
Size = new System.Drawing.Size(2940, 1336);
|
|||
|
TabIndex = 0;
|
|||
|
UseAlternatingBackColors = true;
|
|||
|
View = View.Details;
|
|||
|
AlternateRowBackColor = Color.LightBlue;
|
|||
|
UseCompatibleStateImageBehavior = false;
|
|||
|
VirtualMode = true;
|
|||
|
if (m_Settings.m_SnifferColumns.Count() == 0)
|
|||
|
{
|
|||
|
m_ChosenColumns = new List<string>() {
|
|||
|
"Timestamp",
|
|||
|
"ProcessId",
|
|||
|
"ThreadId",
|
|||
|
"UserSid",
|
|||
|
"Task",
|
|||
|
"Opcode",
|
|||
|
"InterfaceUuid",
|
|||
|
"ProcNum",
|
|||
|
"Protocol",
|
|||
|
"NetworkAddress",
|
|||
|
"Endpoint",
|
|||
|
"Params",
|
|||
|
"AuthenticationLevel",
|
|||
|
"ImpersonationLevel"
|
|||
|
};
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_ChosenColumns = m_Settings.m_SnifferColumns;
|
|||
|
}
|
|||
|
|
|||
|
DoubleClick += new EventHandler((object obj, EventArgs e2) =>
|
|||
|
{
|
|||
|
if (SelectedObjects == null || SelectedObjects.Count == 0)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
var selectedRow = SelectedObjects.Cast<ParsedEtwEvent>().ToList()[0];
|
|||
|
});
|
|||
|
CellRightClick += RightClickHandler;
|
|||
|
}
|
|||
|
|
|||
|
public
|
|||
|
void
|
|||
|
ChooseColumns(Guid EtwProviderGuid)
|
|||
|
{
|
|||
|
var allColumns = new List<string>();
|
|||
|
try
|
|||
|
{
|
|||
|
//
|
|||
|
// Add any member of the ParsedEtwEvent class that is marked OLVColumn
|
|||
|
//
|
|||
|
var props = ReflectionHelper.GetOlvAttributes(typeof(ParsedEtwEvent));
|
|||
|
allColumns.AddRange(props);
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
MessageBox.Show("Unable to reflect ParsedEtwEvent: " + ex.Message);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
//
|
|||
|
// Add columns for custom fields inside UserData.
|
|||
|
//
|
|||
|
var info = EtwProviderParser.GetDistinctProviderEventInfo(
|
|||
|
EtwProviderGuid);
|
|||
|
if (info.ContainsKey("UserDataProperties"))
|
|||
|
{
|
|||
|
allColumns.AddRange(info["UserDataProperties"]);
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
MessageBox.Show("Unable to load columns: " +
|
|||
|
ex.Message);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
using (var chooser = new EtwColumnPicker(
|
|||
|
allColumns, m_ChosenColumns, m_Settings))
|
|||
|
{
|
|||
|
chooser.ShowDialog();
|
|||
|
if (chooser.DialogResult == DialogResult.OK)
|
|||
|
{
|
|||
|
m_ChosenColumns = chooser.m_SelectedColumns;
|
|||
|
BuildColumns();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public
|
|||
|
void
|
|||
|
SetGroupingStrategy(bool EnableGroupingByActivity)
|
|||
|
{
|
|||
|
if (!EnableGroupingByActivity)
|
|||
|
{
|
|||
|
ShowGroups = false;
|
|||
|
return;
|
|||
|
}
|
|||
|
ShowGroups = true;
|
|||
|
AlwaysGroupByColumn = AllColumns[4]; // ActivityID
|
|||
|
BuildGroups();
|
|||
|
}
|
|||
|
|
|||
|
public
|
|||
|
void
|
|||
|
BuildColumns()
|
|||
|
{
|
|||
|
//
|
|||
|
// Add UserData columns that were chosen by the user (or the default ones)
|
|||
|
//
|
|||
|
var cols = new List<OLVColumn>();
|
|||
|
foreach (var columnName in m_ChosenColumns)
|
|||
|
{
|
|||
|
OLVColumn col;
|
|||
|
|
|||
|
if (columnName == "Params")
|
|||
|
{
|
|||
|
//
|
|||
|
// This is a special-case hack for Params, which is an
|
|||
|
// array of structs. This particular property is useful
|
|||
|
// for RPC, so we'll manually handle it. Other arrays
|
|||
|
// are not generally handled, because we have no idea
|
|||
|
// how many elements are there. Columns cannot be created
|
|||
|
// randomly on the fly as data comes in, so we have to
|
|||
|
// choose a random number of array elements to support now.
|
|||
|
// The column names produced below are aligned with how
|
|||
|
// EtwEventParser handles storing array elements in the
|
|||
|
// UserDataProperties array.
|
|||
|
//
|
|||
|
for (int i = 0; i < 4; i++)
|
|||
|
{
|
|||
|
col = new OLVColumn("Params-" + i, null);
|
|||
|
col.Renderer = new NoEllipsisRenderer();
|
|||
|
col.Name = "Params-" + i;
|
|||
|
col.AspectGetter = delegate (object Row)
|
|||
|
{
|
|||
|
if (Row == null)
|
|||
|
{
|
|||
|
return "";
|
|||
|
}
|
|||
|
var row = (ParsedEtwEvent)Row;
|
|||
|
if (!row.UserDataProperties.ContainsKey(col.Name))
|
|||
|
{
|
|||
|
return "";
|
|||
|
}
|
|||
|
return row.UserDataProperties[col.Name];
|
|||
|
};
|
|||
|
col.IsVisible = true;
|
|||
|
cols.Add(col);
|
|||
|
}
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
col = new OLVColumn(columnName, null);
|
|||
|
col.Renderer = new NoEllipsisRenderer();
|
|||
|
col.Name = columnName;
|
|||
|
col.AspectGetter = delegate (object Row)
|
|||
|
{
|
|||
|
if (Row == null)
|
|||
|
{
|
|||
|
return "";
|
|||
|
}
|
|||
|
var row = (ParsedEtwEvent)Row;
|
|||
|
//
|
|||
|
// Use reflection to try to pull the value directly from a field
|
|||
|
// in the structure that matches this column name.
|
|||
|
//
|
|||
|
object value = null;
|
|||
|
try
|
|||
|
{
|
|||
|
var properties = Row.GetType().GetProperties(
|
|||
|
BindingFlags.Public | BindingFlags.Instance);
|
|||
|
properties.ToList().ForEach(property =>
|
|||
|
{
|
|||
|
if (property.Name.ToLower() == col.Name.ToLower())
|
|||
|
{
|
|||
|
value = property.GetValue(Row, null);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
catch (Exception) { }
|
|||
|
|
|||
|
//
|
|||
|
// Next, try the UserDataProperties array.
|
|||
|
//
|
|||
|
if (value == null)
|
|||
|
{
|
|||
|
if (!row.UserDataProperties.ContainsKey(col.Name))
|
|||
|
{
|
|||
|
return "";
|
|||
|
}
|
|||
|
return row.UserDataProperties[col.Name];
|
|||
|
}
|
|||
|
return value;
|
|||
|
};
|
|||
|
col.AspectToStringConverter = delegate (object Item)
|
|||
|
{
|
|||
|
if (Item == null)
|
|||
|
{
|
|||
|
return "";
|
|||
|
}
|
|||
|
if (col.Name == "ProcessId" || col.Name == "ProcessID"
|
|||
|
|| col.Name == "ThreadId")
|
|||
|
{
|
|||
|
return Item.ToString();
|
|||
|
}
|
|||
|
else if (col.Name == "UserSid")
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
else if (long.TryParse(Item.ToString(), out long value))
|
|||
|
{
|
|||
|
return "0x" + value.ToString("X");
|
|||
|
}
|
|||
|
return Item.ToString();
|
|||
|
};
|
|||
|
col.IsVisible = true;
|
|||
|
cols.Add(col);
|
|||
|
}
|
|||
|
AllColumns.AddRange(cols.ToArray());
|
|||
|
foreach (var column in AllColumns)
|
|||
|
{
|
|||
|
column.MaximumWidth = -1;
|
|||
|
}
|
|||
|
AllColumns = cols;
|
|||
|
RebuildColumns();
|
|||
|
}
|
|||
|
|
|||
|
public
|
|||
|
void
|
|||
|
Update(List<ParsedEtwEvent> Events)
|
|||
|
{
|
|||
|
SuspendLayout();
|
|||
|
AddObjects(Events);
|
|||
|
ResumeLayout();
|
|||
|
}
|
|||
|
|
|||
|
public
|
|||
|
new
|
|||
|
void
|
|||
|
Reset()
|
|||
|
{
|
|||
|
ClearObjects();
|
|||
|
Groups.Clear();
|
|||
|
}
|
|||
|
|
|||
|
public
|
|||
|
void
|
|||
|
SaveAsText()
|
|||
|
{
|
|||
|
if (Objects == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var data = Objects.Cast<ParsedEtwEvent>().ToList();
|
|||
|
if (data.Count == 0)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
StringBuilder sb = new StringBuilder();
|
|||
|
data.ForEach(ev =>
|
|||
|
{
|
|||
|
sb.AppendLine(ev.ToString());
|
|||
|
});
|
|||
|
var location = Path.GetRandomFileName() + ".txt";
|
|||
|
File.WriteAllText(location, sb.ToString());
|
|||
|
Process.Start(location);
|
|||
|
}
|
|||
|
|
|||
|
private
|
|||
|
void
|
|||
|
RightClickHandler(
|
|||
|
object Obj,
|
|||
|
CellRightClickEventArgs Args
|
|||
|
)
|
|||
|
{
|
|||
|
TabPages.ContextMenu.BuildRightClickMenu(Args, new List<ToolStripMenuItem>{
|
|||
|
new ToolStripMenuItem("Open in Library", null, ContextMenuOpenInLibrary),
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
private
|
|||
|
void
|
|||
|
ContextMenuOpenInLibrary(
|
|||
|
object Sender,
|
|||
|
EventArgs Args
|
|||
|
)
|
|||
|
{
|
|||
|
object tag = ((ToolStripMenuItem)Sender).Tag;
|
|||
|
if (tag == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
var args = (CellRightClickEventArgs)tag;
|
|||
|
var evt = args.Model as ParsedEtwEvent;
|
|||
|
if (!evt.UserDataProperties.ContainsKey("InterfaceUuid"))
|
|||
|
{
|
|||
|
MessageBox.Show("Unable to show RPC server details because " +
|
|||
|
"there is no interface UUID present in the ETW event.");
|
|||
|
return;
|
|||
|
}
|
|||
|
var interfaceId = evt.UserDataProperties["InterfaceUuid"].Replace(
|
|||
|
"{", "").Replace("}", "");
|
|||
|
m_Callbacks.ShowRpcServerDetailsCallback(interfaceId);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|