diff --git a/src/sensor/windows/etw.rs b/src/sensor/windows/etw.rs index 13da1d8..024ab22 100644 --- a/src/sensor/windows/etw.rs +++ b/src/sensor/windows/etw.rs @@ -40,7 +40,7 @@ struct EtwProviders; impl EtwProviders { const KERNEL_PROCESS_GUID: &'static str = "22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716"; const KERNEL_NETWORK_GUID: &'static str = "7dd42a49-5329-4832-8dfd-43d979153a88"; - const KERNEL_FILE_GUID: &'static str = "edd08927-9cc4-4e65-b970-63462d3f77bd"; + const KERNEL_FILE_GUID: &'static str = "edd08927-9cc4-4e65-b970-c2560fb5c289"; const KERNEL_REGISTRY_GUID: &'static str = "70eb4f03-c1de-4f73-a051-33d13d5413bd"; const DNS_CLIENT_GUID: &'static str = "1c95126e-7eea-49a9-a3fe-a378b03ddb4d"; const POWERSHELL_GUID: &'static str = "A0C1853B-5C40-4B15-8766-3CF1C58F985A"; @@ -48,16 +48,17 @@ impl EtwProviders { const SERVICE_CONTROL_MANAGER_GUID: &'static str = "555908d1-a6d7-4695-8e1e-26931d2012f4"; const TASK_SCHEDULER_GUID: &'static str = "de7b24ea-73c8-4a09-985d-5bdadcfa9017"; + // Keyword names follow the Microsoft-Windows-Kernel-File manifest. const KERNEL_FILE_KEYWORD_FILENAME: u64 = 0x0010; const KERNEL_FILE_KEYWORD_CREATE: u64 = 0x0080; - const KERNEL_FILE_KEYWORD_DELETE: u64 = 0x0200; - const KERNEL_FILE_KEYWORD_RENAME: u64 = 0x0400; - const KERNEL_FILE_KEYWORD_SETINFO: u64 = 0x0800; + const KERNEL_FILE_KEYWORD_WRITE: u64 = 0x0200; + const KERNEL_FILE_KEYWORD_DELETE_PATH: u64 = 0x0400; + const KERNEL_FILE_KEYWORD_RENAME_SETLINK_PATH: u64 = 0x0800; const FILE_KEYWORDS: u64 = Self::KERNEL_FILE_KEYWORD_FILENAME | Self::KERNEL_FILE_KEYWORD_CREATE - | Self::KERNEL_FILE_KEYWORD_DELETE - | Self::KERNEL_FILE_KEYWORD_RENAME - | Self::KERNEL_FILE_KEYWORD_SETINFO; + | Self::KERNEL_FILE_KEYWORD_WRITE + | Self::KERNEL_FILE_KEYWORD_DELETE_PATH + | Self::KERNEL_FILE_KEYWORD_RENAME_SETLINK_PATH; const REG_KEYWORD_CREATE_KEY: u64 = 0x1000; const REG_KEYWORD_SET_VALUE_KEY: u64 = 0x2000; @@ -165,6 +166,34 @@ impl EtwProviders { } } +/// Microsoft-Windows-Kernel-File manifest event IDs. +const KERNEL_FILE_EVENT_CREATE: u16 = 12; +const KERNEL_FILE_EVENT_DELETE_PATH: u16 = 26; +const KERNEL_FILE_EVENT_RENAME_PATH: u16 = 27; +const KERNEL_FILE_EVENT_SET_LINK_PATH: u16 = 28; + +/// The manifest provider emits file events with opcode 0, so routing must use +/// manifest event IDs rather than MOF FileIo opcodes. +fn kernel_file_action(event_id: u16) -> SensorAction { + match event_id { + KERNEL_FILE_EVENT_CREATE => SensorAction::Create, + KERNEL_FILE_EVENT_DELETE_PATH => SensorAction::Delete, + KERNEL_FILE_EVENT_RENAME_PATH => SensorAction::Rename, + _ => SensorAction::Modify, + } +} + +/// DeletePath, RenamePath, and SetLinkPath carry their path in `FilePath`; +/// the remaining file events use `FileName`. +fn kernel_file_path_property(event_id: u16) -> &'static str { + match event_id { + KERNEL_FILE_EVENT_DELETE_PATH + | KERNEL_FILE_EVENT_RENAME_PATH + | KERNEL_FILE_EVENT_SET_LINK_PATH => "FilePath", + _ => "FileName", + } +} + struct EtwRouting { kernel_process_guid: GUID, guid_to_category: HashMap, @@ -216,12 +245,7 @@ impl EtwRouting { id if id >= 15 => SensorAction::Connect, _ => return None, }, - EventCategory::File => match record.opcode() { - 64 | 65 => SensorAction::Create, - 70 | 72 => SensorAction::Delete, - 71 => SensorAction::Rename, - _ => SensorAction::Modify, - }, + EventCategory::File => kernel_file_action(record.event_id()), EventCategory::Registry => match record.opcode() { 36 => SensorAction::Create, 38 | 41 => SensorAction::Delete, @@ -509,8 +533,14 @@ fn decode_file(parser: &Parser, record: &EventRecord) -> Option let mappings = field_maps::file_event_mappings(); let fields = FileEventFields { source_filename: None, - target_filename: try_get_string(parser, mappings.get_etw_field("TargetFilename")?) - .map(|path| convert_nt_to_dos(&path)), + target_filename: try_get_string_any( + parser, + &[ + kernel_file_path_property(record.event_id()), + mappings.get_etw_field("TargetFilename")?, + ], + ) + .map(|path| convert_nt_to_dos(&path)), process_id: try_get_uint(parser, mappings.get_etw_field("ProcessId")?), image: try_get_string(parser, mappings.get_etw_field("Image")?) .map(|path| convert_nt_to_dos(&path)), @@ -825,6 +855,23 @@ fn parse_optional_u32(value: Option<&str>) -> Option { mod tests { use super::*; + #[test] + fn kernel_file_event_ids_route_to_actions() { + assert_eq!(kernel_file_action(12), SensorAction::Create); + assert_eq!(kernel_file_action(16), SensorAction::Modify); + assert_eq!(kernel_file_action(26), SensorAction::Delete); + assert_eq!(kernel_file_action(27), SensorAction::Rename); + } + + #[test] + fn kernel_file_path_events_read_file_path() { + assert_eq!(kernel_file_path_property(12), "FileName"); + assert_eq!(kernel_file_path_property(16), "FileName"); + assert_eq!(kernel_file_path_property(26), "FilePath"); + assert_eq!(kernel_file_path_property(27), "FilePath"); + assert_eq!(kernel_file_path_property(28), "FilePath"); + } + #[test] fn provider_guids_are_unique() { let providers = EtwProviders::all(); diff --git a/src/sensor/windows/mapper.rs b/src/sensor/windows/mapper.rs index 0cb7a19..6d85929 100644 --- a/src/sensor/windows/mapper.rs +++ b/src/sensor/windows/mapper.rs @@ -41,16 +41,9 @@ fn action_code_for_record( _ => 0, }, }, - EventCategory::File => match record.opcode() { - 64 | 65 | 70 | 71 | 72 => record.opcode(), - _ => match action { - SensorAction::Create => 64, - SensorAction::Delete => 70, - SensorAction::Rename => 71, - SensorAction::Modify => 65, - _ => 0, - }, - }, + // The manifest-based Kernel-File provider emits events with opcode 0, + // so the action code comes from the routed action, not the opcode. + EventCategory::File => file_action_code(action), EventCategory::Registry => match record.opcode() { 36 | 38 | 39 | 41 => record.opcode(), _ => match action { @@ -68,6 +61,19 @@ fn action_code_for_record( } } +/// Maps a routed file action to the platform-shared action code scheme +/// (64 = create, 65 = modify, 70 = delete, 71 = rename) used by the Linux +/// and macOS sensors as well. +fn file_action_code(action: SensorAction) -> u8 { + match action { + SensorAction::Create => 64, + SensorAction::Delete => 70, + SensorAction::Rename => 71, + SensorAction::Modify => 65, + _ => 0, + } +} + fn raw_event_id_for_record(category: EventCategory, action_code: u8, record: &EventRecord) -> u16 { match category { EventCategory::Process | EventCategory::ImageLoad => record.event_id(), @@ -119,8 +125,9 @@ pub fn map_to_sysmon_id(category: EventCategory, action_code: u8, raw_event_id: #[cfg(test)] mod tests { - use super::map_to_sysmon_id; + use super::{file_action_code, map_to_sysmon_id}; use crate::models::EventCategory; + use crate::sensor::SensorAction; #[test] fn process_start_maps_to_sysmon_1() { @@ -132,6 +139,32 @@ mod tests { assert_eq!(map_to_sysmon_id(EventCategory::File, 64, 999), 11); } + #[test] + fn file_actions_map_to_shared_action_codes() { + assert_eq!(file_action_code(SensorAction::Create), 64); + assert_eq!(file_action_code(SensorAction::Modify), 65); + assert_eq!(file_action_code(SensorAction::Delete), 70); + assert_eq!(file_action_code(SensorAction::Rename), 71); + } + + #[test] + fn file_delete_action_maps_to_sysmon_23() { + let code = file_action_code(SensorAction::Delete); + assert_eq!( + map_to_sysmon_id(EventCategory::File, code, u16::from(code)), + 23 + ); + } + + #[test] + fn file_rename_action_keeps_event_id_71() { + let code = file_action_code(SensorAction::Rename); + assert_eq!( + map_to_sysmon_id(EventCategory::File, code, u16::from(code)), + 71 + ); + } + #[test] fn registry_setvalue_maps_to_sysmon_13() { assert_eq!(map_to_sysmon_id(EventCategory::Registry, 39, 999), 13);