diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 88408164..1f370774 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -12,8 +12,9 @@ - ルールの`details`でeventkey_alias.txtやEvent.EventData内に存在しない情報を`n/a` (not available)と表記するようにした。(#528) (@hitenkoku) - 読み込んだイベント数と検知しなかったイベント数を表示するようにした。 (#538) (@hitenkoku) -- 新しいロゴ。 (@YamatoSecurity) +- 新しいロゴに変更した。(#536) (@YamatoSecurity) - evtxファイルのファイルサイズの合計を出力するようにした。(#540) (@hitenkoku) +- ロゴの色を変更した (#537) (@hitenkoku) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d68dc7c..a2b2efa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ - In the `details` line in a rule, 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). (#528) (@hitenkoku) - Display total event and data reduction count. (How many and what percent of events were ignored.) (#538) (@hitenkoku) -- New logo. (@YamatoSecurity) +- New logo. (#536) (@YamatoSecurity) - Display total evtx file size. (#540) (@hitenkoku) +- Changed logo color. (#537) (@hitenkoku) **Bug Fixes:** diff --git a/rules b/rules index 7a1cb7c0..33126392 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 7a1cb7c0cf4a055abc3b6241c4f5a47c6ae82a9d +Subproject commit 3312639216142abe4f4fffb7e28d92ade7cf6ba0 diff --git a/screenshots/Hayabusa-Startup.png b/screenshots/Hayabusa-Startup.png index fcdfdd38..cf0233f4 100644 Binary files a/screenshots/Hayabusa-Startup.png and b/screenshots/Hayabusa-Startup.png differ diff --git a/src/afterfact.rs b/src/afterfact.rs index 1dce6f30..e94129fd 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -2,6 +2,7 @@ use crate::detections::configs; use crate::detections::print; use crate::detections::print::AlertMessage; use crate::detections::utils; +use crate::detections::utils::write_color_buffer; use chrono::{DateTime, Local, TimeZone, Utc}; use csv::QuoteStyle; use hashbrown::HashMap; @@ -67,11 +68,7 @@ pub fn set_output_color() -> HashMap { } if read_result.is_err() { // color情報がない場合は通常の白色の出力が出てくるのみで動作への影響を与えない為warnとして処理する - AlertMessage::warn( - &mut BufWriter::new(std::io::stderr().lock()), - read_result.as_ref().unwrap_err(), - ) - .ok(); + AlertMessage::warn(read_result.as_ref().unwrap_err()).ok(); return color_map; } read_result.unwrap().into_iter().for_each(|line| { @@ -82,10 +79,10 @@ pub fn set_output_color() -> HashMap { 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(",")) - ) + AlertMessage::warn(&format!( + "Failed hex convert in level_color.txt. Color output is disabled. Input Line: {}", + line.join(",") + )) .ok(); return; } @@ -93,7 +90,10 @@ pub fn set_output_color() -> HashMap { if level.is_empty() || color_code.len() < 3 { return; } - color_map.insert(level.to_lowercase(), Color::Rgb(color_code[0], color_code[1], color_code[2])); + color_map.insert( + level.to_lowercase(), + Color::Rgb(color_code[0], color_code[1], color_code[2]), + ); }); color_map } @@ -115,23 +115,22 @@ fn _print_timeline_hist(timestamps: Vec, length: usize, side_margin_size: u let buf_wtr = BufferWriter::stdout(ColorChoice::Always); let mut wtr = buf_wtr.buffer(); wtr.set_color(ColorSpec::new().set_fg(None)).ok(); + if timestamps.len() < 5 { - write!( + writeln!( wtr, "Event Frequency Timeline could not be displayed as there needs to be more than 5 events.", ) .ok(); - writeln!(wtr).ok(); buf_wtr.print(&wtr).ok(); return; } let title = "Event Frequency Timeline"; let header_row_space = (length - title.len()) / 2; - writeln!(wtr).ok(); - write!(wtr, "{}", " ".repeat(header_row_space)).ok(); - writeln!(wtr, "{}", title).ok(); - writeln!(wtr).ok(); + println!(); + writeln!(wtr, "{}{}", " ".repeat(header_row_space), title).ok(); + println!(); let timestamp_marker_max = if timestamps.len() < 2 { 0 @@ -162,11 +161,7 @@ fn _print_timeline_hist(timestamps: Vec, length: usize, side_margin_size: u pub fn after_fact(all_record_cnt: usize) { let fn_emit_csv_err = |err: Box| { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("Failed to write CSV. {}", err), - ) - .ok(); + AlertMessage::alert(&format!("Failed to write CSV. {}", err)).ok(); process::exit(1); }; @@ -177,11 +172,7 @@ pub fn after_fact(all_record_cnt: usize) { match File::create(csv_path) { Ok(file) => Box::new(BufWriter::new(file)), Err(err) => { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("Failed to open file. {}", err), - ) - .ok(); + AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); process::exit(1); } } @@ -372,10 +363,6 @@ fn _print_unique_results( tail_word: String, color_map: &HashMap, ) { - let buf_wtr = BufferWriter::stdout(ColorChoice::Always); - let mut wtr = buf_wtr.buffer(); - wtr.set_color(ColorSpec::new().set_fg(None)).ok(); - let levels = Vec::from([ "critical", "high", @@ -389,12 +376,15 @@ fn _print_unique_results( counts_by_level.reverse(); // output total results - writeln!( - wtr, - "{} {}: {}", - head_word, - tail_word, - counts_by_level.iter().sum::() + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &format!( + "{} {}: {}", + head_word, + tail_word, + counts_by_level.iter().sum::() + ), ) .ok(); @@ -403,12 +393,13 @@ fn _print_unique_results( "{} {} {}: {}", head_word, level_name, tail_word, counts_by_level[i] ); - - wtr.set_color(ColorSpec::new().set_fg(_get_output_color(color_map, level_name))) - .ok(); - writeln!(wtr, "{}", output_raw_str).ok(); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + _get_output_color(color_map, level_name), + &output_raw_str, + ) + .ok(); } - buf_wtr.print(&wtr).ok(); } fn format_time(time: &DateTime) -> String { diff --git a/src/detections/configs.rs b/src/detections/configs.rs index ab4287c7..a9f1cbed 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -8,7 +8,6 @@ use hashbrown::HashMap; use hashbrown::HashSet; use lazy_static::lazy_static; use regex::Regex; -use std::io::BufWriter; use std::sync::RwLock; lazy_static! { pub static ref CONFIG: RwLock = RwLock::new(ConfigReader::new()); @@ -153,11 +152,7 @@ fn load_target_ids(path: &str) -> TargetEventIds { let mut ret = TargetEventIds::new(); let lines = utils::read_txt(path); // ファイルが存在しなければエラーとする if lines.is_err() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - lines.as_ref().unwrap_err(), - ) - .ok(); + AlertMessage::alert(lines.as_ref().unwrap_err()).ok(); return ret; } @@ -195,7 +190,6 @@ impl TargetEventTime { Ok(dt) => Some(dt.with_timezone(&Utc)), Err(_) => { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "start-timeline field: the timestamp format is not correct.", ) .ok(); @@ -213,7 +207,6 @@ impl TargetEventTime { Ok(dt) => Some(dt.with_timezone(&Utc)), Err(_) => { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "end-timeline field: the timestamp format is not correct.", ) .ok(); @@ -296,11 +289,7 @@ fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig { // eventkey_aliasが読み込めなかったらエラーで終了とする。 let read_result = utils::read_csv(path); if read_result.is_err() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - read_result.as_ref().unwrap_err(), - ) - .ok(); + AlertMessage::alert(read_result.as_ref().unwrap_err()).ok(); return config; } @@ -332,11 +321,7 @@ fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig { pub fn load_pivot_keywords(path: &str) { let read_result = utils::read_txt(path); if read_result.is_err() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - read_result.as_ref().unwrap_err(), - ) - .ok(); + AlertMessage::alert(read_result.as_ref().unwrap_err()).ok(); } read_result.unwrap().into_iter().for_each(|line| { @@ -406,11 +391,7 @@ fn load_eventcode_info(path: &str) -> EventInfoConfig { let mut config = EventInfoConfig::new(); let read_result = utils::read_csv(path); if read_result.is_err() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - read_result.as_ref().unwrap_err(), - ) - .ok(); + AlertMessage::alert(read_result.as_ref().unwrap_err()).ok(); return config; } diff --git a/src/detections/detection.rs b/src/detections/detection.rs index b6ec1eab..8f282ebd 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -20,7 +20,6 @@ use hashbrown; use hashbrown::HashMap; use serde_json::Value; use std::fmt::Write; -use std::io::BufWriter; use std::sync::Arc; use tokio::{runtime::Runtime, spawn, task::JoinHandle}; @@ -69,7 +68,7 @@ impl Detection { if result_readdir.is_err() { let errmsg = format!("{}", result_readdir.unwrap_err()); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg).ok(); + AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -91,10 +90,10 @@ impl Detection { let errmsg_body = format!("Failed to parse rule file. (FilePath : {})", rule.rulepath); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::warn(&mut std::io::stdout().lock(), &errmsg_body).ok(); + AlertMessage::warn(&errmsg_body).ok(); err_msgs.iter().for_each(|err_msg| { - AlertMessage::warn(&mut std::io::stdout().lock(), err_msg).ok(); + AlertMessage::warn(err_msg).ok(); }); } if !*QUIET_ERRORS_FLAG { diff --git a/src/detections/print.rs b/src/detections/print.rs index 85d2e0e2..2eb5b35a 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -2,6 +2,7 @@ extern crate lazy_static; use crate::detections::configs; use crate::detections::utils; use crate::detections::utils::get_serde_number_to_string; +use crate::detections::utils::write_color_buffer; use chrono::{DateTime, Local, TimeZone, Utc}; use hashbrown::HashMap; use lazy_static::lazy_static; @@ -15,6 +16,7 @@ use std::io::BufWriter; use std::io::{self, Write}; use std::path::Path; use std::sync::Mutex; +use termcolor::{BufferWriter, ColorChoice}; #[derive(Debug)] pub struct Message { @@ -108,11 +110,7 @@ impl Message { } let read_result = utils::read_csv(path); if read_result.is_err() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - read_result.as_ref().unwrap_err(), - ) - .ok(); + AlertMessage::alert(read_result.as_ref().unwrap_err()).ok(); return HashMap::default(); } read_result.unwrap().into_iter().for_each(|line| { @@ -265,13 +263,21 @@ impl AlertMessage { } /// ERRORメッセージを表示する関数 - pub fn alert(w: &mut W, contents: &str) -> io::Result<()> { - writeln!(w, "[ERROR] {}", contents) + pub fn alert(contents: &str) -> io::Result<()> { + write_color_buffer( + BufferWriter::stderr(ColorChoice::Always), + None, + &format!("[ERROR] {}", contents), + ) } /// WARNメッセージを表示する関数 - pub fn warn(w: &mut W, contents: &str) -> io::Result<()> { - writeln!(w, "[WARN] {}", contents) + pub fn warn(contents: &str) -> io::Result<()> { + write_color_buffer( + BufferWriter::stderr(ColorChoice::Always), + None, + &format!("[WARN] {}", contents), + ) } } @@ -281,7 +287,6 @@ mod tests { use crate::detections::print::{AlertMessage, Message}; use hashbrown::HashMap; use serde_json::Value; - use std::io::BufWriter; #[test] fn test_create_and_append_message() { @@ -422,15 +427,13 @@ mod tests { #[test] fn test_error_message() { let input = "TEST!"; - AlertMessage::alert(&mut BufWriter::new(std::io::stdout().lock()), input) - .expect("[ERROR] TEST!"); + AlertMessage::alert(input).expect("[ERROR] TEST!"); } #[test] fn test_warn_message() { let input = "TESTWarn!"; - AlertMessage::warn(&mut BufWriter::new(std::io::stdout().lock()), input) - .expect("[WARN] TESTWarn!"); + AlertMessage::warn(input).expect("[WARN] TESTWarn!"); } #[test] diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs index aa03403e..e7bb9ecc 100644 --- a/src/detections/rule/count.rs +++ b/src/detections/rule/count.rs @@ -8,7 +8,6 @@ use crate::detections::rule::RuleNode; use chrono::{DateTime, TimeZone, Utc}; use hashbrown::HashMap; use serde_json::Value; -use std::io::BufWriter; use std::num::ParseIntError; use std::path::Path; @@ -88,7 +87,7 @@ fn get_alias_value_in_record( ), }; if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg).ok(); + AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -190,7 +189,7 @@ impl TimeFrameInfo { } else { let errmsg = format!("Timeframe is invalid. Input value:{}", value); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg).ok(); + AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -226,7 +225,7 @@ pub fn get_sec_timeframe(rule: &RuleNode) -> Option { Err(err) => { let errmsg = format!("Timeframe number is invalid. timeframe. {}", err); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg).ok(); + AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 7fe6603c..bfad4c46 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -3,6 +3,7 @@ extern crate csv; extern crate regex; use crate::detections::configs; +use termcolor::Color; use tokio::runtime::Builder; use tokio::runtime::Runtime; @@ -12,11 +13,13 @@ use regex::Regex; use serde_json::Value; use std::cmp::Ordering; use std::fs::File; +use std::io; use std::io::prelude::*; use std::io::{BufRead, BufReader}; use std::str; use std::string::String; use std::vec; +use termcolor::{BufferWriter, ColorSpec, WriteColor}; use super::detection::EvtxRecordInfo; @@ -239,6 +242,20 @@ pub fn create_rec_info(data: Value, path: String, keys: &[String]) -> EvtxRecord } } +/** + * 標準出力のカラー出力設定を指定した値に変更し画面出力を行う関数 + */ +pub fn write_color_buffer( + wtr: BufferWriter, + color: Option, + output_str: &str, +) -> io::Result<()> { + let mut buf = wtr.buffer(); + buf.set_color(ColorSpec::new().set_fg(color)).ok(); + writeln!(buf, "{}", output_str).ok(); + wtr.print(&buf) +} + /** * CSVのrecord infoカラムに出力する文字列を作る */ diff --git a/src/filter.rs b/src/filter.rs index 766f2d35..79ffc6b4 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -5,7 +5,6 @@ use crate::detections::print::QUIET_ERRORS_FLAG; use hashbrown::HashSet; use regex::Regex; use std::fs::File; -use std::io::BufWriter; use std::io::{BufRead, BufReader}; #[derive(Debug)] @@ -55,11 +54,7 @@ impl RuleExclude { let f = File::open(filename); if f.is_err() { if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::warn( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("{} does not exist", filename), - ) - .ok(); + AlertMessage::warn(&format!("{} does not exist", filename)).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK diff --git a/src/main.rs b/src/main.rs index 9d9d9492..98e72f5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,12 +19,12 @@ use hayabusa::detections::print::{ QUIET_ERRORS_FLAG, STATISTICS_FLAG, }; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; -use hayabusa::filter; use hayabusa::omikuji::Omikuji; use hayabusa::options::level_tuning::LevelTuning; use hayabusa::yaml::ParseYaml; use hayabusa::{afterfact::after_fact, detections::utils}; use hayabusa::{detections::configs, timeline::timelines::Timeline}; +use hayabusa::{detections::utils::write_color_buffer, filter}; use hhmmss::Hhmmss; use pbr::ProgressBar; use serde_json::Value; @@ -42,6 +42,7 @@ use std::{ path::PathBuf, vec, }; +use termcolor::{BufferWriter, Color, ColorChoice}; use tokio::runtime::Runtime; use tokio::spawn; use tokio::task::JoinHandle; @@ -88,7 +89,12 @@ impl App { if std::env::args().len() == 1 { self.output_logo(); println!(); - println!("{}", configs::CONFIG.read().unwrap().args.usage()); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + configs::CONFIG.read().unwrap().args.usage(), + ) + .ok(); println!(); return; } @@ -105,7 +111,6 @@ impl App { if !self.is_matched_architecture_and_binary() { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "The hayabusa version you ran does not match your PC architecture.\nPlease use the correct architecture. (Binary ending in -x64.exe for 64-bit and -x86.exe for 32-bit.)", ) .ok(); @@ -122,15 +127,16 @@ impl App { match self.update_rules() { Ok(output) => { if output != "You currently have the latest rules." { - println!("Rules updated successfully."); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + "Rules updated successfully.", + ) + .ok(); } } Err(e) => { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("Failed to update rules. {:?} ", e), - ) - .ok(); + AlertMessage::alert(&format!("Failed to update rules. {:?} ", e)).ok(); } } println!(); @@ -139,7 +145,6 @@ impl App { if !Path::new("./config").exists() { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "Hayabusa could not find the config directory.\nPlease run it from the Hayabusa root directory.\nExample: ./hayabusa-1.0.0-windows-x64.exe" ) .ok(); @@ -150,25 +155,19 @@ impl App { for (key, _) in PIVOT_KEYWORD.read().unwrap().iter() { let keywords_file_name = csv_path.to_owned() + "-" + key + ".txt"; if Path::new(&keywords_file_name).exists() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!( - " The file {} already exists. Please specify a different filename.", - &keywords_file_name - ), - ) + AlertMessage::alert(&format!( + " The file {} already exists. Please specify a different filename.", + &keywords_file_name + )) .ok(); return; } } if Path::new(csv_path).exists() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!( - " The file {} already exists. Please specify a different filename.", - csv_path - ), - ) + AlertMessage::alert(&format!( + " The file {} already exists. Please specify a different filename.", + csv_path + )) .ok(); return; } @@ -180,11 +179,21 @@ impl App { } if *STATISTICS_FLAG { - println!("Generating Event ID Statistics"); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + "Generating Event ID Statistics", + ) + .ok(); println!(); } if *LOGONSUMMARY_FLAG { - println!("Generating Logons Summary"); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + "Generating Logons Summary", + ) + .ok(); println!(); } if configs::CONFIG @@ -209,7 +218,6 @@ impl App { .starts_with('.') { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "--filepath only accepts .evtx files. Hidden files are ignored.", ) .ok(); @@ -219,11 +227,7 @@ impl App { } else if let Some(directory) = configs::CONFIG.read().unwrap().args.value_of("directory") { let evtx_files = self.collect_evtxfiles(directory); if evtx_files.is_empty() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - "No .evtx files were found.", - ) - .ok(); + AlertMessage::alert("No .evtx files were found.").ok(); return; } self.analysis_files(evtx_files, &time_filter); @@ -262,11 +266,10 @@ impl App { .value_of("rules") .unwrap_or("rules"), ) { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &err).ok(); + AlertMessage::alert(&err).ok(); } } else { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "Need rule_levels.txt file to use --level-tuning option [default: ./rules/config/level_tuning.txt]", ) .ok(); @@ -277,7 +280,12 @@ impl App { let analysis_end_time: DateTime = Local::now(); let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time); println!(); - println!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &format!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()), + ) + .ok(); println!(); // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが9となるのでエラーログファイル自体が生成されない。 @@ -315,7 +323,7 @@ impl App { for (key, _) in PIVOT_KEYWORD.read().unwrap().iter() { output += &(pivot_file.to_owned() + "-" + key + ".txt" + "\n"); } - println!("{}", output); + write_color_buffer(BufferWriter::stdout(ColorChoice::Always), None, &output).ok(); } else { //標準出力の場合 let mut output = "The following pivot keywords were found:\n".to_string(); @@ -335,18 +343,16 @@ impl App { output += "\n"; } - print!("{}", output); + write_color_buffer(BufferWriter::stdout(ColorChoice::Always), None, &output).ok(); } } } #[cfg(not(target_os = "windows"))] fn collect_liveanalysis_files(&self) -> Option> { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - "-l / --liveanalysis needs to be run as Administrator on Windows.\r\n", - ) - .ok(); + AlertMessage::alert("-l / --liveanalysis needs to be run as Administrator on Windows.") + .ok(); + println!(); None } @@ -357,20 +363,14 @@ impl App { let evtx_files = self.collect_evtxfiles(&[log_dir, "System32\\winevt\\Logs".to_string()].join("/")); if evtx_files.is_empty() { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - "No .evtx files were found.", - ) - .ok(); + AlertMessage::alert("No .evtx files were found.").ok(); return None; } Some(evtx_files) } else { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - "-l / --liveanalysis needs to be run as Administrator on Windows.\r\n", - ) - .ok(); + AlertMessage::alert("-l / --liveanalysis needs to be run as Administrator on Windows.") + .ok(); + println!(); None } } @@ -380,7 +380,7 @@ impl App { if entries.is_err() { let errmsg = format!("{}", entries.unwrap_err()); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg).ok(); + AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -426,11 +426,7 @@ impl App { match fs::read_to_string("./contributors.txt") { Ok(contents) => println!("{}", contents), Err(err) => { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("{}", err), - ) - .ok(); + AlertMessage::alert(&format!("{}", err)).ok(); } } } @@ -443,7 +439,12 @@ impl App { .value_of("min-level") .unwrap_or("informational") .to_uppercase(); - println!("Analyzing event files: {:?}", evtx_files.len()); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &format!("Analyzing event files: {:?}", evtx_files.len()), + ) + .ok(); let mut total_file_size = ByteSize::b(0); for file_path in &evtx_files { @@ -461,7 +462,6 @@ impl App { if rule_files.is_empty() { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "No rules were loaded. Please download the latest rules with the --update-rules option.\r\n", ) .ok(); @@ -524,8 +524,7 @@ impl App { record_result.unwrap_err() ); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg) - .ok(); + AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -654,7 +653,17 @@ impl App { fn output_logo(&self) { let fp = &"art/logo.txt".to_string(); let content = fs::read_to_string(fp).unwrap_or_default(); - println!("{}", content); + let output_color = if configs::CONFIG.read().unwrap().args.is_present("no-color") { + None + } else { + Some(Color::Green) + }; + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + output_color, + &content, + ) + .ok(); } /// output easter egg arts @@ -669,7 +678,7 @@ impl App { None => {} Some(path) => { let content = fs::read_to_string(path).unwrap_or_default(); - println!("{}", content); + write_color_buffer(BufferWriter::stdout(ColorChoice::Always), None, &content).ok(); } } } @@ -682,9 +691,12 @@ impl App { let hayabusa_repo = Repository::open(Path::new(".")); let hayabusa_rule_repo = Repository::open(Path::new("rules")); if hayabusa_repo.is_err() && hayabusa_rule_repo.is_err() { - println!( - "Attempting to git clone the hayabusa-rules repository into the rules folder." - ); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + "Attempting to git clone the hayabusa-rules repository into the rules folder.", + ) + .ok(); // execution git clone of hayabusa-rules repository when failed open hayabusa repository. result = self.clone_rules(); } else if hayabusa_rule_repo.is_ok() { @@ -712,11 +724,7 @@ impl App { submodule.update(true, None)?; let submodule_repo = submodule.open()?; if let Err(e) = self.pull_repository(&submodule_repo) { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("Failed submodule update. {}", e), - ) - .ok(); + AlertMessage::alert(&format!("Failed submodule update. {}", e)).ok(); is_success_submodule_update = false; } } @@ -753,11 +761,7 @@ impl App { .find_remote("origin")? .fetch(&["main"], None, None) .map_err(|e| { - AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), - &format!("Failed git fetch to rules folder. {}", e), - ) - .ok(); + AlertMessage::alert(&format!("Failed git fetch to rules folder. {}", e)).ok(); }) { Ok(it) => it, Err(_err) => return Err(git2::Error::from_str(&String::default())), @@ -775,7 +779,6 @@ impl App { Ok("Finished fast forward merge.".to_string()) } else if analysis.0.is_normal() { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), "update-rules option is git Fast-Forward merge only. please check your rules folder." , ).ok(); @@ -797,7 +800,6 @@ impl App { } Err(e) => { AlertMessage::alert( - &mut BufWriter::new(std::io::stderr().lock()), &format!( "Failed to git clone into the rules folder. Please rename your rules folder name. {}", e @@ -868,10 +870,15 @@ impl App { *update_count_by_rule_type .entry(tmp[3].to_string()) .or_insert(0b0) += 1; - println!( - "[Updated] {} (Modified: {} | Path: {})", - tmp[0], tmp[1], tmp[2] - ); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &format!( + "[Updated] {} (Modified: {} | Path: {})", + tmp[0], tmp[1], tmp[2] + ), + ) + .ok(); } println!(); for (key, value) in &update_count_by_rule_type { @@ -880,7 +887,12 @@ impl App { if !&update_count_by_rule_type.is_empty() { Ok("Rule updated".to_string()) } else { - println!("You currently have the latest rules."); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + "You currently have the latest rules.", + ) + .ok(); Ok("You currently have the latest rules.".to_string()) } } diff --git a/src/options/level_tuning.rs b/src/options/level_tuning.rs index c8cf6283..ed3e9573 100644 --- a/src/options/level_tuning.rs +++ b/src/options/level_tuning.rs @@ -1,9 +1,12 @@ +use crate::detections::utils::write_color_buffer; use crate::detections::{configs, utils}; use crate::filter::RuleExclude; use crate::yaml::ParseYaml; use std::collections::HashMap; use std::fs::{self, File}; use std::io::Write; +use termcolor::{BufferWriter, ColorChoice}; + pub struct LevelTuning {} impl LevelTuning { @@ -55,7 +58,12 @@ impl LevelTuning { // Convert rule files for (path, rule) in rulefile_loader.files { if let Some(new_level) = tuning_map.get(rule["id"].as_str().unwrap()) { - println!("path: {}", path); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &format!("path: {}", path), + ) + .ok(); let mut content = match fs::read_to_string(&path) { Ok(_content) => _content, Err(e) => return Result::Err(e.to_string()), @@ -85,11 +93,16 @@ impl LevelTuning { file.write_all(content.as_bytes()).unwrap(); file.flush().unwrap(); - println!( - "level: {} -> {}", - rule["level"].as_str().unwrap(), - new_level - ); + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &format!( + "level: {} -> {}", + rule["level"].as_str().unwrap(), + new_level + ), + ) + .ok(); } } Result::Ok(()) diff --git a/src/yaml.rs b/src/yaml.rs index dc5ca046..b56fb6a0 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -10,7 +10,6 @@ use hashbrown::HashMap; use std::ffi::OsStr; use std::fs; use std::io; -use std::io::BufWriter; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use yaml_rust::Yaml; @@ -65,7 +64,7 @@ impl ParseYaml { path.as_ref().to_path_buf().display(), ); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::alert(&mut BufWriter::new(std::io::stderr().lock()), &errmsg)?; + AlertMessage::alert(&errmsg)?; } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -97,7 +96,7 @@ impl ParseYaml { read_content.unwrap_err() ); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::warn(&mut BufWriter::new(std::io::stderr().lock()), &errmsg)?; + AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -118,7 +117,7 @@ impl ParseYaml { yaml_contents.unwrap_err() ); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::warn(&mut BufWriter::new(std::io::stderr().lock()), &errmsg)?; + AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -170,7 +169,7 @@ impl ParseYaml { read_content.unwrap_err() ); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::warn(&mut BufWriter::new(std::io::stderr().lock()), &errmsg)?; + AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK @@ -191,7 +190,7 @@ impl ParseYaml { yaml_contents.unwrap_err() ); if configs::CONFIG.read().unwrap().args.is_present("verbose") { - AlertMessage::warn(&mut BufWriter::new(std::io::stderr().lock()), &errmsg)?; + AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { ERROR_LOG_STACK