diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 22992552..c402f4c5 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -1,10 +1,11 @@ # 変更点 -## v1.4 [2022/XX/XX] +## v1.4.0 [2022/XX/XX] **新機能:** - `--target-file-ext` オプションの追加。evtx以外の拡張子を指定する事ができます。ただし、ファイルの中身の形式はevtxファイル形式である必要があります。 (#586) (@hitenkoku) +- `--exclude-status` オプションの追加。ルール内の`status`フィールドをもとに、読み込み対象から除外するフィルタを利用することができます。 (#596) (@hitenkoku) **改善:** @@ -36,6 +37,7 @@ - `--rfc-3339` オプションの時刻表示形式を変更した。 (#574) (@hitenkoku) - `-R/ --display-record-id`オプションを`-R/ --hide-record-id`に変更。レコードIDはデフォルトで出力するようにして`-R`オプションを付けた際に表示しないように変更した。(#579) (@hitenkoku) - ルール読み込み時のメッセージを追加した。 (#583) (@hitenkoku) +- `rules/tools/sigmac/testfiles`内のテスト用のymlファイルを読み込まないようにした. (#602) (@hitenkoku) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 43853336..c6166101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ # Changes -## v1.4 [2022/XX/XX] +## v1.4.0 [2022/XX/XX] **New Features:** - Added `--target-file-ext` option. You can specify additional file extensions to scan in addtition to the default `.evtx` files. For example, `--target-file-ext evtx_data` or multiple extensions with `--target-file-ext evtx1 evtx2`. (#586) (@hitenkoku) +- Added `--exclude-status` option: You can ignore rules based on their `status`. (#596) (@hitenkoku) **Enhancements:** @@ -12,6 +13,7 @@ - Updated the default usage and help menu. (#387) (@hitenkoku) - Added default details output based on `rules/config/default_details.txt` when no `details` field in a rule is specified. (i.e. Sigma rules) (#359) (@hitenkoku) - Added saved file size output when `output` is specified. (#595) (@hitenkoku) +- Ignored loading yml file in `rules/tools/sigmac/testfiles`. (#602) (@hitenkoku) **Bug Fixes:** diff --git a/README-Japanese.md b/README-Japanese.md index 621efc54..cd93e1d6 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -335,6 +335,7 @@ OPTIONS: -d, --directory .evtxファイルを持つディレクトリのパス -D, --enable-deprecated-rules Deprecatedルールを有効にする --end-timeline 解析対象とするイベントログの終了時刻 (例: "2022-02-22 23:59:59 +09:00") + --exclude-status ... 読み込み対象外とするルール内でのステータス (ex: experimental) (ex: stable test) -f, --filepath 1つの.evtxファイルに対して解析を行う -F, --full-data 全てのフィールド情報を出力する -h, --help ヘルプ情報を表示する diff --git a/README.md b/README.md index b4f48494..e61c07ca 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,7 @@ OPTIONS: -d, --directory Directory of multiple .evtx files -D, --enable-deprecated-rules Enable rules marked as deprecated --end-timeline End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00") + --exclude-status ... Ignore rules according to status (ex: experimental) (ex: stable test) -f, --filepath File path to one .evtx file -F, --full-data Print all field information -h, --help Print help information diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 883d7858..f6d06a61 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -32,6 +32,8 @@ lazy_static! { pub static ref TERM_SIZE: Option<(Width, Height)> = terminal_size(); pub static ref TARGET_EXTENSIONS: HashSet = get_target_extensions(CONFIG.read().unwrap().args.evtx_file_ext.as_ref()); + pub static ref EXCLUDE_STATUS: HashSet = + convert_option_vecs_to_hs(CONFIG.read().unwrap().args.exclude_status.as_ref()); } pub struct ConfigReader<'a> { @@ -211,6 +213,10 @@ pub struct Config { /// Specify additional target file extensions (ex: evtx_data) (ex: evtx1 evtx2) #[clap(long = "target-file-ext", multiple_values = true)] pub evtx_file_ext: Option>, + + /// Ignore rules according to status (ex: experimental) (ex: stable test) + #[clap(long = "exclude-status", multiple_values = true)] + pub exclude_status: Option>, } impl ConfigReader<'_> { @@ -461,12 +467,17 @@ pub fn load_pivot_keywords(path: &str) { /// --target-file-extで追加された拡張子から、調査対象ファイルの拡張子セットを返す関数 pub fn get_target_extensions(arg: Option<&Vec>) -> HashSet { - let mut target_file_extensions: HashSet = - arg.unwrap_or(&Vec::new()).iter().cloned().collect(); + let mut target_file_extensions: HashSet = convert_option_vecs_to_hs(arg); target_file_extensions.insert(String::from("evtx")); target_file_extensions } +/// Option>の内容をHashSetに変換する関数 +pub fn convert_option_vecs_to_hs(arg: Option<&Vec>) -> HashSet { + let ret: HashSet = arg.unwrap_or(&Vec::new()).iter().cloned().collect(); + ret +} + #[derive(Debug, Clone)] pub struct EventInfo { pub evttitle: String, diff --git a/src/detections/detection.rs b/src/detections/detection.rs index dfb01167..597fce32 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1,6 +1,9 @@ extern crate csv; 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; @@ -119,13 +122,11 @@ impl Detection { .filter_map(return_if_success) .collect(); if !*LOGONSUMMARY_FLAG { - let _ = &rulefile_loader - .rule_load_cnt - .insert(String::from("rule parsing error"), parseerror_count); Detection::print_rule_load_info( &rulefile_loader.rulecounter, &rulefile_loader.rule_load_cnt, &rulefile_loader.rule_status_cnt, + &parseerror_count, ); } ret @@ -363,46 +364,80 @@ impl Detection { rc: &HashMap, ld_rc: &HashMap, st_rc: &HashMap, + err_rc: &u128, ) { if *STATISTICS_FLAG { return; } let mut sorted_ld_rc: Vec<(&String, &u128)> = ld_rc.iter().collect(); sorted_ld_rc.sort_by(|a, b| a.0.cmp(b.0)); + let args = &configs::CONFIG.read().unwrap().args; + sorted_ld_rc.into_iter().for_each(|(key, value)| { - //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する - println!( - "{} rules: {}", - make_ascii_titlecase(key.clone().as_mut()), - value, - ); + if value != &0_u128 { + let disable_flag = if key == "noisy" && !args.enable_noisy_rules { + " (Disabled)" + } else { + "" + }; + //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する + println!( + "{} rules: {}{}", + make_ascii_titlecase(key.clone().as_mut()), + value, + disable_flag, + ); + } }); + if err_rc != &0_u128 { + write_color_buffer( + &BufferWriter::stdout(ColorChoice::Always), + Some(Color::Red), + &format!("Rule parsing errors: {}", err_rc), + ) + .ok(); + } println!(); let mut sorted_st_rc: Vec<(&String, &u128)> = st_rc.iter().collect(); let total_loaded_rule_cnt: u128 = sorted_st_rc.iter().map(|(_, v)| v.to_owned()).sum(); sorted_st_rc.sort_by(|a, b| a.0.cmp(b.0)); sorted_st_rc.into_iter().for_each(|(key, value)| { - let rate = if value == &0_u128 { - 0 as f64 - } else { - (*value as f64) / (total_loaded_rule_cnt as f64) * 100.0 - }; - //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する - println!( - "{} rules: {} ({:.2}%)", - make_ascii_titlecase(key.clone().as_mut()), - value, - rate - ); + if value != &0_u128 { + let rate = (*value as f64) / (total_loaded_rule_cnt as f64) * 100.0; + let deprecated_flag = if key == "deprecated" && !args.enable_deprecated_rules { + " (Disabled)" + } else { + "" + }; + //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する + write_color_buffer( + &BufferWriter::stdout(ColorChoice::Always), + None, + &format!( + "{} rules: {} ({:.2}%){}", + make_ascii_titlecase(key.clone().as_mut()), + value, + rate, + deprecated_flag + ), + ) + .ok(); + } }); println!(); let mut sorted_rc: Vec<(&String, &u128)> = rc.iter().collect(); sorted_rc.sort_by(|a, b| a.0.cmp(b.0)); sorted_rc.into_iter().for_each(|(key, value)| { - println!("{} rules: {}", key, value); + write_color_buffer( + &BufferWriter::stdout(ColorChoice::Always), + None, + &format!("{} rules: {}", key, value), + ) + .ok(); }); + println!("Total enabled detection rules: {}", total_loaded_rule_cnt); println!(); } diff --git a/src/filter.rs b/src/filter.rs index 425829e3..41fa26ec 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -29,18 +29,16 @@ impl RuleExclude { pub fn exclude_ids() -> RuleExclude { let mut exclude_ids = RuleExclude::default(); - if !configs::CONFIG.read().unwrap().args.enable_noisy_rules { - exclude_ids.insert_ids(&format!( - "{}/noisy_rules.txt", - configs::CONFIG - .read() - .unwrap() - .args - .config - .as_path() - .display() - )); - }; + exclude_ids.insert_ids(&format!( + "{}/noisy_rules.txt", + configs::CONFIG + .read() + .unwrap() + .args + .config + .as_path() + .display() + )); exclude_ids.insert_ids(&format!( "{}/exclude_rules.txt", diff --git a/src/yaml.rs b/src/yaml.rs index 49c1ba12..51d2a162 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -2,9 +2,9 @@ extern crate serde_derive; 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; -use crate::detections::print::QUIET_ERRORS_FLAG; +use crate::detections::print::{ERROR_LOG_STACK, QUIET_ERRORS_FLAG}; use crate::filter::RuleExclude; use hashbrown::HashMap; use std::ffi::OsStr; @@ -165,6 +165,19 @@ impl ParseYaml { return io::Result::Ok(ret); } + // ignore if tool test yml file in hayabusa-rules. + if path + .to_str() + .unwrap() + .contains("rules/tools/sigmac/test_files") + || path + .to_str() + .unwrap() + .contains("rules\\tools\\sigmac\\test_files") + { + return io::Result::Ok(ret); + } + // 個別のファイルの読み込みは即終了としない。 let read_content = self.read_file(path); if read_content.is_err() { @@ -231,7 +244,28 @@ impl ParseYaml { } else { "noisy" }; - let entry = self.rule_load_cnt.entry(entry_key.to_string()).or_insert(0); + // テスト用のルール(ID:000...0)の場合はexcluded ruleのカウントから除外するようにする + if v != "00000000-0000-0000-0000-000000000000" { + let entry = + self.rule_load_cnt.entry(entry_key.to_string()).or_insert(0); + *entry += 1; + } + if entry_key == "excluded" + || (entry_key == "noisy" + && !configs::CONFIG.read().unwrap().args.enable_noisy_rules) + { + return Option::None; + } + } + } + + let status = &yaml_doc["status"].as_str(); + if let Some(s) = status { + if EXCLUDE_STATUS.contains(&s.to_string()) { + let entry = self + .rule_load_cnt + .entry("excluded".to_string()) + .or_insert(0); *entry += 1; return Option::None; } @@ -271,19 +305,6 @@ impl ParseYaml { if doc_level_num < args_level_num { return Option::None; } - - if !configs::CONFIG.read().unwrap().args.enable_deprecated_rules { - let rule_status = &yaml_doc["status"].as_str().unwrap_or_default(); - if *rule_status == "deprecated" { - let entry = self - .rule_status_cnt - .entry(rule_status.to_string()) - .or_insert(0); - *entry += 1; - return Option::None; - } - } - Option::Some((filepath, yaml_doc)) }) .collect(); @@ -439,7 +460,7 @@ mod tests { yaml.read_dir(path, "", &exclude_ids).unwrap(); assert_eq!( yaml.rule_status_cnt.get("deprecated").unwrap().to_owned(), - 2 + 1 ); } }