use crate::detections::print::AlertMessage; use crate::detections::utils; use chrono::{DateTime, Utc}; use clap::{App, AppSettings, ArgMatches}; use hashbrown::HashMap; use hashbrown::HashSet; use lazy_static::lazy_static; use std::io::BufWriter; use std::sync::RwLock; lazy_static! { pub static ref CONFIG: RwLock = RwLock::new(ConfigReader::new()); pub static ref LEVELMAP: HashMap = { let mut levelmap = HashMap::new(); levelmap.insert("INFORMATIONAL".to_owned(), 1); levelmap.insert("LOW".to_owned(), 2); levelmap.insert("MEDIUM".to_owned(), 3); levelmap.insert("HIGH".to_owned(), 4); levelmap.insert("CRITICAL".to_owned(), 5); return levelmap; }; pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias("config/eventkey_alias.txt"); } #[derive(Clone)] pub struct ConfigReader { pub args: ArgMatches<'static>, pub event_timeline_config: EventInfoConfig, pub target_eventids: TargetEventIds, } impl ConfigReader { pub fn new() -> Self { ConfigReader { args: build_app(), event_timeline_config: load_eventcode_info("config/timeline_event_info.txt"), target_eventids: load_target_ids("config/target_eventids.txt"), } } } fn build_app<'a>() -> ArgMatches<'a> { let program = std::env::args() .nth(0) .and_then(|s| { std::path::PathBuf::from(s) .file_stem() .map(|s| s.to_string_lossy().into_owned()) }) .unwrap(); if is_test_mode() { return ArgMatches::default(); } let usages = "-d --directory=[DIRECTORY] 'Directory of multiple .evtx files.' -f --filepath=[FILEPATH] 'File path to one .evtx file.' -r --rules=[RULEDIRECTORY/RULEFILE] 'Rule file or directory (default: ./rules)' -c --color 'Output with color. (Terminal needs to support True Color.)' -o --output=[CSV_TIMELINE] 'Save the timeline in CSV format. (example: results.csv)' -v --verbose 'Output verbose information.' -D --enable-deprecated-rules 'Enable sigma rules marked as deprecated.' -n --enable-noisy-rules 'Enable rules marked as noisy.' -u --update-rules 'Clone latest hayabusa-rule' -m --min-level=[LEVEL] 'Minimum level for rules. (default: informational)' -l --live-analysis 'Analyze to WINDIR\\System32\\winevt\\Logs (Windows Only. Need Administrator privileges.)' --start-timeline=[STARTTIMELINE] 'Start time of the event to load from event file. (example: '2018/11/28 12:00:00 +09:00')' --end-timeline=[ENDTIMELINE] 'End time of the event to load from event file. (example: '2018/11/28 12:00:00 +09:00')' --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)' -U --utc 'Output time in UTC format. (default: local time)' -t --thread-number=[NUMBER] 'Thread number. (default: optimal number for performance.)' -s --statistics 'Prints statistics of event IDs.' -q --quiet 'Quiet mode. Do not display the launch banner.' -Q --quiet-errors 'Quiet errors mode. Do not save error logs.' --contributors 'Prints the list of contributors.'"; App::new(&program) .about("Hayabusa: Aiming to be the world's greatest Windows event log analysis tool!") .version("1.1.0") .author("Yamato Security (https://github.com/Yamato-Security/hayabusa)") .setting(AppSettings::VersionlessSubcommands) .usage(usages) .args_from_usage(usages) .get_matches() } fn is_test_mode() -> bool { for i in std::env::args() { if i == "--test" { return true; } } return false; } #[derive(Debug, Clone)] pub struct TargetEventIds { ids: HashSet, } impl TargetEventIds { pub fn new() -> TargetEventIds { return TargetEventIds { ids: HashSet::new(), }; } pub fn is_target(&self, id: &String) -> bool { // 中身が空の場合は全EventIdを対象とする。 if self.ids.is_empty() { return true; } return self.ids.contains(id); } } fn load_target_ids(path: &str) -> TargetEventIds { let mut ret = TargetEventIds::new(); let lines = utils::read_txt(path); // ファイルが存在しなければエラーとする if lines.is_err() { AlertMessage::alert( &mut BufWriter::new(std::io::stderr().lock()), &lines.as_ref().unwrap_err(), ) .ok(); return ret; } for line in lines.unwrap() { if line.is_empty() { continue; } ret.ids.insert(line); } 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("start-timeline") { 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 BufWriter::new(std::io::stderr().lock()), &format!("start-timeline field: {}", err), ) .ok(); None } } } else { None }; let end_time = if let Some(e_time) = CONFIG.read().unwrap().args.value_of("end-timeline") { 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 BufWriter::new(std::io::stderr().lock()), &format!("end-timeline 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, key_to_split_eventkey: HashMap>, } impl EventKeyAliasConfig { pub fn new() -> EventKeyAliasConfig { return EventKeyAliasConfig { key_to_eventkey: HashMap::new(), key_to_split_eventkey: HashMap::new(), }; } pub fn get_event_key(&self, alias: &String) -> Option<&String> { return self.key_to_eventkey.get(alias); } pub fn get_event_key_split(&self, alias: &String) -> Option<&Vec> { return self.key_to_split_eventkey.get(alias); } } fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig { let mut config = EventKeyAliasConfig::new(); let read_result = utils::read_csv(path); if read_result.is_err() { AlertMessage::alert( &mut BufWriter::new(std::io::stderr().lock()), &read_result.as_ref().unwrap_err(), ) .ok(); return config; } // eventkey_aliasが読み込めなかったらエラーで終了とする。 read_result.unwrap().into_iter().for_each(|line| { if line.len() != 2 { return; } let empty = &"".to_string(); let alias = line.get(0).unwrap_or(empty); let event_key = line.get(1).unwrap_or(empty); if alias.len() == 0 || event_key.len() == 0 { return; } config .key_to_eventkey .insert(alias.to_owned(), event_key.to_owned()); let splits = event_key.split(".").map(|s| s.len()).collect(); config .key_to_split_eventkey .insert(alias.to_owned(), splits); }); config.key_to_eventkey.shrink_to_fit(); return config; } #[derive(Debug, Clone)] pub struct EventInfo { pub evttitle: String, pub detectflg: String, pub comment: String, } impl EventInfo { pub fn new() -> EventInfo { let evttitle = "Unknown".to_string(); let detectflg = "".to_string(); let comment = "".to_string(); return EventInfo { evttitle, detectflg, comment, }; } } #[derive(Debug, Clone)] pub struct EventInfoConfig { eventinfo: HashMap, } impl EventInfoConfig { pub fn new() -> EventInfoConfig { return EventInfoConfig { eventinfo: HashMap::new(), }; } pub fn get_event_id(&self, eventid: &String) -> Option<&EventInfo> { return self.eventinfo.get(eventid); } } fn load_eventcode_info(path: &str) -> EventInfoConfig { let mut infodata = EventInfo::new(); let mut config = EventInfoConfig::new(); let read_result = utils::read_csv(path); if read_result.is_err() { AlertMessage::alert( &mut BufWriter::new(std::io::stderr().lock()), &read_result.as_ref().unwrap_err(), ) .ok(); return config; } // timeline_event_infoが読み込めなかったらエラーで終了とする。 read_result.unwrap().into_iter().for_each(|line| { if line.len() != 4 { return; } let empty = &"".to_string(); let eventcode = line.get(0).unwrap_or(empty); let event_title = line.get(1).unwrap_or(empty); let detect_flg = line.get(2).unwrap_or(empty); let comment = line.get(3).unwrap_or(empty); infodata = EventInfo { evttitle: event_title.to_string(), detectflg: detect_flg.to_string(), comment: comment.to_string(), }; config .eventinfo .insert(eventcode.to_owned(), infodata.to_owned()); }); return config; } #[cfg(test)] mod tests { use crate::detections::configs; use chrono::{DateTime, Utc}; // #[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] 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 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); } }