diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 2930389c..7486dd45 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -12,6 +12,7 @@ - LinuxとmacOSのバイナリサイズをより小さくするために、デバッグシンボルをストリップします。(#568) (@YamatoSecurity) - 新たな時刻表示のオプションとして`--US-time`、`--US-military-time`、`--European-time`の3つを追加した (#574) (@hitenkoku) - `--rfc-3339` オプションの時刻表示形式を変更した。 (#574) (@hitenkoku) +- `-R/ --display-record-id`オプションを`-R/ --hide-record-id`に変更。レコードIDはデフォルトで出力するようにして`-R`オプションを付けた際に表示しないように変更した。(#579) (@hitenkoku) - ルール読み込み時のメッセージを追加した。 (#583) (@hitenkoku) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 1781d7ca..27f99e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Strip debug symbols by default for smaller Linux and macOS binaries. (#568) (@YamatoSecurity) - Added new output time format options. (`--US-time`, `--US-military-time`, `--European-time`) (#574) (@hitenkoku) - Changed output time format when `--rfc-3339` option is enabled. (#574) (@hitenkoku) +- Changed the `-R / --display-record-id` option to `-R / --hide-record-id` and now by default the event record ID is displayed. You can hide the record ID with `-R / --hide-record-id`. (#579) (@hitenkoku) - Added rule loading message. (#583) (@hitenkoku) **Bug Fixes:** diff --git a/README-Japanese.md b/README-Japanese.md index 5824c5a1..e3a8d076 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -61,6 +61,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/) - [ログオン情報の要約](#ログオン情報の要約) - [サンプルevtxファイルでHayabusaをテストする](#サンプルevtxファイルでhayabusaをテストする) - [Hayabusaの出力](#hayabusaの出力) + - [Levelの省略](#levelの省略) - [MITRE ATT&CK戦術の省略](#mitre-attck戦術の省略) - [Channel情報の省略](#channel情報の省略) - [プログレスバー](#プログレスバー) @@ -329,7 +330,7 @@ USAGE: -C, --config [RULE_CONFIG_DIRECTORY] 'ルールフォルダのコンフィグディレクトリ(デフォルト: .\rules\config)' -o, --output [CSV_TIMELINE] 'タイムラインをCSV形式で保存する。(例: results.csv)' --all-tags '出力したCSVファイルにルール内のタグ情報を全て出力する。' - -R, --display-record-id 'イベントレコードIDを出力する。' + -R, --hide-record-id 'イベントレコードIDを表示しない。' -v, --verbose '詳細な情報を出力する。' -V, --visualize-timeline 'イベント頻度タイムラインを出力する。' -D, --enable-deprecated-rules 'Deprecatedルールを有効にする。' @@ -499,6 +500,7 @@ Hayabusaの結果を標準出力に表示しているとき(デフォルト) * `Event ID`: イベントログの``フィールドから来ています。 * `Level`: YML検知ルールの`level`フィールドから来ています。(例:`informational`, `low`, `medium`, `high`, `critical`) デフォルトでは、すべてのレベルのアラートとイベントが出力されますが、`-m`オプションで最低のレベルを指定することができます。例えば`-m high`オプションを付けると、`high`と`critical`アラートしか出力されません。 * `Title`: YML検知ルールの`title`フィールドから来ています。 +* `RecordID`: イベントレコードIDです。``フィールドから来ています。`-R`もしくは`--hide-record-id`オプションを付けると表示されません。 * `Details`: YML検知ルールの`details`フィールドから来ていますが、このフィールドはHayabusaルールにしかありません。このフィールドはアラートとイベントに関する追加情報を提供し、ログの``部分から有用なデータを抽出することができます。イベントキーのマッピングが間違っている場合、もしくはフィールドが存在しない場合で抽出ができなかった箇所は`n/a` (not available)と記載されます。 CSVファイルとして保存する場合、以下の列が追加されます: @@ -507,9 +509,18 @@ CSVファイルとして保存する場合、以下の列が追加されます: * `Rule Path`: アラートまたはイベントを生成した検知ルールへのパス。 * `File Path`: アラートまたはイベントを起こしたevtxファイルへのパス。 -`-R`もしくは`--display-record-id`オプションを指定した場合、``の情報が`RecordID`カラムに出力されます。 `-F`もしくは`--full-data`オプションを指定した場合、全てのフィールド情報が`RecordInformation`カラムにで出力されます。 +## Levelの省略 + +簡潔に出力するためにLevelを以下のように省略し出力しています。 + +* `crit`: `critical` +* `high`: `high` +* `med `: `med` +* `low `: `low` +* `info`: `informational` + ## MITRE ATT&CK戦術の省略 簡潔に出力するためにMITRE ATT&CKの戦術を以下のように省略しています。 diff --git a/README.md b/README.md index d87b0ffd..6257e0b5 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre - [Logon Summary Generator](#logon-summary-generator) - [Testing Hayabusa on Sample Evtx Files](#testing-hayabusa-on-sample-evtx-files) - [Hayabusa Output](#hayabusa-output) + - [Level Abbrevations](#level-abbrevations) - [MITRE ATT&CK Tactics Abbreviations](#mitre-attck-tactics-abbreviations) - [Channel Abbreviations](#channel-abbreviations) - [Progress Bar](#progress-bar) @@ -327,7 +328,7 @@ USAGE: -C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\rules\config)' -o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)' --all-tags 'Output all tags when saving to a CSV file.' - -R, --display-record-id 'Display event record ID.' + -R, --hide-record-id 'Do not display the EventRecordID number.' -v, --verbose 'Output verbose information.' -V, --visualize-timeline 'Output event frequency timeline.' -D, --enable-deprecated-rules 'Enable rules marked as deprecated.' @@ -497,6 +498,7 @@ When hayabusa output is being displayed to the screen (the default), it will dis * `Channel`: The name of log. This comes from the `` field in the event log. * `Event ID`: This comes from the `` field in the event log. * `Level`: This comes from the `level` field in the YML detection rule. (`informational`, `low`, `medium`, `high`, `critical`) By default, all level alerts will be displayed but you can set the minimum level with `-m`. For example, you can set `-m high`) in order to only scan for and display high and critical alerts. +* `RecordID`: This comes from the `` field in the event log. You can hidde this output with the `-R` or `--hide-record-id` option. * `Title`: This comes from the `title` field in the YML detection rule. * `Details`: This comes from the `details` field in the YML detection rule, however, only hayabusa rules have this field. This field gives extra information about the alert or event and can extract useful data from the `` portion of the log. For example, usernames, command line information, process information, etc... When a placeholder points to a field that does not exist or there is an incorrect alias mapping, it will be outputted as `n/a` (not available). @@ -506,9 +508,18 @@ The following additional columns will be added to the output when saving to a CS * `Rule Path`: The path to the detection rule that generated the alert or event. * `File Path`: The path to the evtx file that caused the alert or event. -If you add the `-R` or `--display-record-id` option, a `RecordId` column with event record ID values taken from `` will also be added. If you add the `-F` or `--full-data` option, a `RecordInformation` column with all field information will also be added. +## Level Abbrevations + +In order to save space, we use the following abbrevations when displaying the alert `level`. + +* `crit`: `critical` +* `high`: `high` +* `med `: `med` +* `low `: `low` +* `info`: `informational` + ## MITRE ATT&CK Tactics Abbreviations In order to save space, we use the following abbreviations when displaying MITRE ATT&CK tactic tags. diff --git a/screenshots/Hayabusa-Results.png b/screenshots/Hayabusa-Results.png index 3a2b7e07..026b686d 100644 Binary files a/screenshots/Hayabusa-Results.png and b/screenshots/Hayabusa-Results.png differ diff --git a/src/afterfact.rs b/src/afterfact.rs index bce27093..1e36212e 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1,6 +1,6 @@ use crate::detections::configs; use crate::detections::print; -use crate::detections::print::AlertMessage; +use crate::detections::print::{AlertMessage, IS_HIDE_RECORD_ID}; use crate::detections::utils; use crate::detections::utils::write_color_buffer; use chrono::{DateTime, Local, TimeZone, Utc}; @@ -29,10 +29,10 @@ pub struct CsvFormat<'a> { event_i_d: &'a str, level: &'a str, mitre_attack: &'a str, - rule_title: &'a str, - details: &'a str, #[serde(skip_serializing_if = "Option::is_none")] record_i_d: Option<&'a str>, + rule_title: &'a str, + details: &'a str, #[serde(skip_serializing_if = "Option::is_none")] record_information: Option<&'a str>, rule_path: &'a str, @@ -47,10 +47,10 @@ pub struct DisplayFormat<'a> { pub channel: &'a str, pub event_i_d: &'a str, pub level: &'a str, - pub rule_title: &'a str, - pub details: &'a str, #[serde(skip_serializing_if = "Option::is_none")] record_i_d: Option<&'a str>, + pub rule_title: &'a str, + pub details: &'a str, #[serde(skip_serializing_if = "Option::is_none")] pub record_information: Option<&'a str>, } @@ -217,6 +217,13 @@ fn emit_csv( "informational", "undefined", ]); + let level_abbr: HashMap = HashMap::from([ + (String::from("cruitical"), String::from("crit")), + (String::from("high"), String::from("high")), + (String::from("medium"), String::from("med ")), + (String::from("low"), String::from("low ")), + (String::from("informational"), String::from("info")), + ]); // レベル別、日ごとの集計用変数の初期化 for level_init in levels { detect_counts_by_date_and_level.insert(level_init.to_string(), HashMap::new()); @@ -233,10 +240,7 @@ fn emit_csv( timestamps.push(_get_timestamp(time)); for detect_info in detect_infos { detected_record_idset.insert(format!("{}_{}", time, detect_info.eventid)); - let mut level = detect_info.level.to_string(); - if level == "informational" { - level = "info".to_string(); - } + let level = detect_info.level.to_string(); let time_str = format_time(time, false); if displayflag { let record_id = detect_info @@ -247,15 +251,24 @@ fn emit_csv( .record_information .as_ref() .map(|recinfo| _format_cellpos(recinfo, ColPos::Last)); - let details = detect_info + let ctr_char_exclude_details = detect_info .detail .chars() .filter(|&c| !c.is_control()) .collect::(); - let dispformat = DisplayFormat { + let details = if ctr_char_exclude_details.is_empty() { + "-".to_string() + } else { + ctr_char_exclude_details + }; + + let dispformat: _ = DisplayFormat { timestamp: &_format_cellpos(&time_str, ColPos::First), - level: &_format_cellpos(&level, ColPos::Other), + level: &_format_cellpos( + level_abbr.get(&level).unwrap_or(&level), + ColPos::Other, + ), computer: &_format_cellpos(&detect_info.computername, ColPos::Other), event_i_d: &_format_cellpos(&detect_info.eventid, ColPos::Other), channel: &_format_cellpos(&detect_info.channel, ColPos::Other), @@ -285,7 +298,7 @@ fn emit_csv( // csv output format wtr.serialize(CsvFormat { timestamp: &time_str, - level: &level, + level: level_abbr.get(&level).unwrap_or(&level).trim(), computer: &detect_info.computername, event_i_d: &detect_info.eventid, channel: &detect_info.channel, @@ -423,11 +436,10 @@ fn _get_serialized_disp_output(dispformat: Option) -> String { "RuleTitle", "Details", ]; - let arg_match = &configs::CONFIG.read().unwrap().args; - if arg_match.is_present("display-record-id") { - titles.push("RecordID"); + if !*IS_HIDE_RECORD_ID { + titles.insert(5, "RecordID"); } - if arg_match.is_present("full-data") { + if configs::CONFIG.read().unwrap().args.is_present("full-data") { titles.push("RecordInformation"); } return format!("{}\n", titles.join("|")); @@ -725,7 +737,7 @@ mod tests { .unwrap(); let expect_tz = expect_time.with_timezone(&Local); let expect = - "Timestamp,Computer,Channel,EventID,Level,MitreAttack,RuleTitle,Details,RecordID,RecordInformation,RulePath,FilePath\n" + "Timestamp,Computer,Channel,EventID,Level,MitreAttack,RecordID,RuleTitle,Details,RecordInformation,RulePath,FilePath\n" .to_string() + &expect_tz .clone() @@ -742,12 +754,12 @@ mod tests { + "," + test_attack + "," + + test_record_id + + "," + test_title + "," + output + "," - + test_record_id - + "," + test_recinfo + "," + test_rulepath @@ -779,7 +791,7 @@ mod tests { let test_timestamp = Utc .datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ") .unwrap(); - let expect_header = "Timestamp|Computer|Channel|EventID|Level|RuleTitle|Details\n"; + let expect_header = "Timestamp|Computer|Channel|EventID|Level|RecordID|RuleTitle|Details\n"; let expect_tz = test_timestamp.with_timezone(&Local); let expect_no_header = expect_tz @@ -795,12 +807,12 @@ mod tests { + "|" + test_level + "|" + + test_recid + + "|" + test_title + "|" + output + "|" - + test_recid - + "|" + test_recinfo + "\n"; assert_eq!(_get_serialized_disp_output(None,), expect_header); diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 2b42bb8b..15b2e973 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -76,7 +76,7 @@ fn build_app<'a>() -> ArgMatches<'a> { -C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\\rules\\config)' -o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)' --all-tags 'Output all tags when saving to a CSV file.' - -R, --display-record-id 'Display event record ID.' + -R, --hide-record-id 'Do not display EventRecordID number.' -v, --verbose 'Output verbose information.' -V, --visualize-timeline 'Output event frequency timeline.' -D, --enable-deprecated-rules 'Enable rules marked as deprecated.' diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 63bf81df..23a38cd8 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -9,7 +9,7 @@ use crate::detections::print::MESSAGES; use crate::detections::print::PIVOT_KEYWORD_LIST_FLAG; use crate::detections::print::QUIET_ERRORS_FLAG; use crate::detections::print::STATISTICS_FLAG; -use crate::detections::print::{CH_CONFIG, IS_DISPLAY_RECORD_ID, TAGS_CONFIG}; +use crate::detections::print::{CH_CONFIG, IS_HIDE_RECORD_ID, TAGS_CONFIG}; use crate::detections::rule; use crate::detections::rule::AggResult; use crate::detections::rule::RuleNode; @@ -233,7 +233,7 @@ impl Detection { .record_information .as_ref() .map(|recinfo| recinfo.to_string()); - let rec_id = if *IS_DISPLAY_RECORD_ID { + let rec_id = if !*IS_HIDE_RECORD_ID { Some( get_serde_number_to_string(&record_info.record["Event"]["System"]["EventRecordID"]) .unwrap_or_default(), @@ -281,7 +281,7 @@ impl Detection { } else { Option::None }; - let rec_id = if *IS_DISPLAY_RECORD_ID { + let rec_id = if !*IS_HIDE_RECORD_ID { Some(String::default()) } else { None diff --git a/src/detections/print.rs b/src/detections/print.rs index 5680ab63..1ab92db4 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -79,11 +79,11 @@ lazy_static! { .unwrap() .args .is_present("pivot-keywords-list"); - pub static ref IS_DISPLAY_RECORD_ID: bool = configs::CONFIG + pub static ref IS_HIDE_RECORD_ID: bool = configs::CONFIG .read() .unwrap() .args - .is_present("display-record-id"); + .is_present("hide-record-id"); } impl Default for Message {