use super::decode::{DecodedEvent, EventField, decode_trace_event};
use crate::types::{ProcessId, ThreadId};
use std::time::SystemTime;
use windows::core::GUID;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SystemProvider {
Process,
Registry,
Network,
FileIo,
ImageLoad,
}
impl SystemProvider {
pub(crate) fn trace_flags(self) -> u32 {
use windows::Win32::System::Diagnostics::Etw::*;
match self {
SystemProvider::Process => (EVENT_TRACE_FLAG_PROCESS | EVENT_TRACE_FLAG_THREAD).0,
SystemProvider::Registry => EVENT_TRACE_FLAG_REGISTRY.0,
SystemProvider::Network => EVENT_TRACE_FLAG_NETWORK_TCPIP.0,
SystemProvider::FileIo => (EVENT_TRACE_FLAG_FILE_IO | EVENT_TRACE_FLAG_FILE_IO_INIT).0,
SystemProvider::ImageLoad => EVENT_TRACE_FLAG_IMAGE_LOAD.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum TraceLevel {
Critical = 1,
Error = 2,
Warning = 3,
Info = 4,
Verbose = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ThreadContext {
pub process_id: ProcessId,
pub thread_id: ThreadId,
}
impl ThreadContext {
pub fn new(process_id: ProcessId, thread_id: ThreadId) -> Self {
Self {
process_id,
thread_id,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StackTrace {
pub match_id: u64,
pub frames: Vec<u64>,
}
impl StackTrace {
pub fn new(match_id: u64, frames: Vec<u64>) -> Self {
Self { match_id, frames }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CpuSample {
pub processor_number: u8,
}
impl CpuSample {
pub fn new(processor_number: u8) -> Self {
Self { processor_number }
}
}
#[derive(Debug, Clone)]
pub struct TraceEvent {
pub id: u16,
pub version: u8,
pub opcode: u8,
pub level: u8,
pub provider_guid: GUID,
pub process_id: ProcessId,
pub thread_id: ThreadId,
pub timestamp: SystemTime,
pub data: Vec<u8>,
pub thread_context: Option<ThreadContext>,
pub stack_trace: Option<StackTrace>,
pub cpu_sample: Option<CpuSample>,
fields: Option<Vec<EventField>>,
}
impl TraceEvent {
pub fn from_event_record(
record: &windows::Win32::System::Diagnostics::Etw::EVENT_RECORD,
) -> Self {
Self::from_event_record_with_fields(record, None)
}
pub(crate) fn from_event_record_with_fields(
record: &windows::Win32::System::Diagnostics::Etw::EVENT_RECORD,
fields: Option<Vec<EventField>>,
) -> Self {
let header = &record.EventHeader;
let desc = &header.EventDescriptor;
let timestamp = filetime_to_systemtime(header.TimeStamp);
let data = if record.UserDataLength > 0 && !record.UserData.is_null() {
unsafe {
std::slice::from_raw_parts(
record.UserData as *const u8,
record.UserDataLength as usize,
)
.to_vec()
}
} else {
Vec::new()
};
TraceEvent {
id: desc.Id,
version: desc.Version,
opcode: desc.Opcode,
level: desc.Level,
provider_guid: header.ProviderId,
process_id: ProcessId::new(header.ProcessId),
thread_id: ThreadId::new(header.ThreadId),
timestamp,
data,
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields,
}
}
pub fn decode(&self) -> DecodedEvent {
decode_trace_event(self)
}
pub fn fields(&self) -> Option<&[EventField]> {
self.fields.as_deref()
}
}
fn filetime_to_systemtime(filetime: i64) -> SystemTime {
const FILETIME_TO_UNIX_EPOCH: i64 = 116_444_736_000_000_000;
const NANOS_PER_100NS: u32 = 100;
let intervals_since_unix = filetime.saturating_sub(FILETIME_TO_UNIX_EPOCH);
let seconds = (intervals_since_unix / 10_000_000) as u64;
let nanos = ((intervals_since_unix % 10_000_000) * NANOS_PER_100NS as i64) as u32;
SystemTime::UNIX_EPOCH + std::time::Duration::new(seconds, nanos)
}
#[cfg(test)]
mod tests {
use super::{DecodedEvent, TraceEvent};
use crate::etw::{
EventField, EventFieldValue, FileIoOperation, RegistryOperation, TcpOperation,
};
use crate::types::{ProcessId, ThreadId};
use std::time::SystemTime;
use windows::Win32::System::Diagnostics::Etw::{
FileIoGuid, ImageLoadGuid, ProcessGuid, RegistryGuid, TcpIpGuid,
};
#[test]
fn decode_process_v0_start_event() {
let event = TraceEvent {
id: 0,
version: 0,
opcode: 1,
level: 0,
provider_guid: ProcessGuid,
process_id: ProcessId::new(672),
thread_id: ThreadId::new(0),
timestamp: SystemTime::UNIX_EPOCH,
data: vec![
160, 2, 0, 0, 220, 7, 0, 0, 0, 0, 0, 0, 96, 140, 79, 210, 6, 180, 255, 255, 0, 0,
0, 0, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 54, 245, 194, 143, 120, 21,
213, 94, 151, 105, 93, 135, 89, 4, 0, 0, 99, 109, 100, 46, 101, 120, 101, 0, 0, 0,
0, 0,
],
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields: None,
};
match event.decode() {
DecodedEvent::ProcessStart(decoded) => {
assert_eq!(decoded.process_id, ProcessId::new(672));
assert_eq!(decoded.parent_process_id, ProcessId::new(2012));
assert_eq!(decoded.image_file_name, "cmd.exe");
}
other => panic!("unexpected decode result: {other:?}"),
}
}
#[test]
fn decode_image_v2_v3_v4_load_event() {
let event = TraceEvent {
id: 0,
version: 3,
opcode: 10,
level: 0,
provider_guid: ImageLoadGuid,
process_id: ProcessId::new(3996),
thread_id: ThreadId::new(0),
timestamp: SystemTime::UNIX_EPOCH,
data: vec![
0, 0, 8, 72, 251, 127, 0, 0, 0, 32, 8, 0, 0, 0, 0, 0, 156, 15, 0, 0, 70, 110, 8, 0,
255, 233, 215, 246, 12, 1, 0, 0, 0, 0, 8, 72, 251, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 68, 0, 101, 0, 118, 0, 105, 0, 99, 0, 101, 0, 92,
0, 72, 0, 97, 0, 114, 0, 100, 0, 100, 0, 105, 0, 115, 0, 107, 0, 86, 0, 111, 0,
108, 0, 117, 0, 109, 0, 101, 0, 50, 0, 92, 0, 87, 0, 105, 0, 110, 0, 100, 0, 111,
0, 119, 0, 115, 0, 92, 0, 83, 0, 121, 0, 115, 0, 116, 0, 101, 0, 109, 0, 51, 0, 50,
0, 92, 0, 98, 0, 99, 0, 114, 0, 121, 0, 112, 0, 116, 0, 112, 0, 114, 0, 105, 0,
109, 0, 105, 0, 116, 0, 105, 0, 118, 0, 101, 0, 115, 0, 46, 0, 100, 0, 108, 0, 108,
0, 0, 0,
],
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields: None,
};
for version in [2u8, 3u8, 4u8] {
let mut candidate = event.clone();
candidate.version = version;
match candidate.decode() {
DecodedEvent::ImageLoad(decoded) => {
assert_eq!(decoded.process_id, ProcessId::new(3996));
assert!(decoded.file_name.ends_with("bcryptprimitives.dll"));
assert_eq!(decoded.version, version);
}
other => panic!("unexpected decode result for version {version}: {other:?}"),
}
}
}
#[test]
fn decode_generic_from_preparsed_fields() {
let event = TraceEvent {
id: 999,
version: 1,
opcode: 42,
level: 4,
provider_guid: windows::core::GUID::from_u128(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa),
process_id: ProcessId::new(1),
thread_id: ThreadId::new(1),
timestamp: SystemTime::UNIX_EPOCH,
data: Vec::new(),
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields: Some(vec![EventField {
name: "Example".to_string(),
value: EventFieldValue::U32(7),
}]),
};
match event.decode() {
DecodedEvent::Generic(fields) => {
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].name, "Example");
}
other => panic!("unexpected decode result: {other:?}"),
}
}
#[test]
fn decode_typed_tcp_from_preparsed_fields() {
let event = TraceEvent {
id: 10,
version: 2,
opcode: 10,
level: 4,
provider_guid: TcpIpGuid,
process_id: ProcessId::new(1234),
thread_id: ThreadId::new(1),
timestamp: SystemTime::UNIX_EPOCH,
data: Vec::new(),
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields: Some(vec![
EventField {
name: "PID".to_string(),
value: EventFieldValue::U32(1234),
},
EventField {
name: "saddr".to_string(),
value: EventFieldValue::String("10.0.0.10".to_string()),
},
EventField {
name: "sport".to_string(),
value: EventFieldValue::U16(5050),
},
EventField {
name: "daddr".to_string(),
value: EventFieldValue::String("1.1.1.1".to_string()),
},
EventField {
name: "dport".to_string(),
value: EventFieldValue::U16(443),
},
]),
};
match event.decode() {
DecodedEvent::Tcp(tcp) => {
assert_eq!(tcp.operation, TcpOperation::Send);
assert_eq!(tcp.process_id, Some(ProcessId::new(1234)));
assert_eq!(tcp.destination_port, Some(443));
}
other => panic!("unexpected decode result: {other:?}"),
}
}
#[test]
fn decode_typed_registry_from_preparsed_fields() {
let event = TraceEvent {
id: 14,
version: 1,
opcode: 14,
level: 4,
provider_guid: RegistryGuid,
process_id: ProcessId::new(5678),
thread_id: ThreadId::new(1),
timestamp: SystemTime::UNIX_EPOCH,
data: Vec::new(),
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields: Some(vec![
EventField {
name: "ProcessId".to_string(),
value: EventFieldValue::U32(5678),
},
EventField {
name: "KeyName".to_string(),
value: EventFieldValue::String("\\Registry\\Machine\\SOFTWARE".to_string()),
},
EventField {
name: "ValueName".to_string(),
value: EventFieldValue::String("TestValue".to_string()),
},
EventField {
name: "Status".to_string(),
value: EventFieldValue::U32(0),
},
]),
};
match event.decode() {
DecodedEvent::Registry(reg) => {
assert_eq!(reg.operation, RegistryOperation::SetValue);
assert_eq!(reg.process_id, Some(ProcessId::new(5678)));
assert_eq!(reg.value_name.as_deref(), Some("TestValue"));
}
other => panic!("unexpected decode result: {other:?}"),
}
}
#[test]
fn decode_typed_fileio_from_preparsed_fields() {
let event = TraceEvent {
id: 32,
version: 1,
opcode: 32,
level: 4,
provider_guid: FileIoGuid,
process_id: ProcessId::new(2222),
thread_id: ThreadId::new(1),
timestamp: SystemTime::UNIX_EPOCH,
data: Vec::new(),
thread_context: None,
stack_trace: None,
cpu_sample: None,
fields: Some(vec![
EventField {
name: "ProcessId".to_string(),
value: EventFieldValue::U32(2222),
},
EventField {
name: "OpenPath".to_string(),
value: EventFieldValue::String("C:\\Temp\\test.txt".to_string()),
},
EventField {
name: "FileObject".to_string(),
value: EventFieldValue::U64(0x1000),
},
EventField {
name: "IrpPtr".to_string(),
value: EventFieldValue::Pointer(0x2000),
},
EventField {
name: "CreateOptions".to_string(),
value: EventFieldValue::U32(0x20),
},
]),
};
match event.decode() {
DecodedEvent::FileIo(file) => {
assert_eq!(file.operation, FileIoOperation::Create);
assert_eq!(file.process_id, Some(ProcessId::new(2222)));
assert_eq!(file.open_path.as_deref(), Some("C:\\Temp\\test.txt"));
assert_eq!(file.file_object, Some(0x1000));
assert_eq!(file.irp_ptr, Some(0x2000));
}
other => panic!("unexpected decode result: {other:?}"),
}
}
}