From 2aa19ca02ca8fa58a77f0b2e8e6df4386c64c161 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:52:12 +0900 Subject: [PATCH 01/14] changed table output crate from prettytable-rs to comfy_table #707 - 1. [] Unified output one table with -s and -d option - 2. [] add channel column to table output - 3. [] Remove First Timestamp and Last Timestamp with -d option - 4. [] Output csv with -o and -s option - 5. [] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- Cargo.lock | 66 +-------------------------------------- Cargo.toml | 1 - src/timeline/timelines.rs | 14 ++++++--- 3 files changed, 10 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b6b3458..ccbf9f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,7 +248,7 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" dependencies = [ - "encode_unicode 0.3.6", + "encode_unicode", "libc", "once_cell", "terminal_size", @@ -403,27 +403,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "discard" version = "1.0.4" @@ -448,12 +427,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - [[package]] name = "encoding" version = "0.2.33" @@ -781,7 +754,6 @@ dependencies = [ "num_cpus", "openssl", "pbr", - "prettytable-rs", "pulldown-cmark", "quick-xml", "rand", @@ -1399,20 +1371,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" -[[package]] -name = "prettytable-rs" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f375cb74c23b51d23937ffdeb48b1fbf5b6409d4b9979c1418c1de58bc8f801" -dependencies = [ - "atty", - "csv", - "encode_unicode 1.0.0", - "lazy_static", - "term", - "unicode-width", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1551,17 +1509,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - [[package]] name = "regex" version = "1.6.0" @@ -1997,17 +1944,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "termcolor" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index f1435d16..48db1b77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ hashbrown = "0.12.*" hex = "0.4.*" git2 = "0.*" termcolor = "*" -prettytable-rs = "0.*" krapslog = "*" terminal_size = "*" bytesize = "1.*" diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 751643cd..5b240bb0 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,6 +1,8 @@ use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; -use prettytable::{Cell, Row, Table}; +use comfy_table::*; +use comfy_table::modifiers::UTF8_ROUND_CORNERS; +use comfy_table::presets::UTF8_FULL; use super::metrics::EventMetrics; use hashbrown::HashMap; @@ -142,7 +144,9 @@ impl Timeline { } } else { let mut logins_stats_tb = Table::new(); - logins_stats_tb.set_titles(row!["User", "Failed", "Successful"]); + logins_stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) + .set_style(TableComponent::VerticalLines, ' '); + logins_stats_tb.set_header(vec!["User", "Failed", "Successful"]); // 集計件数でソート let mut mapsorted: Vec<_> = self.stats.stats_login_list.iter().collect(); mapsorted.sort_by(|x, y| x.0.cmp(y.0)); @@ -153,13 +157,13 @@ impl Timeline { //key.to_string().pop(); username.pop(); username.remove(0); - logins_stats_tb.add_row(Row::new(vec![ + logins_stats_tb.add_row(vec![ Cell::new(&username), Cell::new(&values[1].to_string()), Cell::new(&values[0].to_string()), - ])); + ]); } - logins_stats_tb.printstd(); + println!("{logins_stats_tb}"); println!(); } } From 7db4f739a27c1a4447d507e0267d792ac4149cb1 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:57:13 +0900 Subject: [PATCH 02/14] changed doc comment --- src/timeline/timelines.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 5b240bb0..b010324a 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -131,7 +131,8 @@ impl Timeline { msges.push("---------------------------------------".to_string()); msges } - // ユーザ毎のログイン統計情報出力メッセージ生成 + + /// ユーザ毎のログイン統計情報出力メッセージ生成 fn tm_loginstats_tb_set_msg(&self) { println!("Logon Summary"); if self.stats.stats_login_list.is_empty() { From a254c5794938ae084c6b0caa8758c390ea76d057 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 21:28:20 +0900 Subject: [PATCH 03/14] removed no use crate(prettytable-rs) import #707 - 1. [] Unified output one table with -s and -d option - 2. [] add channel column to table output - 3. [] Remove First Timestamp and Last Timestamp with -d option - 4. [] Output csv with -o and -s option - 5. [] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index db666270..655018ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,4 @@ pub mod options; pub mod timeline; pub mod yaml; #[macro_use] -extern crate prettytable; -#[macro_use] extern crate horrorshow; From a152439cc9840b8548faff3f3ca4109c5e6492e3 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 21:43:40 +0900 Subject: [PATCH 04/14] separate two columnt Count and Percent #707 - 1. [] Unified output one table with -s and -d option - 2. [] add channel column to table output - 3. [] Remove First Timestamp and Last Timestamp with -d option - 4. [] Output csv with -o and -s option - 5. [x] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/timeline/timelines.rs | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index b010324a..eb10e1e6 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -48,22 +48,25 @@ impl Timeline { sammsges.push(format!("Total Event Records: {}\n", self.stats.total)); sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time)); - sammsges.push("Count (Percent)\tID\tEvent\t".to_string()); - sammsges.push("--------------- ------- ---------------".to_string()); + + let mut stats_tb = Table::new(); + stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) + .set_style(TableComponent::VerticalLines, ' '); + stats_tb.set_header(vec!["Count", "Percent", "ID", "Event"]); + // 集計件数でソート let mut mapsorted: Vec<_> = self.stats.stats_list.iter().collect(); mapsorted.sort_by(|x, y| y.1.cmp(x.1)); // イベントID毎の出力メッセージ生成 - let stats_msges: Vec = self.tm_stats_set_msg(mapsorted); + let stats_msges: Vec> = self.tm_stats_set_msg(mapsorted); for msgprint in sammsges.iter() { println!("{}", msgprint); } - for msgprint in stats_msges.iter() { - println!("{}", msgprint); - } + stats_tb.add_rows(stats_msges); + println!("{stats_tb}"); } pub fn tm_logon_stats_dsp_msg(&mut self) { @@ -86,8 +89,8 @@ impl Timeline { } // イベントID毎の出力メッセージ生成 - fn tm_stats_set_msg(&self, mapsorted: Vec<(&std::string::String, &usize)>) -> Vec { - let mut msges: Vec = Vec::new(); + fn tm_stats_set_msg(&self, mapsorted: Vec<(&std::string::String, &usize)>) -> Vec> { + let mut msges: Vec> = Vec::new(); for (event_id, event_cnt) in mapsorted.iter() { // 件数の割合を算出 @@ -101,34 +104,31 @@ impl Timeline { .get_event_id(*event_id) .is_some(); // event_id_info.txtに登録あるものは情報設定 + // 出力メッセージ1行作成 if conf { - // 出力メッセージ1行作成 - msges.push(format!( - "{0} ({1:.1}%)\t{2}\t{3}", - event_cnt, - (rate * 1000.0).round() / 10.0, - event_id, - &CONFIG + msges.push(vec! + [event_cnt.to_string(), + format!("{:.1}%", (rate * 1000.0).round() / 10.0), + event_id.to_string(), + CONFIG .read() .unwrap() .event_timeline_config .get_event_id(*event_id) .unwrap() - .evttitle, - )); + .evttitle.to_string(), + ] + ); } else { - // 出力メッセージ1行作成 - msges.push(format!( - "{0} ({1:.1}%)\t{2}\t{3}", - event_cnt, - (rate * 1000.0).round() / 10.0, - event_id, - "Unknown", - )); + msges.push( + vec![ + event_cnt.to_string(), + format!("{:.1}%", (rate * 1000.0).round() / 10.0), + event_id.to_string(), + "Unknown".to_string(), + ]); } } - - msges.push("---------------------------------------".to_string()); msges } From dc67fd3f053202ca5b788a85d4d1ff184062ef93 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:08:09 +0900 Subject: [PATCH 05/14] refactoring --- src/timeline/metrics.rs | 98 ++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 54 deletions(-) diff --git a/src/timeline/metrics.rs b/src/timeline/metrics.rs index d04e2ddd..065e7802 100644 --- a/src/timeline/metrics.rs +++ b/src/timeline/metrics.rs @@ -66,79 +66,69 @@ impl EventMetrics { self.filepath = records[0].evtx_filepath.as_str().to_owned(); // sortしなくてもイベントログのTimeframeを取得できるように修正しました。 // sortしないことにより計算量が改善されています。 - // もうちょっと感じに書けるといえば書けます。 for record in records.iter() { - let evttime = utils::get_event_value( + if let Some(evttime) = utils::get_event_value( "Event.System.TimeCreated_attributes.SystemTime", &record.record, ) - .map(|evt_value| evt_value.to_string()); - if evttime.is_none() { - continue; - } - - let evttime = evttime.unwrap(); - if self.start_time.is_empty() || evttime < self.start_time { - self.start_time = evttime.to_string(); - } - if self.end_time.is_empty() || evttime > self.end_time { - self.end_time = evttime; - } + .map(|evt_value| evt_value.to_string()) { + if self.start_time.is_empty() || evttime < self.start_time { + self.start_time = evttime.to_string(); + } + if self.end_time.is_empty() || evttime > self.end_time { + self.end_time = evttime; + } + }; } self.total += records.len(); } - // EventIDで集計 + /// EventID`で集計 fn stats_eventid(&mut self, records: &[EvtxRecordInfo]) { // let mut evtstat_map = HashMap::new(); for record in records.iter() { - let evtid = utils::get_event_value("EventID", &record.record); - if evtid.is_none() { - continue; - } + if let Some(idnum) = utils::get_event_value("EventID", &record.record) { + let count: &mut usize = self.stats_list.entry(idnum.to_string()).or_insert(0); + *count += 1; + }; - let idnum = evtid.unwrap(); - let count: &mut usize = self.stats_list.entry(idnum.to_string()).or_insert(0); - *count += 1; } // return evtstat_map; } // Login event fn stats_login_eventid(&mut self, records: &[EvtxRecordInfo]) { for record in records.iter() { - let evtid = utils::get_event_value("EventID", &record.record); - if evtid.is_none() { - continue; - } - let idnum: i64 = if evtid.unwrap().is_number() { - evtid.unwrap().as_i64().unwrap() - } else { - evtid - .unwrap() - .as_str() - .unwrap() - .parse::() - .unwrap_or_default() - }; - if !(idnum == 4624 || idnum == 4625) { - continue; - } + if let Some(evtid) = utils::get_event_value("EventID", &record.record) { - let username = utils::get_event_value("TargetUserName", &record.record); - let countlist: [usize; 2] = [0, 0]; - if idnum == 4624 { - let count: &mut [usize; 2] = self - .stats_login_list - .entry(username.unwrap().to_string()) - .or_insert(countlist); - count[0] += 1; - } else if idnum == 4625 { - let count: &mut [usize; 2] = self - .stats_login_list - .entry(username.unwrap().to_string()) - .or_insert(countlist); - count[1] += 1; - } + let idnum: i64 = if evtid.is_number() { + evtid.as_i64().unwrap() + } else { + evtid + .as_str() + .unwrap() + .parse::() + .unwrap_or_default() + }; + if !(idnum == 4624 || idnum == 4625) { + continue; + } + + let username = utils::get_event_value("TargetUserName", &record.record); + let countlist: [usize; 2] = [0, 0]; + if idnum == 4624 { + let count: &mut [usize; 2] = self + .stats_login_list + .entry(username.unwrap().to_string()) + .or_insert(countlist); + count[0] += 1; + } else if idnum == 4625 { + let count: &mut [usize; 2] = self + .stats_login_list + .entry(username.unwrap().to_string()) + .or_insert(countlist); + count[1] += 1; + } + }; } } } From 06c4e5684271bc8853c78ce38b0bc39f55854f7d Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:09:28 +0900 Subject: [PATCH 06/14] fixed login stats table vertical line format --- src/timeline/timelines.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index eb10e1e6..7a67b00b 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -50,8 +50,7 @@ impl Timeline { sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time)); let mut stats_tb = Table::new(); - stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) - .set_style(TableComponent::VerticalLines, ' '); + stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); stats_tb.set_header(vec!["Count", "Percent", "ID", "Event"]); From cdfdd62a5c167ca98f53933f4dc77e49d386db62 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:48:52 +0900 Subject: [PATCH 07/14] Unified output one table with -s and -d option #707 - 1. [x] Unified output one table with -s and -d option - 2. [] add channel column to table output - 3. [] Remove First Timestamp and Last Timestamp with -d option - 4. [] Output csv with -o and -s option - 5. [x] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/main.rs | 20 ++++++++++++-------- src/timeline/metrics.rs | 2 +- src/timeline/timelines.rs | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index b08f2659..c50d3fa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -656,15 +656,22 @@ impl App { self.rule_keys = self.get_all_keys(&rule_files); let mut detection = detection::Detection::new(rule_files); let mut total_records: usize = 0; + let mut tl = Timeline::new(); for evtx_file in evtx_files { if configs::CONFIG.read().unwrap().args.verbose { println!("Checking target evtx FilePath: {:?}", &evtx_file); } let cnt_tmp: usize; - (detection, cnt_tmp) = self.analysis_file(evtx_file, detection, time_filter); + (detection, cnt_tmp, tl) = self.analysis_file(evtx_file, detection, time_filter, tl.clone()); total_records += cnt_tmp; pb.inc(); } + if *METRICS_FLAG { + tl.tm_stats_dsp_msg(); + } + if *LOGONSUMMARY_FLAG { + tl.tm_logon_stats_dsp_msg(); + } if configs::CONFIG.read().unwrap().args.output.is_some() { println!(); println!(); @@ -683,15 +690,15 @@ impl App { evtx_filepath: PathBuf, mut detection: detection::Detection, time_filter: &TargetEventTime, - ) -> (detection::Detection, usize) { + mut tl: Timeline + ) -> (detection::Detection, usize, Timeline) { let path = evtx_filepath.display(); let parser = self.evtx_to_jsons(evtx_filepath.clone()); let mut record_cnt = 0; if parser.is_none() { - return (detection, record_cnt); + return (detection, record_cnt, tl); } - let mut tl = Timeline::new(); let mut parser = parser.unwrap(); let mut records = parser.records_json_value(); @@ -760,10 +767,7 @@ impl App { } } - tl.tm_stats_dsp_msg(); - tl.tm_logon_stats_dsp_msg(); - - (detection, record_cnt) + (detection, record_cnt, tl) } async fn create_rec_infos( diff --git a/src/timeline/metrics.rs b/src/timeline/metrics.rs index 065e7802..fe756572 100644 --- a/src/timeline/metrics.rs +++ b/src/timeline/metrics.rs @@ -2,7 +2,7 @@ use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG}; use crate::detections::{detection::EvtxRecordInfo, utils}; use hashbrown::HashMap; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EventMetrics { pub total: usize, pub filepath: String, diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 7a67b00b..fd27612b 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -7,7 +7,7 @@ use comfy_table::presets::UTF8_FULL; use super::metrics::EventMetrics; use hashbrown::HashMap; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Timeline { pub stats: EventMetrics, } From 65994dbd463d5cd1788bd2f55bc6eaec0025be9f Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:50:06 +0900 Subject: [PATCH 08/14] Removed First Timestamp and Last Timestamp with -d option #707 - 1. [x] Unified output one table with -s and -d option - 2. [] add channel column to table output - 3. [x] Remove First Timestamp and Last Timestamp with -d option - 4. [] Output csv with -o and -s option - 5. [x] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/timeline/metrics.rs | 1 - src/timeline/timelines.rs | 14 +++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/timeline/metrics.rs b/src/timeline/metrics.rs index fe756572..6a376611 100644 --- a/src/timeline/metrics.rs +++ b/src/timeline/metrics.rs @@ -93,7 +93,6 @@ impl EventMetrics { }; } - // return evtstat_map; } // Login event fn stats_login_eventid(&mut self, records: &[EvtxRecordInfo]) { diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index fd27612b..f09c22c2 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -43,11 +43,15 @@ impl Timeline { } // 出力メッセージ作成 let mut sammsges: Vec = Vec::new(); - sammsges.push("---------------------------------------".to_string()); - sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); - sammsges.push(format!("Total Event Records: {}\n", self.stats.total)); - sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); - sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time)); + let total_event_record = format!("\nTotal Event Records: {}\n", self.stats.total); + if CONFIG.read().unwrap().args.filepath.is_some() { + sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); + sammsges.push(total_event_record); + sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); + sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time)); + } else { + sammsges.push(total_event_record); + } let mut stats_tb = Table::new(); stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); From 11477f8e137920646907fba9073d47550bfeb449 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 29 Sep 2022 00:36:54 +0900 Subject: [PATCH 09/14] ADD chanel column to table output #707 - 1. [x] Unified output one table with -s and -d option - 2. [x] add channel column to table output - 3. [x] Remove First Timestamp and Last Timestamp with -d option - 4. [] Output csv with -o and -s option - 5. [x] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/timeline/metrics.rs | 11 ++++++++--- src/timeline/timelines.rs | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/timeline/metrics.rs b/src/timeline/metrics.rs index 6a376611..d88c2f8e 100644 --- a/src/timeline/metrics.rs +++ b/src/timeline/metrics.rs @@ -8,7 +8,7 @@ pub struct EventMetrics { pub filepath: String, pub start_time: String, pub end_time: String, - pub stats_list: HashMap, + pub stats_list: HashMap<(String, String), usize>, pub stats_login_list: HashMap, } /** @@ -20,7 +20,7 @@ impl EventMetrics { filepath: String, start_time: String, end_time: String, - stats_list: HashMap, + stats_list: HashMap<(String, String), usize>, stats_login_list: HashMap, ) -> EventMetrics { EventMetrics { @@ -87,8 +87,13 @@ impl EventMetrics { fn stats_eventid(&mut self, records: &[EvtxRecordInfo]) { // let mut evtstat_map = HashMap::new(); for record in records.iter() { + let channel = if let Some(ch) = utils::get_event_value("Channel", &record.record) { + ch.to_string() + } else { + "-".to_string() + }; if let Some(idnum) = utils::get_event_value("EventID", &record.record) { - let count: &mut usize = self.stats_list.entry(idnum.to_string()).or_insert(0); + let count: &mut usize = self.stats_list.entry((idnum.to_string(), channel)).or_insert(0); *count += 1; }; diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index f09c22c2..bab366b7 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,4 +1,4 @@ -use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG}; +use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG, CH_CONFIG}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; use comfy_table::*; use comfy_table::modifiers::UTF8_ROUND_CORNERS; @@ -55,7 +55,7 @@ impl Timeline { let mut stats_tb = Table::new(); stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); - stats_tb.set_header(vec!["Count", "Percent", "ID", "Event"]); + stats_tb.set_header(vec!["Count", "Percent", "Channel", "ID", "Event"]); // 集計件数でソート @@ -92,10 +92,10 @@ impl Timeline { } // イベントID毎の出力メッセージ生成 - fn tm_stats_set_msg(&self, mapsorted: Vec<(&std::string::String, &usize)>) -> Vec> { + fn tm_stats_set_msg(&self, mapsorted: Vec<(&(std::string::String, std::string::String), &usize)>) -> Vec> { let mut msges: Vec> = Vec::new(); - for (event_id, event_cnt) in mapsorted.iter() { + for ((event_id, channel), event_cnt) in mapsorted.iter() { // 件数の割合を算出 let rate: f32 = **event_cnt as f32 / self.stats.total as f32; @@ -104,20 +104,23 @@ impl Timeline { .read() .unwrap() .event_timeline_config - .get_event_id(*event_id) + .get_event_id(event_id) .is_some(); // event_id_info.txtに登録あるものは情報設定 // 出力メッセージ1行作成 + let fmted_channel = channel.replace('\"', ""); + let ch = CH_CONFIG.get(fmted_channel.to_lowercase().as_str()).unwrap_or(&fmted_channel).to_string(); if conf { msges.push(vec! [event_cnt.to_string(), format!("{:.1}%", (rate * 1000.0).round() / 10.0), + ch, event_id.to_string(), CONFIG .read() .unwrap() .event_timeline_config - .get_event_id(*event_id) + .get_event_id(event_id) .unwrap() .evttitle.to_string(), ] @@ -127,7 +130,8 @@ impl Timeline { vec![ event_cnt.to_string(), format!("{:.1}%", (rate * 1000.0).round() / 10.0), - event_id.to_string(), + ch, + event_id.replace('\"', ""), "Unknown".to_string(), ]); } @@ -157,8 +161,6 @@ impl Timeline { for (key, values) in &mapsorted { let mut username: String = key.to_string(); - //key.to_string().retain(|c| c != '\"'); - //key.to_string().pop(); username.pop(); username.remove(0); logins_stats_tb.add_row(vec![ From 3a073b419c0093e5b412d8535c1451e730b1c26d Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 29 Sep 2022 00:41:25 +0900 Subject: [PATCH 10/14] Remove First Timestamp and Last Timestamp with -d option (-L option) #707 - 1. [x] Unified output one table with -M/-L and -d option - 2. [x] add channel column to table output - 3. [x] Remove First Timestamp and Last Timestamp with -d option (-M / -L option) - 4. [] Output csv with -o and -M / -L option - 5. [x] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/timeline/timelines.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index bab366b7..2a43bc03 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -78,12 +78,15 @@ impl Timeline { } // 出力メッセージ作成 let mut sammsges: Vec = Vec::new(); - sammsges.push("---------------------------------------".to_string()); - sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); - sammsges.push(format!("Total Event Records: {}\n", self.stats.total)); - sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); - sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time)); - sammsges.push("---------------------------------------".to_string()); + let total_event_record = format!("\nTotal Event Records: {}\n", self.stats.total); + if CONFIG.read().unwrap().args.filepath.is_some() { + sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); + sammsges.push(total_event_record); + sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); + sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time)); + } else { + sammsges.push(total_event_record); + } for msgprint in sammsges.iter() { println!("{}", msgprint); } From 6fef1c94e648a2dfbe193af46668f4ae6d1a2d01 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 29 Sep 2022 01:30:23 +0900 Subject: [PATCH 11/14] Added output csv with -o option and -M / -L option #707 - 1. [x] Unified output one table with -M/-L and -d option - 2. [x] add channel column to table output - 3. [x] Remove First Timestamp and Last Timestamp with -d option (-M / -L option) - 4. [x] Output csv with -o and -M / -L option - 5. [x] Separete two column Count and Percent - 6. [x] change table format output crate from prettytable-rs to comfy_table. --- src/timeline/timelines.rs | 82 ++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 2a43bc03..168cbe8a 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,4 +1,9 @@ -use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG, CH_CONFIG}; +use std::io::BufWriter; +use std::fs::File; + +use csv::WriterBuilder; +use downcast_rs::__std::process; +use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG, CH_CONFIG, AlertMessage}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; use comfy_table::*; use comfy_table::modifiers::UTF8_ROUND_CORNERS; @@ -53,9 +58,31 @@ impl Timeline { sammsges.push(total_event_record); } + let header = vec!["Count", "Percent", "Channel", "ID", "Event"]; + let target; + let mut wtr= + if let Some(csv_path) = &CONFIG.read().unwrap().args.output { + // output to file + match File::create(csv_path) { + Ok(file) => { + target = Box::new(BufWriter::new(file)); + Some(WriterBuilder::new().from_writer(target)) + }, + Err(err) => { + AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); + process::exit(1); + } + } + } else { + None + }; + if let Some(ref mut w) = wtr { + w.write_record(&header).ok(); + } + let mut stats_tb = Table::new(); stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); - stats_tb.set_header(vec!["Count", "Percent", "Channel", "ID", "Event"]); + stats_tb.set_header(header); // 集計件数でソート @@ -68,6 +95,14 @@ impl Timeline { for msgprint in sammsges.iter() { println!("{}", msgprint); } + if CONFIG.read().unwrap().args.output.is_some() { + for msg in stats_msges.iter(){ + if let Some(ref mut w) = wtr { + w.write_record(msg).ok(); + } + + } + } stats_tb.add_rows(stats_msges); println!("{stats_tb}"); } @@ -153,11 +188,33 @@ impl Timeline { for msgprint in loginmsges.iter() { println!("{}", msgprint); } - } else { + } else { + + let header = vec!["User", "Failed", "Successful"]; + let target; + let mut wtr= + if let Some(csv_path) = &CONFIG.read().unwrap().args.output { + // output to file + match File::create(csv_path) { + Ok(file) => { + target = Box::new(BufWriter::new(file)); + Some(WriterBuilder::new().from_writer(target)) + }, + Err(err) => { + AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); + process::exit(1); + } + } + } else { + None + }; + if let Some(ref mut w) = wtr { + w.write_record(&header).ok(); + } + let mut logins_stats_tb = Table::new(); logins_stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) - .set_style(TableComponent::VerticalLines, ' '); - logins_stats_tb.set_header(vec!["User", "Failed", "Successful"]); + logins_stats_tb.set_header(&header); // 集計件数でソート let mut mapsorted: Vec<_> = self.stats.stats_login_list.iter().collect(); mapsorted.sort_by(|x, y| x.0.cmp(y.0)); @@ -166,11 +223,16 @@ impl Timeline { let mut username: String = key.to_string(); username.pop(); username.remove(0); - logins_stats_tb.add_row(vec![ - Cell::new(&username), - Cell::new(&values[1].to_string()), - Cell::new(&values[0].to_string()), - ]); + let record_data = vec![ + username, + values[1].to_string(), + values[0].to_string(), + ]; + if let Some(ref mut w) = wtr { + w.write_record(&record_data).ok(); + } + logins_stats_tb.add_row(record_data); + } println!("{logins_stats_tb}"); println!(); From 0090e61e81c71139abb4fcf5bf73dbb66a3ca103 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 29 Sep 2022 01:32:21 +0900 Subject: [PATCH 12/14] fixed syntax error --- src/timeline/timelines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 168cbe8a..75430c09 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -213,7 +213,7 @@ impl Timeline { } let mut logins_stats_tb = Table::new(); - logins_stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) + logins_stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); logins_stats_tb.set_header(&header); // 集計件数でソート let mut mapsorted: Vec<_> = self.stats.stats_login_list.iter().collect(); From 4e9d833fb0c2aac9ad1273accda251327b8014a1 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 29 Sep 2022 01:33:18 +0900 Subject: [PATCH 13/14] cargo fmt --- src/main.rs | 7 ++-- src/timeline/metrics.rs | 18 ++++----- src/timeline/timelines.rs | 79 +++++++++++++++++++-------------------- 3 files changed, 51 insertions(+), 53 deletions(-) diff --git a/src/main.rs b/src/main.rs index c50d3fa9..fa78a0cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -662,13 +662,14 @@ impl App { println!("Checking target evtx FilePath: {:?}", &evtx_file); } let cnt_tmp: usize; - (detection, cnt_tmp, tl) = self.analysis_file(evtx_file, detection, time_filter, tl.clone()); + (detection, cnt_tmp, tl) = + self.analysis_file(evtx_file, detection, time_filter, tl.clone()); total_records += cnt_tmp; pb.inc(); } if *METRICS_FLAG { tl.tm_stats_dsp_msg(); - } + } if *LOGONSUMMARY_FLAG { tl.tm_logon_stats_dsp_msg(); } @@ -690,7 +691,7 @@ impl App { evtx_filepath: PathBuf, mut detection: detection::Detection, time_filter: &TargetEventTime, - mut tl: Timeline + mut tl: Timeline, ) -> (detection::Detection, usize, Timeline) { let path = evtx_filepath.display(); let parser = self.evtx_to_jsons(evtx_filepath.clone()); diff --git a/src/timeline/metrics.rs b/src/timeline/metrics.rs index d88c2f8e..1126e88f 100644 --- a/src/timeline/metrics.rs +++ b/src/timeline/metrics.rs @@ -71,7 +71,8 @@ impl EventMetrics { "Event.System.TimeCreated_attributes.SystemTime", &record.record, ) - .map(|evt_value| evt_value.to_string()) { + .map(|evt_value| evt_value.to_string()) + { if self.start_time.is_empty() || evttime < self.start_time { self.start_time = evttime.to_string(); } @@ -93,30 +94,27 @@ impl EventMetrics { "-".to_string() }; if let Some(idnum) = utils::get_event_value("EventID", &record.record) { - let count: &mut usize = self.stats_list.entry((idnum.to_string(), channel)).or_insert(0); + let count: &mut usize = self + .stats_list + .entry((idnum.to_string(), channel)) + .or_insert(0); *count += 1; }; - } } // Login event fn stats_login_eventid(&mut self, records: &[EvtxRecordInfo]) { for record in records.iter() { if let Some(evtid) = utils::get_event_value("EventID", &record.record) { - let idnum: i64 = if evtid.is_number() { evtid.as_i64().unwrap() } else { - evtid - .as_str() - .unwrap() - .parse::() - .unwrap_or_default() + evtid.as_str().unwrap().parse::().unwrap_or_default() }; if !(idnum == 4624 || idnum == 4625) { continue; } - + let username = utils::get_event_value("TargetUserName", &record.record); let countlist: [usize; 2] = [0, 0]; if idnum == 4624 { diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 75430c09..86ac113f 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,13 +1,13 @@ -use std::io::BufWriter; use std::fs::File; +use std::io::BufWriter; -use csv::WriterBuilder; -use downcast_rs::__std::process; -use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG, CH_CONFIG, AlertMessage}; +use crate::detections::message::{AlertMessage, CH_CONFIG, LOGONSUMMARY_FLAG, METRICS_FLAG}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; -use comfy_table::*; use comfy_table::modifiers::UTF8_ROUND_CORNERS; use comfy_table::presets::UTF8_FULL; +use comfy_table::*; +use csv::WriterBuilder; +use downcast_rs::__std::process; use super::metrics::EventMetrics; use hashbrown::HashMap; @@ -57,34 +57,34 @@ impl Timeline { } else { sammsges.push(total_event_record); } - + let header = vec!["Count", "Percent", "Channel", "ID", "Event"]; let target; - let mut wtr= - if let Some(csv_path) = &CONFIG.read().unwrap().args.output { + let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().args.output { // output to file match File::create(csv_path) { Ok(file) => { target = Box::new(BufWriter::new(file)); Some(WriterBuilder::new().from_writer(target)) - }, + } Err(err) => { - AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); + AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); process::exit(1); } } - } else { - None + } else { + None }; if let Some(ref mut w) = wtr { w.write_record(&header).ok(); } let mut stats_tb = Table::new(); - stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); + stats_tb + .load_preset(UTF8_FULL) + .apply_modifier(UTF8_ROUND_CORNERS); stats_tb.set_header(header); - // 集計件数でソート let mut mapsorted: Vec<_> = self.stats.stats_list.iter().collect(); mapsorted.sort_by(|x, y| y.1.cmp(x.1)); @@ -96,11 +96,10 @@ impl Timeline { println!("{}", msgprint); } if CONFIG.read().unwrap().args.output.is_some() { - for msg in stats_msges.iter(){ + for msg in stats_msges.iter() { if let Some(ref mut w) = wtr { w.write_record(msg).ok(); } - } } stats_tb.add_rows(stats_msges); @@ -130,7 +129,10 @@ impl Timeline { } // イベントID毎の出力メッセージ生成 - fn tm_stats_set_msg(&self, mapsorted: Vec<(&(std::string::String, std::string::String), &usize)>) -> Vec> { + fn tm_stats_set_msg( + &self, + mapsorted: Vec<(&(std::string::String, std::string::String), &usize)>, + ) -> Vec> { let mut msges: Vec> = Vec::new(); for ((event_id, channel), event_cnt) in mapsorted.iter() { @@ -147,10 +149,13 @@ impl Timeline { // event_id_info.txtに登録あるものは情報設定 // 出力メッセージ1行作成 let fmted_channel = channel.replace('\"', ""); - let ch = CH_CONFIG.get(fmted_channel.to_lowercase().as_str()).unwrap_or(&fmted_channel).to_string(); + let ch = CH_CONFIG + .get(fmted_channel.to_lowercase().as_str()) + .unwrap_or(&fmted_channel) + .to_string(); if conf { - msges.push(vec! - [event_cnt.to_string(), + msges.push(vec![ + event_cnt.to_string(), format!("{:.1}%", (rate * 1000.0).round() / 10.0), ch, event_id.to_string(), @@ -160,12 +165,11 @@ impl Timeline { .event_timeline_config .get_event_id(event_id) .unwrap() - .evttitle.to_string(), - ] - ); + .evttitle + .to_string(), + ]); } else { - msges.push( - vec![ + msges.push(vec![ event_cnt.to_string(), format!("{:.1}%", (rate * 1000.0).round() / 10.0), ch, @@ -188,32 +192,32 @@ impl Timeline { for msgprint in loginmsges.iter() { println!("{}", msgprint); } - } else { - + } else { let header = vec!["User", "Failed", "Successful"]; let target; - let mut wtr= - if let Some(csv_path) = &CONFIG.read().unwrap().args.output { + let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().args.output { // output to file match File::create(csv_path) { Ok(file) => { target = Box::new(BufWriter::new(file)); Some(WriterBuilder::new().from_writer(target)) - }, + } Err(err) => { AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); process::exit(1); } } - } else { - None + } else { + None }; if let Some(ref mut w) = wtr { w.write_record(&header).ok(); } let mut logins_stats_tb = Table::new(); - logins_stats_tb.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); + logins_stats_tb + .load_preset(UTF8_FULL) + .apply_modifier(UTF8_ROUND_CORNERS); logins_stats_tb.set_header(&header); // 集計件数でソート let mut mapsorted: Vec<_> = self.stats.stats_login_list.iter().collect(); @@ -223,16 +227,11 @@ impl Timeline { let mut username: String = key.to_string(); username.pop(); username.remove(0); - let record_data = vec![ - username, - values[1].to_string(), - values[0].to_string(), - ]; + let record_data = vec![username, values[1].to_string(), values[0].to_string()]; if let Some(ref mut w) = wtr { w.write_record(&record_data).ok(); - } + } logins_stats_tb.add_row(record_data); - } println!("{logins_stats_tb}"); println!(); From bac2d5733b94a0799295f23980dcd2f414eb94b0 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 29 Sep 2022 01:52:00 +0900 Subject: [PATCH 14/14] updated changelog --- CHANGELOG-Japanese.md | 6 ++++++ CHANGELOG.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 2e59ede0..28c1468c 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -11,6 +11,12 @@ - EventID解析のオプションをmetricsオプションに変更した。(旧: `-s, --statistics` -> 新: `-M, --metrics`) (#706) (@hitenkoku) - ルール更新オプション(`-u`)を利用したときにHayabusaの新バージョンがないかを確認し、表示するようにした。 (#710) (@hitenkoku) - HTMLレポート内にロゴを追加した。 (#714) (@hitenkoku) +- メトリクスオプション(`-M --metrics`)もしくはログオン情報(`-L --logon-summary`)と`-d`オプションを利用した場合に1つのテーブルで表示されるように修正した。 (#707) (@hitenkoku) +- メトリクスオプションの結果出力にチャンネル列を追加した。 (#707) (@hitenkoku) +- メトリクスオプション(`-M --metrics`)もしくはログオン情報(`-L --logon-summary`)と`-d`オプションを利用した場合に「First Timestamp」と「Last Timestamp」の出力を行わないように修正した。 (#707) (@hitenkoku) +- メトリクスオプションとログオン情報オプションに対してcsv出力機能(`-o --output`)を追加した。 (#707) (@hitenkoku) +- メトリクスオプションの出力を検出回数と全体の割合が1つのセルで表示されていた箇所を2つの列に分けた。 (#707) (@hitenkoku) +- メトリクスオプションとログオン情報の画面出力に利用していたprettytable-rsクレートをcomfy_tableクレートに修正した. (#707) (@hitenkoku) ## v1.6.0 [2022/09/16] diff --git a/CHANGELOG.md b/CHANGELOG.md index dd6703b3..97716bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ (Note: `statistics_event_info.txt` was changed to `event_id_info.txt`.) - Display new version of Hayabusa link when updating if there is a newer version. (#710) (@hitenkoku) - Added logo in HTML summary output. (#714) (@hitenkoku) +- Unified output one table of -M or -L option with -d option. (#707) (@hitenkoku) +- Added Channel column to metrics output. (#707) (@hitenkoku) +- Removed First Timestamp and Last Timestamp of -M and -L option with -d option. (#707) (@hitenkoku) +- Added csv output option(`-o --output`) when -M and -L option is used. (#707) (@hitenkoku) +- Separated Count and Percent columns in metric output. (#707) (@hitenkoku) +- Changed output table format of metric option and logon information crate from prettytable-rs to comfy_table. (#707) (@hitenkoku) ## v1.6.0 [2022/09/16]