diff --git a/Cargo.lock b/Cargo.lock index 56a41433..94443ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "parking_lot_core", +] + [[package]] name = "dialoguer" version = "0.9.0" @@ -681,6 +693,7 @@ dependencies = [ "clap 3.2.8", "crossbeam-utils", "csv", + "dashmap", "downcast-rs", "evtx", "flate2", @@ -690,6 +703,7 @@ dependencies = [ "hhmmss", "hyper", "is_elevated", + "itertools", "krapslog", "lazy_static", "linked-hash-map", @@ -863,6 +877,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" diff --git a/Cargo.toml b/Cargo.toml index 05ad4831..7ab7e73c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +itertools = "*" +dashmap = "*" clap = { version = "3.*", features = ["derive", "cargo"]} evtx = { git = "https://github.com/Yamato-Security/hayabusa-evtx.git" , features = ["fast-alloc"]} quick-xml = {version = "0.*", features = ["serialize"] } diff --git a/src/afterfact.rs b/src/afterfact.rs index 152fe67f..425b5137 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1,7 +1,7 @@ use crate::detections::configs; use crate::detections::configs::{CURRENT_EXE_PATH, TERM_SIZE}; -use crate::detections::print; -use crate::detections::print::{AlertMessage, IS_HIDE_RECORD_ID}; +use crate::detections::message::{self}; +use crate::detections::message::{AlertMessage, IS_HIDE_RECORD_ID}; use crate::detections::utils; use crate::detections::utils::{get_writable_color, write_color_buffer}; use bytesize::ByteSize; @@ -9,11 +9,13 @@ use chrono::{DateTime, Local, TimeZone, Utc}; use csv::QuoteStyle; use hashbrown::HashMap; use hashbrown::HashSet; +use itertools::Itertools; use krapslog::{build_sparkline, build_time_markers}; use lazy_static::lazy_static; use serde::Serialize; use std::cmp::min; use std::error::Error; +use std::fmt::Debug; use std::fs; use std::fs::File; use std::io; @@ -205,7 +207,6 @@ fn emit_csv( disp_wtr_buf.set_color(ColorSpec::new().set_fg(None)).ok(); - let messages = print::MESSAGES.lock().unwrap(); // level is devided by "Critical","High","Medium","Low","Informational","Undefined". let mut total_detect_counts_by_level: Vec = vec![0; 6]; let mut unique_detect_counts_by_level: Vec = vec![0; 6]; @@ -242,8 +243,9 @@ fn emit_csv( let mut timestamps: Vec = Vec::new(); let mut plus_header = true; let mut detected_record_idset: HashSet = HashSet::new(); - let detect_union = messages.iter(); - for (time, detect_infos) in detect_union { + for time in message::MESSAGES.clone().into_read_only().keys().sorted() { + let multi = message::MESSAGES.get(time).unwrap(); + let (_, detect_infos) = multi.pair(); timestamps.push(_get_timestamp(time)); for detect_info in detect_infos { detected_record_idset.insert(format!("{}_{}", time, detect_info.eventid)); @@ -703,8 +705,8 @@ mod tests { use crate::afterfact::_get_serialized_disp_output; use crate::afterfact::emit_csv; use crate::afterfact::format_time; - use crate::detections::print; - use crate::detections::print::{DetectInfo, Message}; + use crate::detections::message; + use crate::detections::message::DetectInfo; use chrono::{Local, TimeZone, Utc}; use hashbrown::HashMap; use serde_json::Value; @@ -720,7 +722,7 @@ mod tests { } fn test_emit_csv_output() { - let mock_ch_filter = Message::create_output_filter_config( + let mock_ch_filter = message::create_output_filter_config( "rules/config/channel_abbreviations.txt", true, false, @@ -737,7 +739,7 @@ mod tests { let test_recinfo = "record_infoinfo11"; let test_record_id = "11111"; { - let mut messages = print::MESSAGES.lock().unwrap(); + let messages = &message::MESSAGES; messages.clear(); let val = r##" { @@ -754,7 +756,7 @@ mod tests { } "##; let event: Value = serde_json::from_str(val).unwrap(); - messages.insert( + message::insert( &event, output.to_string(), DetectInfo { diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 31dd83ee..3d22cf75 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -1,6 +1,6 @@ +use crate::detections::message::AlertMessage; use crate::detections::pivot::PivotKeyword; use crate::detections::pivot::PIVOT_KEYWORD; -use crate::detections::print::AlertMessage; use crate::detections::utils; use chrono::{DateTime, Utc}; use clap::{App, CommandFactory, Parser}; diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 5ed131cc..4d0437eb 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -4,15 +4,14 @@ use crate::detections::configs; use crate::detections::utils::write_color_buffer; use termcolor::{BufferWriter, Color, ColorChoice}; -use crate::detections::pivot::insert_pivot_keyword; -use crate::detections::print::AlertMessage; -use crate::detections::print::DetectInfo; -use crate::detections::print::ERROR_LOG_STACK; -use crate::detections::print::MESSAGES; -use crate::detections::print::{CH_CONFIG, DEFAULT_DETAILS, IS_HIDE_RECORD_ID, TAGS_CONFIG}; -use crate::detections::print::{ +use crate::detections::message::AlertMessage; +use crate::detections::message::DetectInfo; +use crate::detections::message::ERROR_LOG_STACK; +use crate::detections::message::{CH_CONFIG, DEFAULT_DETAILS, IS_HIDE_RECORD_ID, TAGS_CONFIG}; +use crate::detections::message::{ LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, STATISTICS_FLAG, }; +use crate::detections::pivot::insert_pivot_keyword; use crate::detections::rule; use crate::detections::rule::AggResult; use crate::detections::rule::RuleNode; @@ -27,6 +26,8 @@ use std::path::Path; use std::sync::Arc; use tokio::{runtime::Runtime, spawn, task::JoinHandle}; +use super::message; + // イベントファイルの1レコード分の情報を保持する構造体 #[derive(Clone, Debug)] pub struct EvtxRecordInfo { @@ -268,7 +269,7 @@ impl Detection { record_information: opt_record_info, record_id: rec_id, }; - MESSAGES.lock().unwrap().insert( + message::insert( &record_info.record, rule.yaml["details"] .as_str() @@ -312,10 +313,7 @@ impl Detection { record_id: rec_id, }; - MESSAGES - .lock() - .unwrap() - .insert_message(detect_info, agg_result.start_timedate) + message::insert_message(detect_info, agg_result.start_timedate) } ///aggregation conditionのcount部分の検知出力文の文字列を返す関数 diff --git a/src/detections/message.rs b/src/detections/message.rs new file mode 100644 index 00000000..3c6fb544 --- /dev/null +++ b/src/detections/message.rs @@ -0,0 +1,601 @@ +extern crate lazy_static; +use crate::detections::configs; +use crate::detections::configs::CURRENT_EXE_PATH; +use crate::detections::utils; +use crate::detections::utils::get_serde_number_to_string; +use crate::detections::utils::write_color_buffer; +use chrono::{DateTime, Local, TimeZone, Utc}; +use dashmap::DashMap; +use hashbrown::HashMap; +use lazy_static::lazy_static; +use regex::Regex; +use serde_json::Value; +use std::env; +use std::fs::create_dir; +use std::fs::File; +use std::io::BufWriter; +use std::io::{self, Write}; +use std::path::Path; +use std::sync::Mutex; +use termcolor::{BufferWriter, ColorChoice}; + +#[derive(Debug, Clone)] +pub struct DetectInfo { + pub filepath: String, + pub rulepath: String, + pub level: String, + pub computername: String, + pub eventid: String, + pub channel: String, + pub alert: String, + pub detail: String, + pub tag_info: String, + pub record_information: Option, + pub record_id: Option, +} + +pub struct AlertMessage {} + +lazy_static! { + #[derive(Debug,PartialEq, Eq, Ord, PartialOrd)] + pub static ref MESSAGES: DashMap, Vec> = DashMap::new(); + pub static ref ALIASREGEX: Regex = Regex::new(r"%[a-zA-Z0-9-_\[\]]+%").unwrap(); + pub static ref SUFFIXREGEX: Regex = Regex::new(r"\[([0-9]+)\]").unwrap(); + pub static ref ERROR_LOG_PATH: String = format!( + "./logs/errorlog-{}.log", + Local::now().format("%Y%m%d_%H%M%S") + ); + pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG.read().unwrap().args.quiet_errors; + pub static ref ERROR_LOG_STACK: Mutex> = Mutex::new(Vec::new()); + pub static ref STATISTICS_FLAG: bool = configs::CONFIG.read().unwrap().args.statistics; + pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().args.logon_summary; + pub static ref TAGS_CONFIG: HashMap = create_output_filter_config( + utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/output_tag.txt") + .to_str() + .unwrap(), + true, + configs::CONFIG.read().unwrap().args.all_tags + ); + pub static ref CH_CONFIG: HashMap = create_output_filter_config( + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/channel_abbreviations.txt" + ) + .to_str() + .unwrap(), + false, + configs::CONFIG.read().unwrap().args.all_tags + ); + pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = + configs::CONFIG.read().unwrap().args.pivot_keywords_list; + pub static ref IS_HIDE_RECORD_ID: bool = configs::CONFIG.read().unwrap().args.hide_record_id; + pub static ref DEFAULT_DETAILS: HashMap = get_default_details(&format!( + "{}/default_details.txt", + configs::CONFIG + .read() + .unwrap() + .args + .config + .as_path() + .display() + )); +} + +/// ファイルパスで記載されたtagでのフル名、表示の際に置き換えられる文字列のHashMapを作成する関数。 +/// ex. attack.impact,Impact +pub fn create_output_filter_config( + path: &str, + read_tags: bool, + pass_flag: bool, +) -> HashMap { + let mut ret: HashMap = HashMap::new(); + if read_tags && pass_flag { + return ret; + } + let read_result = utils::read_csv(path); + if read_result.is_err() { + AlertMessage::alert(read_result.as_ref().unwrap_err()).ok(); + return HashMap::default(); + } + read_result.unwrap().into_iter().for_each(|line| { + if line.len() != 2 { + return; + } + + let empty = &"".to_string(); + let tag_full_str = line.get(0).unwrap_or(empty).trim(); + let tag_replace_str = line.get(1).unwrap_or(empty).trim(); + + ret.insert(tag_full_str.to_owned(), tag_replace_str.to_owned()); + }); + ret +} + +/// メッセージの設定を行う関数。aggcondition対応のためrecordではなく出力をする対象時間がDatetime形式での入力としている +pub fn insert_message(detect_info: DetectInfo, event_time: DateTime) { + if let Some(mut v) = MESSAGES.get_mut(&event_time) { + let (_, info) = v.pair_mut(); + info.push(detect_info); + } else { + let m = vec![detect_info; 1]; + MESSAGES.insert(event_time, m); + } +} + +/// メッセージを設定 +pub fn insert(event_record: &Value, output: String, mut detect_info: DetectInfo) { + detect_info.detail = parse_message(event_record, output); + let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + let time = get_event_time(event_record).unwrap_or(default_time); + insert_message(detect_info, time) +} + +fn parse_message(event_record: &Value, output: String) -> String { + let mut return_message: String = output; + let mut hash_map: HashMap = HashMap::new(); + for caps in ALIASREGEX.captures_iter(&return_message) { + let full_target_str = &caps[0]; + let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent + let target_str = full_target_str + .chars() + .skip(1) + .take(target_length) + .collect::(); + + let array_str = if let Some(_array_str) = configs::EVENTKEY_ALIAS.get_event_key(&target_str) + { + _array_str.to_string() + } else { + "Event.EventData.".to_owned() + &target_str + }; + + let split: Vec<&str> = array_str.split('.').collect(); + let mut tmp_event_record: &Value = event_record; + for s in &split { + if let Some(record) = tmp_event_record.get(s) { + tmp_event_record = record; + } + } + let suffix_match = SUFFIXREGEX.captures(&target_str); + let suffix: i64 = match suffix_match { + Some(cap) => cap.get(1).map_or(-1, |a| a.as_str().parse().unwrap_or(-1)), + None => -1, + }; + if suffix >= 1 { + tmp_event_record = tmp_event_record + .get("Data") + .unwrap() + .get((suffix - 1) as usize) + .unwrap_or(tmp_event_record); + } + let hash_value = get_serde_number_to_string(tmp_event_record); + if hash_value.is_some() { + if let Some(hash_value) = hash_value { + // UnicodeのWhitespace characterをそのままCSVに出力すると見難いので、スペースに変換する。なお、先頭と最後のWhitespace characterは単に削除される。 + let hash_value: Vec<&str> = hash_value.split_whitespace().collect(); + let hash_value = hash_value.join(" "); + hash_map.insert(full_target_str.to_string(), hash_value); + } + } else { + hash_map.insert(full_target_str.to_string(), "n/a".to_string()); + } + } + + for (k, v) in &hash_map { + return_message = return_message.replace(k, v); + } + + return_message +} + +/// メッセージを返す +pub fn get(time: DateTime) -> Vec { + match MESSAGES.get(&time) { + Some(v) => v.to_vec(), + None => Vec::new(), + } +} + +/// 最後に表示を行う +pub fn print() { + let mut detect_count = 0; + for multi in MESSAGES.iter() { + let (key, detect_infos) = multi.pair(); + for detect_info in detect_infos.iter() { + println!("{} <{}> {}", key, detect_info.alert, detect_info.detail); + } + detect_count += detect_infos.len(); + } + println!(); + println!("Total events:{:?}", detect_count); +} + +pub fn get_event_time(event_record: &Value) -> Option> { + let system_time = &event_record["Event"]["System"]["TimeCreated_attributes"]["SystemTime"]; + return utils::str_time_to_datetime(system_time.as_str().unwrap_or("")); +} + +/// detailsのdefault値をファイルから読み取る関数 +pub fn get_default_details(filepath: &str) -> HashMap { + let read_result = utils::read_csv(filepath); + match read_result { + Err(_e) => { + AlertMessage::alert(&_e).ok(); + HashMap::new() + } + Ok(lines) => { + let mut ret: HashMap = HashMap::new(); + lines + .into_iter() + .try_for_each(|line| -> Result<(), String> { + let provider = match line.get(0) { + Some(_provider) => _provider.trim(), + _ => { + return Result::Err( + "Failed to read provider in default_details.txt.".to_string(), + ) + } + }; + let eid = match line.get(1) { + Some(eid_str) => match eid_str.trim().parse::() { + Ok(_eid) => _eid, + _ => { + return Result::Err( + "Parse Error EventID in default_details.txt.".to_string(), + ) + } + }, + _ => { + return Result::Err( + "Failed to read EventID in default_details.txt.".to_string(), + ) + } + }; + let details = match line.get(2) { + Some(detail) => detail.trim(), + _ => { + return Result::Err( + "Failed to read details in default_details.txt.".to_string(), + ) + } + }; + ret.insert(format!("{}_{}", provider, eid), details.to_string()); + Ok(()) + }) + .ok(); + ret + } + } +} + +impl AlertMessage { + ///対象のディレクトリが存在することを確認後、最初の定型文を追加して、ファイルのbufwriterを返す関数 + pub fn create_error_log(path_str: String) { + if *QUIET_ERRORS_FLAG { + return; + } + let path = Path::new(&path_str); + if !path.parent().unwrap().exists() { + create_dir(path.parent().unwrap()).ok(); + } + let mut error_log_writer = BufWriter::new(File::create(path).unwrap()); + error_log_writer + .write_all( + format!( + "user input: {:?}\n", + format_args!("{}", env::args().collect::>().join(" ")) + ) + .as_bytes(), + ) + .ok(); + let error_logs = ERROR_LOG_STACK.lock().unwrap(); + error_logs.iter().for_each(|error_log| { + writeln!(error_log_writer, "{}", error_log).ok(); + }); + println!( + "Errors were generated. Please check {} for details.", + *ERROR_LOG_PATH + ); + println!(); + } + + /// ERRORメッセージを表示する関数 + pub fn alert(contents: &str) -> io::Result<()> { + write_color_buffer( + &BufferWriter::stderr(ColorChoice::Always), + None, + &format!("[ERROR] {}", contents), + true, + ) + } + + /// WARNメッセージを表示する関数 + pub fn warn(contents: &str) -> io::Result<()> { + write_color_buffer( + &BufferWriter::stderr(ColorChoice::Always), + None, + &format!("[WARN] {}", contents), + true, + ) + } +} + +#[cfg(test)] +mod tests { + use crate::detections::message::AlertMessage; + use crate::detections::message::{parse_message, MESSAGES}; + use hashbrown::HashMap; + use serde_json::Value; + + use super::{create_output_filter_config, get_default_details}; + + #[test] + fn test_error_message() { + let input = "TEST!"; + AlertMessage::alert(input).expect("[ERROR] TEST!"); + } + + #[test] + fn test_warn_message() { + let input = "TESTWarn!"; + AlertMessage::warn(input).expect("[WARN] TESTWarn!"); + } + + #[test] + /// outputで指定されているキー(eventkey_alias.txt内で設定済み)から対象のレコード内の情報でメッセージをパースしているか確認する関数 + fn test_parse_message() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest1" + }, + "System": { + "Computer": "testcomputer1", + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest1 computername:testcomputer1"; + assert_eq!( + parse_message( + &event_record, + "commandline:%CommandLine% computername:%ComputerName%".to_owned() + ), + expected, + ); + } + + #[test] + fn test_parse_message_auto_search() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "NoAlias": "no_alias" + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "alias:no_alias"; + assert_eq!( + parse_message(&event_record, "alias:%NoAlias%".to_owned()), + expected, + ); + } + + #[test] + /// outputで指定されているキーが、eventkey_alias.txt内で設定されていない場合の出力テスト + fn test_parse_message_not_exist_key_in_output() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest2" + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "NoExistAlias:n/a"; + assert_eq!( + parse_message(&event_record, "NoExistAlias:%NoAliasNoHit%".to_owned()), + expected, + ); + } + #[test] + /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt + fn test_parse_message_not_exist_value_in_record() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest3" + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest3 computername:n/a"; + assert_eq!( + parse_message( + &event_record, + "commandline:%CommandLine% computername:%ComputerName%".to_owned() + ), + expected, + ); + } + #[test] + /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt + fn test_parse_message_multiple_no_suffix_in_record() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest3", + "Data": [ + "data1", + "data2", + "data3" + ] + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest3 data:[\"data1\",\"data2\",\"data3\"]"; + assert_eq!( + parse_message( + &event_record, + "commandline:%CommandLine% data:%Data%".to_owned() + ), + expected, + ); + } + #[test] + /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt + fn test_parse_message_multiple_with_suffix_in_record() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest3", + "Data": [ + "data1", + "data2", + "data3" + ] + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest3 data:data2"; + assert_eq!( + parse_message( + &event_record, + "commandline:%CommandLine% data:%Data[2]%".to_owned() + ), + expected, + ); + } + #[test] + /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt + fn test_parse_message_multiple_no_exist_in_record() { + MESSAGES.clear(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest3", + "Data": [ + "data1", + "data2", + "data3" + ] + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest3 data:n/a"; + assert_eq!( + parse_message( + &event_record, + "commandline:%CommandLine% data:%Data[0]%".to_owned() + ), + expected, + ); + } + #[test] + /// test of loading output filter config by output_tag.txt + fn test_load_output_tag() { + let actual = create_output_filter_config("test_files/config/output_tag.txt", true, false); + let expected: HashMap = HashMap::from([ + ("attack.impact".to_string(), "Impact".to_string()), + ("xxx".to_string(), "yyy".to_string()), + ]); + _check_hashmap_element(&expected, actual); + } + + #[test] + /// test of loading pass by output_tag.txt + fn test_no_load_output_tag() { + let actual = create_output_filter_config("test_files/config/output_tag.txt", true, true); + let expected: HashMap = HashMap::new(); + _check_hashmap_element(&expected, actual); + } + + #[test] + /// loading test to channel_abbrevations.txt + fn test_load_abbrevations() { + let actual = + create_output_filter_config("test_files/config/channel_abbreviations.txt", false, true); + let actual2 = create_output_filter_config( + "test_files/config/channel_abbreviations.txt", + false, + false, + ); + let expected: HashMap = HashMap::from([ + ("Security".to_string(), "Sec".to_string()), + ("xxx".to_string(), "yyy".to_string()), + ]); + _check_hashmap_element(&expected, actual); + _check_hashmap_element(&expected, actual2); + } + + #[test] + fn _get_default_defails() { + let expected: HashMap = HashMap::from([ + ("Microsoft-Windows-PowerShell_4104".to_string(),"%ScriptBlockText%".to_string()),("Microsoft-Windows-Security-Auditing_4624".to_string(), "User: %TargetUserName% | Comp: %WorkstationName% | IP Addr: %IpAddress% | LID: %TargetLogonId% | Process: %ProcessName%".to_string()), + ("Microsoft-Windows-Sysmon_1".to_string(), "Cmd: %CommandLine% | Process: %Image% | User: %User% | Parent Cmd: %ParentCommandLine% | LID: %LogonId% | PID: %ProcessId% | PGUID: %ProcessGuid%".to_string()), + ("Service Control Manager_7031".to_string(), "Svc: %param1% | Crash Count: %param2% | Action: %param5%".to_string()), + ]); + let actual = get_default_details("test_files/config/default_details.txt"); + _check_hashmap_element(&expected, actual); + } + + /// check two HashMap element length and value + fn _check_hashmap_element(expected: &HashMap, actual: HashMap) { + assert_eq!(expected.len(), actual.len()); + for (k, v) in expected.iter() { + assert!(actual.get(k).unwrap_or(&String::default()) == v); + } + } +} diff --git a/src/detections/mod.rs b/src/detections/mod.rs index 5a081dff..58bbafeb 100644 --- a/src/detections/mod.rs +++ b/src/detections/mod.rs @@ -1,6 +1,6 @@ pub mod configs; pub mod detection; +pub mod message; pub mod pivot; -pub mod print; pub mod rule; pub mod utils; diff --git a/src/detections/print.rs b/src/detections/print.rs deleted file mode 100644 index 96014349..00000000 --- a/src/detections/print.rs +++ /dev/null @@ -1,770 +0,0 @@ -extern crate lazy_static; -use crate::detections::configs; -use crate::detections::configs::CURRENT_EXE_PATH; -use crate::detections::utils; -use crate::detections::utils::get_serde_number_to_string; -use crate::detections::utils::write_color_buffer; -use chrono::{DateTime, Local, TimeZone, Utc}; -use hashbrown::HashMap; -use lazy_static::lazy_static; -use regex::Regex; -use serde_json::Value; -use std::collections::BTreeMap; -use std::env; -use std::fs::create_dir; -use std::fs::File; -use std::io::BufWriter; -use std::io::{self, Write}; -use std::path::Path; -use std::sync::Mutex; -use termcolor::{BufferWriter, ColorChoice}; - -#[derive(Debug)] -pub struct Message { - map: BTreeMap, Vec>, -} - -#[derive(Debug, Clone)] -pub struct DetectInfo { - pub filepath: String, - pub rulepath: String, - pub level: String, - pub computername: String, - pub eventid: String, - pub channel: String, - pub alert: String, - pub detail: String, - pub tag_info: String, - pub record_information: Option, - pub record_id: Option, -} - -pub struct AlertMessage {} - -lazy_static! { - pub static ref MESSAGES: Mutex = Mutex::new(Message::new()); - pub static ref ALIASREGEX: Regex = Regex::new(r"%[a-zA-Z0-9-_\[\]]+%").unwrap(); - pub static ref SUFFIXREGEX: Regex = Regex::new(r"\[([0-9]+)\]").unwrap(); - pub static ref ERROR_LOG_PATH: String = format!( - "./logs/errorlog-{}.log", - Local::now().format("%Y%m%d_%H%M%S") - ); - pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG.read().unwrap().args.quiet_errors; - pub static ref ERROR_LOG_STACK: Mutex> = Mutex::new(Vec::new()); - pub static ref STATISTICS_FLAG: bool = configs::CONFIG.read().unwrap().args.statistics; - pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().args.logon_summary; - pub static ref TAGS_CONFIG: HashMap = Message::create_output_filter_config( - utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/output_tag.txt") - .to_str() - .unwrap(), - true, - configs::CONFIG.read().unwrap().args.all_tags - ); - pub static ref CH_CONFIG: HashMap = Message::create_output_filter_config( - utils::check_setting_path( - &CURRENT_EXE_PATH.to_path_buf(), - "rules/config/channel_abbreviations.txt" - ) - .to_str() - .unwrap(), - false, - configs::CONFIG.read().unwrap().args.all_tags - ); - pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = - configs::CONFIG.read().unwrap().args.pivot_keywords_list; - pub static ref IS_HIDE_RECORD_ID: bool = configs::CONFIG.read().unwrap().args.hide_record_id; - pub static ref DEFAULT_DETAILS: HashMap = - Message::get_default_details(&format!( - "{}/default_details.txt", - configs::CONFIG - .read() - .unwrap() - .args - .config - .as_path() - .display() - )); -} - -impl Default for Message { - fn default() -> Self { - Self::new() - } -} - -impl Message { - pub fn new() -> Self { - let messages: BTreeMap, Vec> = BTreeMap::new(); - Message { map: messages } - } - - /// ファイルパスで記載されたtagでのフル名、表示の際に置き換えられる文字列のHashMapを作成する関数。 - /// ex. attack.impact,Impact - pub fn create_output_filter_config( - path: &str, - read_tags: bool, - pass_flag: bool, - ) -> HashMap { - let mut ret: HashMap = HashMap::new(); - if read_tags && pass_flag { - return ret; - } - let read_result = utils::read_csv(path); - if read_result.is_err() { - AlertMessage::alert(read_result.as_ref().unwrap_err()).ok(); - return HashMap::default(); - } - read_result.unwrap().into_iter().for_each(|line| { - if line.len() != 2 { - return; - } - - let empty = &"".to_string(); - let tag_full_str = line.get(0).unwrap_or(empty).trim(); - let tag_replace_str = line.get(1).unwrap_or(empty).trim(); - - ret.insert(tag_full_str.to_owned(), tag_replace_str.to_owned()); - }); - ret - } - - /// メッセージの設定を行う関数。aggcondition対応のためrecordではなく出力をする対象時間がDatetime形式での入力としている - pub fn insert_message(&mut self, detect_info: DetectInfo, event_time: DateTime) { - if let Some(v) = self.map.get_mut(&event_time) { - v.push(detect_info); - } else { - let m = vec![detect_info; 1]; - self.map.insert(event_time, m); - } - } - - /// メッセージを設定 - pub fn insert(&mut self, event_record: &Value, output: String, mut detect_info: DetectInfo) { - detect_info.detail = self.parse_message(event_record, output); - let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - let time = Message::get_event_time(event_record).unwrap_or(default_time); - self.insert_message(detect_info, time) - } - - fn parse_message(&mut self, event_record: &Value, output: String) -> String { - let mut return_message: String = output; - let mut hash_map: HashMap = HashMap::new(); - for caps in ALIASREGEX.captures_iter(&return_message) { - let full_target_str = &caps[0]; - let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent - let target_str = full_target_str - .chars() - .skip(1) - .take(target_length) - .collect::(); - - let array_str = - if let Some(_array_str) = configs::EVENTKEY_ALIAS.get_event_key(&target_str) { - _array_str.to_string() - } else { - "Event.EventData.".to_owned() + &target_str - }; - - let split: Vec<&str> = array_str.split('.').collect(); - let mut tmp_event_record: &Value = event_record; - for s in &split { - if let Some(record) = tmp_event_record.get(s) { - tmp_event_record = record; - } - } - let suffix_match = SUFFIXREGEX.captures(&target_str); - let suffix: i64 = match suffix_match { - Some(cap) => cap.get(1).map_or(-1, |a| a.as_str().parse().unwrap_or(-1)), - None => -1, - }; - if suffix >= 1 { - tmp_event_record = tmp_event_record - .get("Data") - .unwrap() - .get((suffix - 1) as usize) - .unwrap_or(tmp_event_record); - } - let hash_value = get_serde_number_to_string(tmp_event_record); - if hash_value.is_some() { - if let Some(hash_value) = hash_value { - // UnicodeのWhitespace characterをそのままCSVに出力すると見難いので、スペースに変換する。なお、先頭と最後のWhitespace characterは単に削除される。 - let hash_value: Vec<&str> = hash_value.split_whitespace().collect(); - let hash_value = hash_value.join(" "); - hash_map.insert(full_target_str.to_string(), hash_value); - } - } else { - hash_map.insert(full_target_str.to_string(), "n/a".to_string()); - } - } - - for (k, v) in &hash_map { - return_message = return_message.replace(k, v); - } - - return_message - } - - /// メッセージを返す - pub fn get(&self, time: DateTime) -> Vec { - match self.map.get(&time) { - Some(v) => v.to_vec(), - None => Vec::new(), - } - } - - /// Messageのなかに入っているメッセージすべてを表示する - pub fn debug(&self) { - println!("{:?}", self.map); - } - - /// 最後に表示を行う - pub fn print(&self) { - let mut detect_count = 0; - for (key, detect_infos) in self.map.iter() { - for detect_info in detect_infos.iter() { - println!("{} <{}> {}", key, detect_info.alert, detect_info.detail); - } - detect_count += detect_infos.len(); - } - println!(); - println!("Total events:{:?}", detect_count); - } - - pub fn iter(&self) -> &BTreeMap, Vec> { - &self.map - } - - pub fn get_event_time(event_record: &Value) -> Option> { - let system_time = &event_record["Event"]["System"]["TimeCreated_attributes"]["SystemTime"]; - return utils::str_time_to_datetime(system_time.as_str().unwrap_or("")); - } - - /// message内のマップをクリアする。テストする際の冪等性の担保のため作成。 - pub fn clear(&mut self) { - self.map.clear(); - } - - /// detailsのdefault値をファイルから読み取る関数 - pub fn get_default_details(filepath: &str) -> HashMap { - let read_result = utils::read_csv(filepath); - match read_result { - Err(_e) => { - AlertMessage::alert(&_e).ok(); - HashMap::new() - } - Ok(lines) => { - let mut ret: HashMap = HashMap::new(); - lines - .into_iter() - .try_for_each(|line| -> Result<(), String> { - let provider = match line.get(0) { - Some(_provider) => _provider.trim(), - _ => { - return Result::Err( - "Failed to read provider in default_details.txt.".to_string(), - ) - } - }; - let eid = match line.get(1) { - Some(eid_str) => match eid_str.trim().parse::() { - Ok(_eid) => _eid, - _ => { - return Result::Err( - "Parse Error EventID in default_details.txt.".to_string(), - ) - } - }, - _ => { - return Result::Err( - "Failed to read EventID in default_details.txt.".to_string(), - ) - } - }; - let details = match line.get(2) { - Some(detail) => detail.trim(), - _ => { - return Result::Err( - "Failed to read details in default_details.txt.".to_string(), - ) - } - }; - ret.insert(format!("{}_{}", provider, eid), details.to_string()); - Ok(()) - }) - .ok(); - ret - } - } - } -} - -impl AlertMessage { - ///対象のディレクトリが存在することを確認後、最初の定型文を追加して、ファイルのbufwriterを返す関数 - pub fn create_error_log(path_str: String) { - if *QUIET_ERRORS_FLAG { - return; - } - let path = Path::new(&path_str); - if !path.parent().unwrap().exists() { - create_dir(path.parent().unwrap()).ok(); - } - let mut error_log_writer = BufWriter::new(File::create(path).unwrap()); - error_log_writer - .write_all( - format!( - "user input: {:?}\n", - format_args!("{}", env::args().collect::>().join(" ")) - ) - .as_bytes(), - ) - .ok(); - let error_logs = ERROR_LOG_STACK.lock().unwrap(); - error_logs.iter().for_each(|error_log| { - writeln!(error_log_writer, "{}", error_log).ok(); - }); - println!( - "Errors were generated. Please check {} for details.", - *ERROR_LOG_PATH - ); - println!(); - } - - /// ERRORメッセージを表示する関数 - pub fn alert(contents: &str) -> io::Result<()> { - write_color_buffer( - &BufferWriter::stderr(ColorChoice::Always), - None, - &format!("[ERROR] {}", contents), - true, - ) - } - - /// WARNメッセージを表示する関数 - pub fn warn(contents: &str) -> io::Result<()> { - write_color_buffer( - &BufferWriter::stderr(ColorChoice::Always), - None, - &format!("[WARN] {}", contents), - true, - ) - } -} - -#[cfg(test)] -mod tests { - use crate::detections::print::DetectInfo; - use crate::detections::print::{AlertMessage, Message}; - use hashbrown::HashMap; - use serde_json::Value; - - #[test] - fn test_create_and_append_message() { - let mut message = Message::new(); - let json_str_1 = r##" - { - "Event": { - "EventData": { - "CommandLine": "hoge" - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record_1: Value = serde_json::from_str(json_str_1).unwrap(); - message.insert( - &event_record_1, - "CommandLine1: %CommandLine%".to_string(), - DetectInfo { - filepath: "a".to_string(), - rulepath: "test_rule".to_string(), - level: "high".to_string(), - computername: "testcomputer1".to_string(), - eventid: "1".to_string(), - channel: String::default(), - alert: "test1".to_string(), - detail: String::default(), - tag_info: "txxx.001".to_string(), - record_information: Option::Some("record_information1".to_string()), - record_id: Option::Some("11111".to_string()), - }, - ); - - let json_str_2 = r##" - { - "Event": { - "EventData": { - "CommandLine": "hoge" - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record_2: Value = serde_json::from_str(json_str_2).unwrap(); - message.insert( - &event_record_2, - "CommandLine2: %CommandLine%".to_string(), - DetectInfo { - filepath: "a".to_string(), - rulepath: "test_rule2".to_string(), - level: "high".to_string(), - computername: "testcomputer2".to_string(), - eventid: "2".to_string(), - channel: String::default(), - alert: "test2".to_string(), - detail: String::default(), - tag_info: "txxx.002".to_string(), - record_information: Option::Some("record_information2".to_string()), - record_id: Option::Some("22222".to_string()), - }, - ); - - let json_str_3 = r##" - { - "Event": { - "EventData": { - "CommandLine": "hoge" - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "2000-01-21T09:06:01Z" - } - } - } - } - "##; - let event_record_3: Value = serde_json::from_str(json_str_3).unwrap(); - message.insert( - &event_record_3, - "CommandLine3: %CommandLine%".to_string(), - DetectInfo { - filepath: "a".to_string(), - rulepath: "test_rule3".to_string(), - level: "high".to_string(), - computername: "testcomputer3".to_string(), - eventid: "3".to_string(), - channel: String::default(), - alert: "test3".to_string(), - detail: String::default(), - tag_info: "txxx.003".to_string(), - record_information: Option::Some("record_information3".to_string()), - record_id: Option::Some("33333".to_string()), - }, - ); - - let json_str_4 = r##" - { - "Event": { - "EventData": { - "CommandLine": "hoge" - } - } - } - "##; - let event_record_4: Value = serde_json::from_str(json_str_4).unwrap(); - message.insert( - &event_record_4, - "CommandLine4: %CommandLine%".to_string(), - DetectInfo { - filepath: "a".to_string(), - rulepath: "test_rule4".to_string(), - level: "medium".to_string(), - computername: "testcomputer4".to_string(), - eventid: "4".to_string(), - channel: String::default(), - alert: "test4".to_string(), - detail: String::default(), - tag_info: "txxx.004".to_string(), - record_information: Option::Some("record_information4".to_string()), - record_id: Option::None, - }, - ); - - let display = format!("{}", format_args!("{:?}", message)); - println!("display::::{}", display); - let expect = "Message { map: {1970-01-01T00:00:00Z: [DetectInfo { filepath: \"a\", rulepath: \"test_rule4\", level: \"medium\", computername: \"testcomputer4\", eventid: \"4\", channel: \"\", alert: \"test4\", detail: \"CommandLine4: hoge\", tag_info: \"txxx.004\", record_information: Some(\"record_information4\"), record_id: None }], 1996-02-27T01:05:01Z: [DetectInfo { filepath: \"a\", rulepath: \"test_rule\", level: \"high\", computername: \"testcomputer1\", eventid: \"1\", channel: \"\", alert: \"test1\", detail: \"CommandLine1: hoge\", tag_info: \"txxx.001\", record_information: Some(\"record_information1\"), record_id: Some(\"11111\") }, DetectInfo { filepath: \"a\", rulepath: \"test_rule2\", level: \"high\", computername: \"testcomputer2\", eventid: \"2\", channel: \"\", alert: \"test2\", detail: \"CommandLine2: hoge\", tag_info: \"txxx.002\", record_information: Some(\"record_information2\"), record_id: Some(\"22222\") }], 2000-01-21T09:06:01Z: [DetectInfo { filepath: \"a\", rulepath: \"test_rule3\", level: \"high\", computername: \"testcomputer3\", eventid: \"3\", channel: \"\", alert: \"test3\", detail: \"CommandLine3: hoge\", tag_info: \"txxx.003\", record_information: Some(\"record_information3\"), record_id: Some(\"33333\") }]} }"; - assert_eq!(display, expect); - } - - #[test] - fn test_error_message() { - let input = "TEST!"; - AlertMessage::alert(input).expect("[ERROR] TEST!"); - } - - #[test] - fn test_warn_message() { - let input = "TESTWarn!"; - AlertMessage::warn(input).expect("[WARN] TESTWarn!"); - } - - #[test] - /// outputで指定されているキー(eventkey_alias.txt内で設定済み)から対象のレコード内の情報でメッセージをパースしているか確認する関数 - fn test_parse_message() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "CommandLine": "parsetest1" - }, - "System": { - "Computer": "testcomputer1", - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "commandline:parsetest1 computername:testcomputer1"; - assert_eq!( - message.parse_message( - &event_record, - "commandline:%CommandLine% computername:%ComputerName%".to_owned() - ), - expected, - ); - } - - #[test] - fn test_parse_message_auto_search() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "NoAlias": "no_alias" - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "alias:no_alias"; - assert_eq!( - message.parse_message(&event_record, "alias:%NoAlias%".to_owned()), - expected, - ); - } - - #[test] - /// outputで指定されているキーが、eventkey_alias.txt内で設定されていない場合の出力テスト - fn test_parse_message_not_exist_key_in_output() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "CommandLine": "parsetest2" - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "NoExistAlias:n/a"; - assert_eq!( - message.parse_message(&event_record, "NoExistAlias:%NoAliasNoHit%".to_owned()), - expected, - ); - } - #[test] - /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt - fn test_parse_message_not_exist_value_in_record() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "CommandLine": "parsetest3" - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "commandline:parsetest3 computername:n/a"; - assert_eq!( - message.parse_message( - &event_record, - "commandline:%CommandLine% computername:%ComputerName%".to_owned() - ), - expected, - ); - } - #[test] - /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt - fn test_parse_message_multiple_no_suffix_in_record() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "CommandLine": "parsetest3", - "Data": [ - "data1", - "data2", - "data3" - ] - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "commandline:parsetest3 data:[\"data1\",\"data2\",\"data3\"]"; - assert_eq!( - message.parse_message( - &event_record, - "commandline:%CommandLine% data:%Data%".to_owned() - ), - expected, - ); - } - #[test] - /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt - fn test_parse_message_multiple_with_suffix_in_record() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "CommandLine": "parsetest3", - "Data": [ - "data1", - "data2", - "data3" - ] - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "commandline:parsetest3 data:data2"; - assert_eq!( - message.parse_message( - &event_record, - "commandline:%CommandLine% data:%Data[2]%".to_owned() - ), - expected, - ); - } - #[test] - /// output test when no exist info in target record output and described key-value data in eventkey_alias.txt - fn test_parse_message_multiple_no_exist_in_record() { - let mut message = Message::new(); - let json_str = r##" - { - "Event": { - "EventData": { - "CommandLine": "parsetest3", - "Data": [ - "data1", - "data2", - "data3" - ] - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event_record: Value = serde_json::from_str(json_str).unwrap(); - let expected = "commandline:parsetest3 data:n/a"; - assert_eq!( - message.parse_message( - &event_record, - "commandline:%CommandLine% data:%Data[0]%".to_owned() - ), - expected, - ); - } - #[test] - /// test of loading output filter config by output_tag.txt - fn test_load_output_tag() { - let actual = - Message::create_output_filter_config("test_files/config/output_tag.txt", true, false); - let expected: HashMap = HashMap::from([ - ("attack.impact".to_string(), "Impact".to_string()), - ("xxx".to_string(), "yyy".to_string()), - ]); - _check_hashmap_element(&expected, actual); - } - - #[test] - /// test of loading pass by output_tag.txt - fn test_no_load_output_tag() { - let actual = - Message::create_output_filter_config("test_files/config/output_tag.txt", true, true); - let expected: HashMap = HashMap::new(); - _check_hashmap_element(&expected, actual); - } - - #[test] - /// loading test to channel_abbrevations.txt - fn test_load_abbrevations() { - let actual = Message::create_output_filter_config( - "test_files/config/channel_abbreviations.txt", - false, - true, - ); - let actual2 = Message::create_output_filter_config( - "test_files/config/channel_abbreviations.txt", - false, - false, - ); - let expected: HashMap = HashMap::from([ - ("Security".to_string(), "Sec".to_string()), - ("xxx".to_string(), "yyy".to_string()), - ]); - _check_hashmap_element(&expected, actual); - _check_hashmap_element(&expected, actual2); - } - - #[test] - fn _get_default_defails() { - let expected: HashMap = HashMap::from([ - ("Microsoft-Windows-PowerShell_4104".to_string(),"%ScriptBlockText%".to_string()),("Microsoft-Windows-Security-Auditing_4624".to_string(), "User: %TargetUserName% | Comp: %WorkstationName% | IP Addr: %IpAddress% | LID: %TargetLogonId% | Process: %ProcessName%".to_string()), - ("Microsoft-Windows-Sysmon_1".to_string(), "Cmd: %CommandLine% | Process: %Image% | User: %User% | Parent Cmd: %ParentCommandLine% | LID: %LogonId% | PID: %ProcessId% | PGUID: %ProcessGuid%".to_string()), - ("Service Control Manager_7031".to_string(), "Svc: %param1% | Crash Count: %param2% | Action: %param5%".to_string()), - ]); - let actual = Message::get_default_details("test_files/config/default_details.txt"); - _check_hashmap_element(&expected, actual); - } - - /// check two HashMap element length and value - fn _check_hashmap_element(expected: &HashMap, actual: HashMap) { - assert_eq!(expected.len(), actual.len()); - for (k, v) in expected.iter() { - assert!(actual.get(k).unwrap_or(&String::default()) == v); - } - } -} diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs index 3f32f028..c6778934 100644 --- a/src/detections/rule/count.rs +++ b/src/detections/rule/count.rs @@ -1,9 +1,9 @@ use crate::detections::configs; -use crate::detections::print::AlertMessage; -use crate::detections::print::ERROR_LOG_STACK; -use crate::detections::print::QUIET_ERRORS_FLAG; +use crate::detections::message; +use crate::detections::message::AlertMessage; +use crate::detections::message::ERROR_LOG_STACK; +use crate::detections::message::QUIET_ERRORS_FLAG; use crate::detections::rule::AggResult; -use crate::detections::rule::Message; use crate::detections::rule::RuleNode; use chrono::{DateTime, TimeZone, Utc}; use hashbrown::HashMap; @@ -33,7 +33,7 @@ pub fn count(rule: &mut RuleNode, record: &Value) { rule, key, field_value, - Message::get_event_time(record).unwrap_or(default_time), + message::get_event_time(record).unwrap_or(default_time), ); } diff --git a/src/detections/rule/mod.rs b/src/detections/rule/mod.rs index cfa1173b..60f55011 100644 --- a/src/detections/rule/mod.rs +++ b/src/detections/rule/mod.rs @@ -1,5 +1,4 @@ extern crate regex; -use crate::detections::print::Message; use chrono::{DateTime, Utc}; diff --git a/src/filter.rs b/src/filter.rs index 41fa26ec..c78b7880 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,7 +1,7 @@ use crate::detections::configs; -use crate::detections::print::AlertMessage; -use crate::detections::print::ERROR_LOG_STACK; -use crate::detections::print::QUIET_ERRORS_FLAG; +use crate::detections::message::AlertMessage; +use crate::detections::message::ERROR_LOG_STACK; +use crate::detections::message::QUIET_ERRORS_FLAG; use hashbrown::HashMap; use regex::Regex; use std::fs::File; diff --git a/src/main.rs b/src/main.rs index ad182426..e4199355 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,12 +13,12 @@ use hashbrown::{HashMap, HashSet}; use hayabusa::detections::configs::CURRENT_EXE_PATH; use hayabusa::detections::configs::{load_pivot_keywords, TargetEventTime, TARGET_EXTENSIONS}; use hayabusa::detections::detection::{self, EvtxRecordInfo}; -use hayabusa::detections::pivot::PivotKeyword; -use hayabusa::detections::pivot::PIVOT_KEYWORD; -use hayabusa::detections::print::{ +use hayabusa::detections::message::{ AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, STATISTICS_FLAG, }; +use hayabusa::detections::pivot::PivotKeyword; +use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::omikuji::Omikuji; use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules}; diff --git a/src/options/update_rules.rs b/src/options/update_rules.rs index 406b2986..be3ca5db 100644 --- a/src/options/update_rules.rs +++ b/src/options/update_rules.rs @@ -1,4 +1,4 @@ -use crate::detections::print::AlertMessage; +use crate::detections::message::AlertMessage; use crate::detections::utils::write_color_buffer; use crate::filter; use crate::yaml::ParseYaml; diff --git a/src/timeline/statistics.rs b/src/timeline/statistics.rs index 3ae81b9a..335f080a 100644 --- a/src/timeline/statistics.rs +++ b/src/timeline/statistics.rs @@ -1,4 +1,4 @@ -use crate::detections::print::{LOGONSUMMARY_FLAG, STATISTICS_FLAG}; +use crate::detections::message::{LOGONSUMMARY_FLAG, STATISTICS_FLAG}; use crate::detections::{detection::EvtxRecordInfo, utils}; use hashbrown::HashMap; diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 34f8bc8f..a0cad83a 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,4 +1,4 @@ -use crate::detections::print::{LOGONSUMMARY_FLAG, STATISTICS_FLAG}; +use crate::detections::message::{LOGONSUMMARY_FLAG, STATISTICS_FLAG}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; use prettytable::{Cell, Row, Table}; diff --git a/src/yaml.rs b/src/yaml.rs index 51d2a162..19ce2eef 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -3,8 +3,8 @@ extern crate yaml_rust; use crate::detections::configs; use crate::detections::configs::EXCLUDE_STATUS; -use crate::detections::print::AlertMessage; -use crate::detections::print::{ERROR_LOG_STACK, QUIET_ERRORS_FLAG}; +use crate::detections::message::AlertMessage; +use crate::detections::message::{ERROR_LOG_STACK, QUIET_ERRORS_FLAG}; use crate::filter::RuleExclude; use hashbrown::HashMap; use std::ffi::OsStr; @@ -316,8 +316,8 @@ impl ParseYaml { #[cfg(test)] mod tests { - use crate::detections::print::AlertMessage; - use crate::detections::print::ERROR_LOG_PATH; + use crate::detections::message::AlertMessage; + use crate::detections::message::ERROR_LOG_PATH; use crate::filter; use crate::yaml; use crate::yaml::RuleExclude;