diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 1ecc53ee..62b94e62 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -6,11 +6,11 @@ - 検知されたイベントが5つ以上の時、イベント頻度のタイムラインを作成するようにした。 (#533) (@hitenkoku) - `--all-tags`オプションでルールにある全てのtagsを、outputで指定したcsvのMitreAttackの列に出力するようにした。 (#525) (@hitenkoku) +- `-R` / `--display-record-id` オプションの追加。evtx file内のレコードを特定するレコードID``が出力できるようになった。 (#548) (@hitenkoku) **改善:** - ルールの`details`でeventkey_alias.txtやEvent.EventData内に存在しない情報を`n/a` (not available)と表記するようにした。(#528) (@hitenkoku) -- ルールの`details`でeventkey_alias.txtやEvent.EventData内に存在しない情報を`n/a` (not available)と表記するようにした。 (#528) (@hitenkoku) - 読み込んだイベント数と検知しなかったイベント数を表示するようにした。 (#538) (@hitenkoku) - 新しいロゴ。 (@YamatoSecurity) - evtxファイルのファイルサイズの合計を出力するようにした。(#540) (@hitenkoku) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12fdedf..2134532f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added Event Frequency Timeline feature to visualize the number of events. (Note: There needs to be more than 5 events.) (#533)(@hitenkoku) - Display all the `tags` defined in a rule to the `MitreAttack` column when saving to CSV file with the `--all-tags` option. (#525) (@hitenkoku) +- Added the `-R / --display-record-id` option: Display the event record ID (``). (#548) (@hitenkoku) **Enhancements:** diff --git a/README-Japanese.md b/README-Japanese.md index fdadaf49..947c1ec0 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -327,6 +327,7 @@ USAGE: -C --config=[RULECONFIGDIRECTORY] 'ルールフォルダのコンフィグディレクトリ(デフォルト: ./rules/config)' -o --output=[CSV_TIMELINE] 'タイムラインをCSV形式で保存する。(例: results.csv)' --all-tags '出力したCSVファイルにルール内のタグ情報を全て出力する。' + -R --display-record-id 'EventRecordIDを出力する。' -v --verbose '詳細な情報を出力する。' -D --enable-deprecated-rules 'Deprecatedルールを有効にする。' -n --enable-noisy-rules 'Noisyルールを有効にする。' @@ -500,7 +501,8 @@ CSVファイルとして保存する場合、以下の列が追加されます: * `Rule Path`: アラートまたはイベントを生成した検知ルールへのパス。 * `File Path`: アラートまたはイベントを起こしたevtxファイルへのパス。 -`-F`もしくは`--full-data`オプションを指定した場合、全てのフィールド情報が新しいカラムで出力されます。 +`-R`もしくは`--display-record-id`オプションを指定した場合、``の情報が`RecordID`カラムに出力されます。 +`-F`もしくは`--full-data`オプションを指定した場合、全てのフィールド情報が`RecordInformation`カラムにで出力されます。 ## MITRE ATT&CK戦術の省略 diff --git a/README.md b/README.md index 235a5fb7..a1724a10 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,7 @@ USAGE: -C --config=[RULECONFIGDIRECTORY] 'Rule config folder. (Default: ./rules/config)' -o --output=[CSV_TIMELINE] 'Save the timeline in CSV format. (Example: results.csv)' --all-tags 'Output all tags when saving to a CSV file.' + -R --display-record-id 'Display EventRecordID.' -v --verbose 'Output verbose information.' -D --enable-deprecated-rules 'Enable rules marked as deprecated.' -n --enable-noisy-rules 'Enable rules marked as noisy.' @@ -499,7 +500,8 @@ 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 `-F` or `--full-data` option, a new column with all field information will also be added. +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. ## MITRE ATT&CK Tactics Abbreviations diff --git a/src/afterfact.rs b/src/afterfact.rs index 209850d9..977d033b 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -31,6 +31,8 @@ pub struct CsvFormat<'a> { rule_title: &'a str, details: &'a str, #[serde(skip_serializing_if = "Option::is_none")] + record_i_d: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] record_information: Option<&'a str>, rule_path: &'a str, file_path: &'a str, @@ -47,6 +49,8 @@ pub struct DisplayFormat<'a> { pub rule_title: &'a str, pub details: &'a str, #[serde(skip_serializing_if = "Option::is_none")] + record_i_d: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub record_information: Option<&'a str>, } @@ -222,6 +226,10 @@ fn emit_csv( level = "info".to_string(); } if displayflag { + let record_id = detect_info + .record_id + .as_ref() + .map(|recinfo| _format_cellpos(recinfo, ColPos::Other)); let recinfo = detect_info .record_information .as_ref() @@ -241,6 +249,7 @@ fn emit_csv( rule_title: &_format_cellpos(&detect_info.alert, ColPos::Other), details: &_format_cellpos(&details, ColPos::Other), record_information: recinfo.as_deref(), + record_i_d: record_id.as_deref(), }; disp_wtr_buf @@ -269,6 +278,7 @@ fn emit_csv( record_information: detect_info.record_information.as_deref(), file_path: &detect_info.filepath, rule_path: &detect_info.rulepath, + record_i_d: detect_info.record_id.as_deref(), })?; } let level_suffix = *configs::LEVELMAP @@ -466,6 +476,7 @@ mod tests { let output = "pokepoke"; let test_attack = "execution/txxxx.yyy"; let test_recinfo = "record_infoinfo11"; + let test_record_id = "11111"; { let mut messages = print::MESSAGES.lock().unwrap(); messages.clear(); @@ -501,6 +512,7 @@ mod tests { detail: String::default(), tag_info: test_attack.to_string(), record_information: Option::Some(test_recinfo.to_string()), + record_id: Option::Some(test_record_id.to_string()), }, ); } @@ -509,7 +521,7 @@ mod tests { .unwrap(); let expect_tz = expect_time.with_timezone(&Local); let expect = - "Timestamp,Computer,Channel,EventID,Level,MitreAttack,RuleTitle,Details,RecordInformation,RulePath,FilePath\n" + "Timestamp,Computer,Channel,EventID,Level,MitreAttack,RuleTitle,Details,RecordID,RecordInformation,RulePath,FilePath\n" .to_string() + &expect_tz .clone() @@ -530,6 +542,8 @@ mod tests { + "," + output + "," + + test_record_id + + "," + test_recinfo + "," + test_rulepath @@ -556,12 +570,13 @@ mod tests { let test_channel = "Sysmon"; let output = "displaytest"; let test_recinfo = "testinfo"; + let test_recid = "22222"; 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|RecordInformation\n"; + "Timestamp|Computer|Channel|EventID|Level|RuleTitle|Details|RecordID|RecordInformation\n"; let expect_tz = test_timestamp.with_timezone(&Local); let expect_no_header = expect_tz @@ -581,6 +596,8 @@ mod tests { + "|" + output + "|" + + test_recid + + "|" + test_recinfo + "\n"; let expect_with_header = expect_header.to_string() + &expect_no_header; @@ -595,6 +612,7 @@ mod tests { rule_title: test_title, details: output, record_information: Some(test_recinfo), + record_i_d: Some(test_recid), }, true ), @@ -611,6 +629,7 @@ mod tests { rule_title: test_title, details: output, record_information: Some(test_recinfo), + record_i_d: Some(test_recid), }, false ), diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 05c7955a..6fc74ba0 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -77,6 +77,7 @@ fn build_app<'a>() -> ArgMatches<'a> { -C --config=[RULECONFIGDIRECTORY] 'Rule config folder. (Default: ./rules/config)' -o --output=[CSV_TIMELINE] 'Save the timeline in CSV format. (Example: results.csv)' --all-tags 'Output all tags when saving to a CSV file.' + -R --display-record-id 'Display EventRecordID.' -v --verbose 'Output verbose information.' -D --enable-deprecated-rules 'Enable rules marked as deprecated.' -n --enable-noisy-rules 'Enable rules marked as noisy.' diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 7e866adc..b6ec1eab 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, TAGS_CONFIG}; +use crate::detections::print::{CH_CONFIG, IS_DISPLAY_RECORD_ID, TAGS_CONFIG}; use crate::detections::rule; use crate::detections::rule::AggResult; use crate::detections::rule::RuleNode; @@ -231,6 +231,14 @@ impl Detection { .record_information .as_ref() .map(|recinfo| recinfo.to_string()); + let rec_id = if *IS_DISPLAY_RECORD_ID { + Some( + get_serde_number_to_string(&record_info.record["Event"]["System"]["EventRecordID"]) + .unwrap_or_default(), + ) + } else { + None + }; let detect_info = DetectInfo { filepath: record_info.evtx_filepath.to_string(), rulepath: rule.rulepath.to_string(), @@ -251,6 +259,7 @@ impl Detection { detail: String::default(), tag_info: tag_info.join(" | "), record_information: recinfo, + record_id: rec_id, }; MESSAGES.lock().unwrap().insert( &record_info.record, @@ -274,6 +283,11 @@ impl Detection { } else { Option::None }; + let rec_id = if *IS_DISPLAY_RECORD_ID { + Some(String::default()) + } else { + None + }; let detect_info = DetectInfo { filepath: "-".to_owned(), rulepath: rule.rulepath.to_owned(), @@ -285,6 +299,7 @@ impl Detection { detail: output, record_information: rec_info, tag_info: tag_info.join(" : "), + record_id: rec_id, }; MESSAGES diff --git a/src/detections/print.rs b/src/detections/print.rs index bf6b6ab2..85d2e0e2 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -33,6 +33,7 @@ pub struct DetectInfo { pub detail: String, pub tag_info: String, pub record_information: Option, + pub record_id: Option, } pub struct AlertMessage {} @@ -75,6 +76,11 @@ lazy_static! { .unwrap() .args .is_present("pivot-keywords-list"); + pub static ref IS_DISPLAY_RECORD_ID: bool = configs::CONFIG + .read() + .unwrap() + .args + .is_present("display-record-id"); } impl Default for Message { @@ -309,6 +315,7 @@ mod tests { detail: String::default(), tag_info: "txxx.001".to_string(), record_information: Option::Some("record_information1".to_string()), + record_id: Option::Some("11111".to_string()), }, ); @@ -341,6 +348,7 @@ mod tests { detail: String::default(), tag_info: "txxx.002".to_string(), record_information: Option::Some("record_information2".to_string()), + record_id: Option::Some("22222".to_string()), }, ); @@ -373,6 +381,7 @@ mod tests { detail: String::default(), tag_info: "txxx.003".to_string(), record_information: Option::Some("record_information3".to_string()), + record_id: Option::Some("33333".to_string()), }, ); @@ -400,12 +409,13 @@ mod tests { 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\") }], 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\") }, 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\") }], 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\") }]} }"; + 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); }