diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 38964f70..87180caa 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -15,6 +15,7 @@ **バグ修正:** - ログオン情報の要約オプションを追加した場合に、Hayabusaがクラッシュしていたのを修正した。 (#674) (@hitenkoku) +- configオプションで指定したルールコンフィグの読み込みができていない問題を修正した。 (#681) (@hitenkoku) - 結果概要のtotal eventsで読み込んだレコード数が出力されていたのを、検査対象にしているevtxファイルの実際のレコード数に修正した。 (#683) (@hitenkoku) ## v1.5.1 [2022/08/20] diff --git a/CHANGELOG.md b/CHANGELOG.md index f09a9a4c..4b02f742 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ **Bug Fixes:** - Hayabusa would crash with `-L` option (logon summary option). (#674) (@hitenkoku) +- Hayabusa would continue to scan without the correct config files but now will print and error and gracefully terminate. (#681) (@hitenkoku) - Fixed total events from the number of scanned events to actual events in evtx. (#683) (@hitenkoku) ## v1.5.1 [2022/08/20] diff --git a/src/afterfact.rs b/src/afterfact.rs index 9a84b2ff..076e5578 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -43,9 +43,14 @@ pub struct Colors { /// level_color.txtファイルを読み込み対応する文字色のマッピングを返却する関数 pub fn set_output_color() -> HashMap { let read_result = utils::read_csv( - utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/level_color.txt") - .to_str() - .unwrap(), + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "config/level_color.txt", + true, + ) + .unwrap() + .to_str() + .unwrap(), ); let mut color_map: HashMap = HashMap::new(); if configs::CONFIG.read().unwrap().args.no_color { diff --git a/src/detections/configs.rs b/src/detections/configs.rs index feda62a4..a0c50461 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -23,10 +23,23 @@ lazy_static! { levelmap.insert("CRITICAL".to_owned(), 5); levelmap }; - pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias(&format!( - "{}/eventkey_alias.txt", - CONFIG.read().unwrap().args.config.as_path().display() - )); + pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias( + utils::check_setting_path( + &CONFIG.read().unwrap().args.config, + "eventkey_alias.txt", + false + ) + .unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/eventkey_alias.txt", + true, + ) + .unwrap() + }) + .to_str() + .unwrap() + ); pub static ref IDS_REGEX: Regex = Regex::new(r"^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$").unwrap(); pub static ref TERM_SIZE: Option<(Width, Height)> = terminal_size(); @@ -52,7 +65,7 @@ impl Default for ConfigReader<'_> { } } -#[derive(Parser)] +#[derive(Parser, Clone)] #[clap( name = "Hayabusa", usage = "hayabusa.exe [OTHER-ACTIONS] [OPTIONS]", @@ -242,23 +255,33 @@ impl ConfigReader<'_> { .help_template("\n\nUSAGE:\n {usage}\n\nOPTIONS:\n{options}"); ConfigReader { app: build_cmd, - args: parse, + args: parse.clone(), headless_help: String::default(), event_timeline_config: load_eventcode_info( - utils::check_setting_path( - &CURRENT_EXE_PATH.to_path_buf(), - "rules/config/statistics_event_info.txt", - ) - .to_str() - .unwrap(), + utils::check_setting_path(&parse.config, "statistics_event_info.txt", false) + .unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/statistics_event_info.txt", + true, + ) + .unwrap() + }) + .to_str() + .unwrap(), ), target_eventids: load_target_ids( - utils::check_setting_path( - &CURRENT_EXE_PATH.to_path_buf(), - "rules/config/target_event_IDs.txt", - ) - .to_str() - .unwrap(), + utils::check_setting_path(&parse.config, "target_event_IDs.txt", false) + .unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/target_event_IDs.txt", + true, + ) + .unwrap() + }) + .to_str() + .unwrap(), ), } } diff --git a/src/detections/message.rs b/src/detections/message.rs index 9aff48c5..e3a3d235 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -49,30 +49,32 @@ lazy_static! { 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/mitre_tactics.txt") - .to_str() + utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/mitre_tactics.txt", true) + .unwrap().to_str() .unwrap(), ); 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" - ) + utils::check_setting_path(&configs::CONFIG.read().unwrap().args.config, "channel_abbreviations.txt", false).unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/channel_abbreviations.txt", true + ).unwrap() + }) .to_str() .unwrap(), ); pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = configs::CONFIG.read().unwrap().args.pivot_keywords_list; - pub static ref DEFAULT_DETAILS: HashMap = get_default_details(&format!( - "{}/default_details.txt", - configs::CONFIG - .read() - .unwrap() - .args - .config - .as_path() - .display() - )); + pub static ref DEFAULT_DETAILS: HashMap = get_default_details( + utils::check_setting_path(&configs::CONFIG.read().unwrap().args.config, "default_details.txt", false).unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/default_details.txt", true + ).unwrap() + }) + .to_str() + .unwrap() + ); pub static ref LEVEL_ABBR: LinkedHashMap = LinkedHashMap::from_iter([ ("critical".to_string(), "crit".to_string()), ("high".to_string(), "high".to_string()), diff --git a/src/detections/utils.rs b/src/detections/utils.rs index d678bb07..f7ee3a14 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -73,7 +73,8 @@ pub fn value_to_string(value: &Value) -> Option { pub fn read_txt(filename: &str) -> Result, String> { let filepath = if filename.starts_with("./") { - check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), filename) + check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), filename, true) + .unwrap() .to_str() .unwrap() .to_string() @@ -380,14 +381,59 @@ pub fn make_ascii_titlecase(s: &mut str) -> String { } /// base_path/path が存在するかを確認し、存在しなければカレントディレクトリを参照するpathを返す関数 -pub fn check_setting_path(base_path: &Path, path: &str) -> PathBuf { +pub fn check_setting_path(base_path: &Path, path: &str, ignore_err: bool) -> Option { if base_path.join(path).exists() { - base_path.join(path) + Some(base_path.join(path)) + } else if ignore_err { + Some(Path::new(path).to_path_buf()) } else { - Path::new(path).to_path_buf() + None } } +/// rule configのファイルの所在を確認する関数。 +pub fn check_rule_config() -> Result<(), String> { + // rules/configのフォルダが存在するかを確認する + let exist_rule_config_folder = + if configs::CONFIG.read().unwrap().args.config == CURRENT_EXE_PATH.to_path_buf() { + check_setting_path( + &configs::CONFIG.read().unwrap().args.config, + "rules/config", + false, + ) + .is_some() + } else { + check_setting_path(&configs::CONFIG.read().unwrap().args.config, "", false).is_some() + }; + if !exist_rule_config_folder { + return Err("The required rules config files were not found. Please download them with --update-rules".to_string()); + } + + // 各種ファイルを確認する + let files = vec![ + "channel_abbreviations.txt", + "target_event_IDs.txt", + "default_details.txt", + "level_tuning.txt", + "statistics_event_info.txt", + "eventkey_alias.txt", + ]; + let mut not_exist_file = vec![]; + for file in &files { + if check_setting_path(&configs::CONFIG.read().unwrap().args.config, file, false).is_none() { + not_exist_file.push(*file); + } + } + + if !not_exist_file.is_empty() { + return Err(format!( + "Could not find the following config files: {}\nPlease specify a correct rules config directory.\n", + not_exist_file.join(", ") + )); + } + Ok(()) +} + ///タイムゾーンに合わせた情報を情報を取得する関数 pub fn format_time(time: &DateTime, date_only: bool) -> String { if configs::CONFIG.read().unwrap().args.utc { @@ -613,23 +659,31 @@ mod tests { let exist_path = Path::new("./test_files").to_path_buf(); let not_exist_path = Path::new("not_exist_path").to_path_buf(); assert_eq!( - check_setting_path(¬_exist_path, "rules") + check_setting_path(¬_exist_path, "rules", true) + .unwrap() .to_str() .unwrap(), "rules" ); assert_eq!( - check_setting_path(¬_exist_path, "fake") + check_setting_path(¬_exist_path, "fake", true) + .unwrap() .to_str() .unwrap(), "fake" ); assert_eq!( - check_setting_path(&exist_path, "rules").to_str().unwrap(), + check_setting_path(&exist_path, "rules", true) + .unwrap() + .to_str() + .unwrap(), exist_path.join("rules").to_str().unwrap() ); assert_eq!( - check_setting_path(&exist_path, "fake").to_str().unwrap(), + check_setting_path(&exist_path, "fake", true) + .unwrap() + .to_str() + .unwrap(), "fake" ); } diff --git a/src/main.rs b/src/main.rs index 4686af24..5da1b162 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,8 @@ use bytesize::ByteSize; use chrono::{DateTime, Datelike, Local}; use evtx::{EvtxParser, ParserSettings}; use hashbrown::{HashMap, HashSet}; -use hayabusa::detections::configs::CURRENT_EXE_PATH; use hayabusa::detections::configs::{load_pivot_keywords, TargetEventTime, TARGET_EXTENSIONS}; +use hayabusa::detections::configs::{CONFIG, CURRENT_EXE_PATH}; use hayabusa::detections::detection::{self, EvtxRecordInfo}; use hayabusa::detections::message::{ AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG, @@ -80,7 +80,9 @@ impl App { utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), "config/pivot_keywords.txt", + true, ) + .unwrap() .to_str() .unwrap(), ); @@ -148,13 +150,19 @@ impl App { // カレントディレクトリ以外からの実行の際にrules-configオプションの指定がないとエラーが発生することを防ぐための処理 if configs::CONFIG.read().unwrap().args.config == Path::new("./rules/config") { configs::CONFIG.write().unwrap().args.config = - utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "./rules/config"); + utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "rules/config", true) + .unwrap(); } // カレントディレクトリ以外からの実行の際にrulesオプションの指定がないとエラーが発生することを防ぐための処理 if configs::CONFIG.read().unwrap().args.rules == Path::new("./rules") { configs::CONFIG.write().unwrap().args.rules = - utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "./rules"); + utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "rules", true).unwrap(); + } + // rule configのフォルダ、ファイルを確認してエラーがあった場合は終了とする + if let Err(e) = utils::check_rule_config() { + AlertMessage::alert(&e).ok(); + return; } if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output { @@ -264,9 +272,18 @@ impl App { let level_tuning_config_path = match level_tuning_val { Some(path) => path.to_owned(), _ => utils::check_setting_path( - &CURRENT_EXE_PATH.to_path_buf(), - "./rules/config/level_tuning.txt", + &CONFIG.read().unwrap().args.config, + "level_tuning.txt", + false, ) + .unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/level_tuning.txt", + true, + ) + .unwrap() + }) .display() .to_string(), }; @@ -469,10 +486,10 @@ impl App { } fn print_contributors(&self) { - match fs::read_to_string(utils::check_setting_path( - &CURRENT_EXE_PATH.to_path_buf(), - "contributors.txt", - )) { + match fs::read_to_string( + utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "contributors.txt", true) + .unwrap(), + ) { Ok(contents) => { write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), @@ -721,7 +738,8 @@ impl App { /// output logo fn output_logo(&self) { - let fp = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "art/logo.txt"); + let fp = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "art/logo.txt", true) + .unwrap(); let content = fs::read_to_string(fp).unwrap_or_default(); let output_color = if configs::CONFIG.read().unwrap().args.no_color { None @@ -748,7 +766,8 @@ impl App { match eggs.get(exec_datestr) { None => {} Some(path) => { - let egg_path = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), path); + let egg_path = + utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), path, true).unwrap(); let content = fs::read_to_string(egg_path).unwrap_or_default(); write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), diff --git a/src/options/profile.rs b/src/options/profile.rs index 70e0e9cf..7483c26f 100644 --- a/src/options/profile.rs +++ b/src/options/profile.rs @@ -15,13 +15,20 @@ lazy_static! { pub static ref PROFILES: Option> = load_profile( check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), - "config/default_profile.yaml" + "config/default_profile.yaml", + true ) + .unwrap() .to_str() .unwrap(), - check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/profiles.yaml") - .to_str() - .unwrap() + check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "config/profiles.yaml", + true + ) + .unwrap() + .to_str() + .unwrap() ); pub static ref LOAEDED_PROFILE_ALIAS: HashSet = HashSet::from_iter( PROFILES