diff --git a/Cargo.lock b/Cargo.lock index cab6a4bf..d28b627b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "console" version = "0.11.3" @@ -766,11 +777,13 @@ dependencies = [ "base64", "chrono", "clap", + "colored", "csv", "dotenv", "evtx", "flate2", "hashbrown", + "hex 0.4.3", "hhmmss", "lazy_static", "linked-hash-map", @@ -802,6 +815,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hhmmss" version = "0.1.0" @@ -1927,7 +1946,7 @@ checksum = "50e26b33762cd2ec755267c4a4af36adb0864b93afbe595ea8ff61b5528f4c11" dependencies = [ "chrono", "error-chain 0.11.0", - "hex", + "hex 0.3.2", "reqwest", "serde", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index af29763b..14c387ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ dotenv = "0.15.0" hhmmss = "*" pbr = "*" hashbrown = "0.11.2" +colored = "2.0.0" +hex = "0.4.3" [target.x86_64-pc-windows-gnu] linker = "x86_64-w64-mingw32-gcc" diff --git a/README-English.md b/README-English.md index d602562f..267937cb 100644 --- a/README-English.md +++ b/README-English.md @@ -242,6 +242,10 @@ When saving to a CSV file an additional two fields will be added: The progress bar will only work with multiple evtx files. It will display in real time the number and percent of evtx files that it has analyzed. +## Color Output +Hayabusa output to the screen can change font color by `Level`. +Config file is `.\config\level_color.txt`. format is `level,(RGB 6digit ColorHex)`. + # Hayabusa rules Hayabusa detection rules are written in a sigma-like YML format and are located in the `rules` folder. In the future, we plan to host the rules at [https://github.com/Yamato-Security/hayabusa-rules](https://github.com/Yamato-Security/hayabusa-rules) so please send any issues and pull requests for rules there instead of the main hayabusa repository. diff --git a/README-Japanese.md b/README-Japanese.md index 22e89881..f51784ac 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -243,6 +243,11 @@ CSVファイルとして保存する場合、以下の2つのフィールドが プログレス・バーは、複数のevtxファイルに対してのみ機能します。 解析したevtxファイルの数と割合をリアルタイムで表示します。 +## 標準出力へのカラー設定 +Hayabusaの結果はLevel毎に文字色を変えることができます。 +`.\config\level_color.txt`の値を変更することで文字色を変えることができます。 +形式は`level名,(6桁のRGBのカラーhex)`です。 + # Hayabusa ルール Hayabusa検知ルールはSigmaのようなYML形式で記述されています。`rules`ディレクトリに入っていますが、将来的には[https://github.com/Yamato-Security/hayabusa-rules](https://github.com/Yamato-Security/hayabusa-rules)のレポジトリで管理する予定なので、ルールのissueとpull requestはhayabusaのレポジトリではなく、ルールレポジトリへお願いします。 diff --git a/config/level_color.txt b/config/level_color.txt new file mode 100644 index 00000000..2a50e2a3 --- /dev/null +++ b/config/level_color.txt @@ -0,0 +1,5 @@ +level,colorcode +critical,ff0000 +high,ffff00 +medium,00ff00 +low,00ffff \ No newline at end of file diff --git a/src/afterfact.rs b/src/afterfact.rs index a19a01b7..40a41911 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1,7 +1,10 @@ use crate::detections::configs; use crate::detections::print; use crate::detections::print::AlertMessage; +use crate::detections::utils; use chrono::{DateTime, Local, TimeZone, Utc}; +use colored::*; +use hashbrown::HashMap; use serde::Serialize; use std::error::Error; use std::fs::File; @@ -26,11 +29,48 @@ pub struct CsvFormat<'a> { #[serde(rename_all = "PascalCase")] pub struct DisplayFormat<'a> { timestamp: &'a str, - computer: &'a str, - event_i_d: &'a str, - level: &'a str, - rule_title: &'a str, - details: &'a str, + pub computer: &'a str, + pub event_i_d: &'a str, + pub level: &'a str, + pub rule_title: &'a str, + pub details: &'a str, +} + +/// level_color.txtファイルを読み込み対応する文字色のマッピングを返却する関数 +pub fn set_output_color() -> Option>> { + let read_result = utils::read_csv("config/level_color.txt"); + if read_result.is_err() { + // color情報がない場合は通常の白色の出力が出てくるのみで動作への影響を与えない為warnとして処理する + AlertMessage::warn( + &mut BufWriter::new(std::io::stderr().lock()), + &read_result.as_ref().unwrap_err(), + ) + .ok(); + return None; + } + let mut color_map: HashMap> = HashMap::new(); + read_result.unwrap().into_iter().for_each(|line| { + if line.len() != 2 { + return; + } + let empty = &"".to_string(); + let level = line.get(0).unwrap_or(empty); + let convert_color_result = hex::decode(line.get(1).unwrap_or(empty).trim()); + if convert_color_result.is_err() { + AlertMessage::warn( + &mut BufWriter::new(std::io::stderr().lock()), + &format!("Failed hex convert in level_color.txt. Color output is disabled. Input Line: {}",line.join(",")) + ) + .ok(); + return; + } + let color_code = convert_color_result.unwrap(); + if level.len() == 0 || color_code.len() < 3 { + return; + } + color_map.insert(level.to_string(), color_code); + }); + return Some(color_map); } pub fn after_fact() { @@ -63,13 +103,17 @@ pub fn after_fact() { // 標準出力に出力する場合 Box::new(BufWriter::new(io::stdout())) }; - - if let Err(err) = emit_csv(&mut target, displayflag) { + let color_map = set_output_color(); + if let Err(err) = emit_csv(&mut target, displayflag, color_map) { fn_emit_csv_err(Box::new(err)); } } -fn emit_csv(writer: &mut W, displayflag: bool) -> io::Result<()> { +fn emit_csv( + writer: &mut W, + displayflag: bool, + color_map: Option>>, +) -> io::Result<()> { let mut wtr; if displayflag { wtr = csv::WriterBuilder::new() @@ -88,13 +132,63 @@ fn emit_csv(writer: &mut W, displayflag: bool) -> io::Result< for (time, detect_infos) in messages.iter() { for detect_info in detect_infos { if displayflag { + // カラーをつけない場合は255,255,255で出力する + let mut output_color: Vec = vec![255, 255, 255]; + if color_map.is_some() { + let target_color = color_map.as_ref().unwrap().get(&detect_info.level); + if target_color.is_some() { + output_color = target_color.unwrap().to_vec(); + } + } wtr.serialize(DisplayFormat { - timestamp: &format!("{} ", &format_time(time)), - level: &format!(" {} ", &detect_info.level), - computer: &format!(" {} ", &detect_info.computername), - event_i_d: &format!(" {} ", &detect_info.eventid), - rule_title: &format!(" {} ", &detect_info.alert), - details: &format!(" {}", &detect_info.detail), + timestamp: &format!( + "{} ", + &format_time(time).truecolor( + output_color[0], + output_color[1], + output_color[2] + ) + ), + level: &format!( + " {} ", + &detect_info.level.truecolor( + output_color[0], + output_color[1], + output_color[2] + ) + ), + computer: &format!( + " {} ", + &detect_info.computername.truecolor( + output_color[0], + output_color[1], + output_color[2] + ) + ), + event_i_d: &format!( + " {} ", + &detect_info.eventid.truecolor( + output_color[0], + output_color[1], + output_color[2] + ) + ), + rule_title: &format!( + " {} ", + &detect_info.alert.truecolor( + output_color[0], + output_color[1], + output_color[2] + ) + ), + details: &format!( + " {}", + &detect_info.detail.truecolor( + output_color[0], + output_color[1], + output_color[2] + ) + ), })?; } else { // csv出力時フォーマット @@ -266,7 +360,7 @@ mod tests { + "\n"; let mut file: Box = Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap()); - assert!(emit_csv(&mut file, false).is_ok()); + assert!(emit_csv(&mut file, false, None).is_ok()); match read_to_string("./test_emit_csv.csv") { Err(_) => panic!("Failed to open file."), Ok(s) => { @@ -337,7 +431,7 @@ mod tests { + "\n"; let mut file: Box = Box::new(File::create("./test_emit_csv_display.txt".to_string()).unwrap()); - assert!(emit_csv(&mut file, true).is_ok()); + assert!(emit_csv(&mut file, true, None).is_ok()); match read_to_string("./test_emit_csv_display.txt") { Err(_) => panic!("Failed to open file."), Ok(s) => { diff --git a/src/detections/print.rs b/src/detections/print.rs index 6c9ef6b8..4bd5b291 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -3,11 +3,11 @@ use crate::detections::configs; use crate::detections::utils; use crate::detections::utils::get_serde_number_to_string; use chrono::{DateTime, Local, TimeZone, Utc}; +use hashbrown::HashMap; use lazy_static::lazy_static; use regex::Regex; use serde_json::Value; use std::collections::BTreeMap; -use std::collections::HashMap; use std::env; use std::fs::create_dir; use std::fs::File;