diff --git a/src/detections/configs.rs b/src/detections/configs.rs index c371bb38..c2fa589f 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -1,4 +1,6 @@ +use crate::detections::print::AlertMessage; use crate::detections::utils; +use chrono::{DateTime, Utc}; use clap::{App, AppSettings, ArgMatches}; use hashbrown::HashMap; use hashbrown::HashSet; @@ -55,6 +57,8 @@ fn build_app<'a>() -> ArgMatches<'a> { --rfc-2822 'Output date and time in RFC 2822 format. Example: Mon, 07 Aug 2006 12:34:56 -0600' --rfc-3339 'Output date and time in RFC 3339 format. Example: 2006-08-07T12:34:56.485214 -06:00' --verbose 'Output verbose information to target event file path and rule file' + --starttimeline=[STARTTIMELINE] 'Start time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00'' + --endtimeline=[ENDTIMELINE]'End time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00'' -q 'Quiet mode. Do not display the launch banner' -r --rules=[RULEDIRECTORY] 'Rule file directory (default: ./rules)' -m --min-level=[LEVEL] 'Minimum level for rules (default: informational)' @@ -119,6 +123,80 @@ fn load_target_ids(path: &str) -> TargetEventIds { return ret; } +#[derive(Debug, Clone)] +pub struct TargetEventTime { + start_time: Option>, + end_time: Option>, +} + +impl TargetEventTime { + pub fn new() -> Self { + let start_time = if let Some(s_time) = CONFIG.read().unwrap().args.value_of("starttimeline") + { + match DateTime::parse_from_str(s_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00 + .or_else(|_| DateTime::parse_from_str(s_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00 + { + Ok(dt) => Some(dt.with_timezone(&Utc)), + Err(err) => { + AlertMessage::alert( + &mut std::io::stderr().lock(), + format!("starttimeline field: {}", err), + ) + .ok(); + None + } + } + } else { + None + }; + let end_time = if let Some(e_time) = CONFIG.read().unwrap().args.value_of("endtimeline") { + match DateTime::parse_from_str(e_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00 + .or_else(|_| DateTime::parse_from_str(e_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00 + { + Ok(dt) => Some(dt.with_timezone(&Utc)), + Err(err) => { + AlertMessage::alert( + &mut std::io::stderr().lock(), + format!("endtimeline field: {}", err), + ) + .ok(); + None + } + } + } else { + None + }; + return Self::set(start_time, end_time); + } + + pub fn set( + start_time: Option>, + end_time: Option>, + ) -> Self { + return Self { + start_time: start_time, + end_time: end_time, + }; + } + + pub fn is_target(&self, eventtime: &Option>) -> bool { + if eventtime.is_none() { + return true; + } + if let Some(starttime) = self.start_time { + if eventtime.unwrap() < starttime { + return false; + } + } + if let Some(endtime) = self.end_time { + if eventtime.unwrap() > endtime { + return false; + } + } + return true; + } +} + #[derive(Debug, Clone)] pub struct EventKeyAliasConfig { key_to_eventkey: HashMap, @@ -233,26 +311,51 @@ fn load_eventcode_info(path: &str) -> EventInfoConfig { return config; } -// #[cfg(test)] -// mod tests { +#[cfg(test)] +mod tests { + use crate::detections::configs; + use chrono::{DateTime, Utc}; -// use crate::detections::configs; + // #[test] + // #[ignore] + // fn singleton_read_and_write() { + // let message = + // "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }"; + // configs::EVENT_KEY_ALIAS_CONFIG = + // configs::load_eventkey_alias("test_files/config/eventkey_alias.txt"); + // let display = format!( + // "{}", + // format_args!( + // "{:?}", + // configs::CONFIG.write().unwrap().event_key_alias_config + // ) + // ); + // assert_eq!(message, display); + // } + // } -// #[test] -// #[ignore] -// fn singleton_read_and_write() { -// let message = -// "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }"; -// configs::EVENT_KEY_ALIAS_CONFIG = -// configs::load_eventkey_alias("test_files/config/eventkey_alias.txt"); + #[test] + fn target_event_time_filter() { + let start_time = Some("2018-02-20T12:00:09Z".parse::>().unwrap()); + let end_time = Some("2020-03-30T12:00:09Z".parse::>().unwrap()); + let time_filter = configs::TargetEventTime::set(start_time, end_time); -// let display = format!( -// "{}", -// format_args!( -// "{:?}", -// configs::CONFIG.write().unwrap().event_key_alias_config -// ) -// ); -// assert_eq!(message, display); -// } -// } + let out_of_range1 = Some("1999-01-01T12:00:09Z".parse::>().unwrap()); + let within_range = Some("2019-02-27T01:05:01Z".parse::>().unwrap()); + let out_of_range2 = Some("2021-02-27T01:05:01Z".parse::>().unwrap()); + + assert_eq!(time_filter.is_target(&out_of_range1), false); + assert_eq!(time_filter.is_target(&within_range), true); + assert_eq!(time_filter.is_target(&out_of_range2), false); + } + + #[test] + fn target_event_time_filter_containes_on_time() { + let start_time = Some("2018-02-20T12:00:09Z".parse::>().unwrap()); + let end_time = Some("2020-03-30T12:00:09Z".parse::>().unwrap()); + let time_filter = configs::TargetEventTime::set(start_time, end_time); + + assert_eq!(time_filter.is_target(&start_time), true); + assert_eq!(time_filter.is_target(&end_time), true); + } +} diff --git a/src/detections/print.rs b/src/detections/print.rs index 30114b8d..594aa504 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -1,5 +1,6 @@ extern crate lazy_static; use crate::detections::configs; +use crate::detections::utils; use crate::detections::utils::get_serde_number_to_string; use chrono::{DateTime, TimeZone, Utc}; use lazy_static::lazy_static; @@ -169,23 +170,7 @@ impl Message { pub fn get_event_time(event_record: &Value) -> Option> { let system_time = &event_record["Event"]["System"]["TimeCreated_attributes"]["SystemTime"]; - let system_time_str = system_time.as_str().unwrap_or(""); - if system_time_str.is_empty() { - return Option::None; - } - - let rfc3339_time = DateTime::parse_from_rfc3339(system_time_str); - if rfc3339_time.is_err() { - return Option::None; - } - let datetime = Utc - .from_local_datetime(&rfc3339_time.unwrap().naive_utc()) - .single(); - if datetime.is_none() { - return Option::None; - } else { - return Option::Some(datetime.unwrap()); - } + return utils::str_time_to_datetime(system_time.as_str().unwrap_or("")); } /// message内のマップをクリアする。テストする際の冪等性の担保のため作成。 diff --git a/src/detections/utils.rs b/src/detections/utils.rs index ef14247d..72d6448c 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -7,6 +7,7 @@ use crate::detections::configs; use tokio::runtime::Builder; use tokio::runtime::Runtime; +use chrono::{DateTime, TimeZone, Utc}; use regex::Regex; use serde_json::Value; use std::fs::File; @@ -93,6 +94,29 @@ pub fn get_event_id_key() -> String { return "Event.System.EventID".to_string(); } +pub fn get_event_time() -> String { + return "Event.System.TimeCreated_attributes.SystemTime".to_string(); +} + +pub fn str_time_to_datetime(system_time_str: &str) -> Option> { + if system_time_str.is_empty() { + return Option::None; + } + + let rfc3339_time = DateTime::parse_from_rfc3339(system_time_str); + if rfc3339_time.is_err() { + return Option::None; + } + let datetime = Utc + .from_local_datetime(&rfc3339_time.unwrap().naive_utc()) + .single(); + if datetime.is_none() { + return Option::None; + } else { + return Option::Some(datetime.unwrap()); + } +} + /// serde:Valueの型を確認し、文字列を返します。 pub fn get_serde_number_to_string(value: &serde_json::Value) -> Option { if value.is_string() { diff --git a/src/main.rs b/src/main.rs index 8e2dfe68..7bce8cc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -165,6 +165,8 @@ fn analysis_file( let mut records = parser.records_json_value(); let tokio_rt = utils::create_tokio_runtime(); + let target_event_time = configs::TargetEventTime::new(); + loop { let mut records_per_detect = vec![]; while records_per_detect.len() < MAX_DETECT_RECORDS { @@ -192,6 +194,14 @@ fn analysis_file( continue; } + let eventtime = utils::get_event_value(&utils::get_event_time(), &data); + if eventtime.is_some() { + let time = utils::str_time_to_datetime(eventtime.unwrap().as_str().unwrap_or("")); + if !target_event_time.is_target(&time) { + continue; + } + } + // EvtxRecordInfo構造体に変更 records_per_detect.push(_create_rec_info(data, &filepath_disp)); }