diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 06f60e3d..908f1553 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -140,7 +140,7 @@ impl TargetEventTime { Err(err) => { AlertMessage::alert( &mut std::io::stderr().lock(), - format!("starttimeline field: {}", err), + format!("start-timeline field: {}", err), ) .ok(); None @@ -157,7 +157,7 @@ impl TargetEventTime { Err(err) => { AlertMessage::alert( &mut std::io::stderr().lock(), - format!("endtimeline field: {}", err), + format!("end-timeline field: {}", err), ) .ok(); None diff --git a/src/detections/detection.rs b/src/detections/detection.rs index d63a73e8..38a4c639 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1,6 +1,7 @@ extern crate csv; use crate::detections::print::AlertMessage; +use crate::detections::print::ERROR_LOG_PATH; use crate::detections::print::MESSAGES; use crate::detections::rule; use crate::detections::rule::AggResult; @@ -11,9 +12,10 @@ use crate::yaml::ParseYaml; use hashbrown; use serde_json::Value; use std::collections::HashMap; -use tokio::{runtime::Runtime, spawn, task::JoinHandle}; - +use std::fs::OpenOptions; +use std::io::BufWriter; use std::sync::Arc; +use tokio::{runtime::Runtime, spawn, task::JoinHandle}; const DIRPATH_RULES: &str = "rules"; @@ -58,7 +60,12 @@ impl Detection { rulefile_loader.read_dir(rulespath.unwrap_or(DIRPATH_RULES), &level, exclude_ids); if result_readdir.is_err() { AlertMessage::alert( - &mut std::io::stderr().lock(), + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), format!("{}", result_readdir.unwrap_err()), ) .ok(); diff --git a/src/detections/print.rs b/src/detections/print.rs index 594aa504..5ee74dd3 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -2,13 +2,18 @@ extern crate lazy_static; use crate::detections::configs; use crate::detections::utils; use crate::detections::utils::get_serde_number_to_string; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{DateTime, Local, TimeZone, Utc}; 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; +use std::io::BufWriter; use std::io::{self, Write}; +use std::path::Path; use std::sync::Mutex; #[derive(Debug)] @@ -32,6 +37,15 @@ pub struct AlertMessage {} lazy_static! { pub static ref MESSAGES: Mutex = Mutex::new(Message::new()); pub static ref ALIASREGEX: Regex = Regex::new(r"%[a-zA-Z0-9-_]+%").unwrap(); + pub static ref ERROR_LOG_PATH: String = format!( + "./logs/errorlog-{}.log", + Local::now().format("%Y%m%d_%H%M%S") + ); + pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG + .read() + .unwrap() + .args + .is_present("quiet-errors"); } impl Message { @@ -180,11 +194,56 @@ impl Message { } impl AlertMessage { - pub fn alert(w: &mut W, contents: String) -> io::Result<()> { - writeln!(w, "[ERROR] {}", contents) + ///対象のディレクトリが存在することを確認後、最初の定型文を追加して、ファイルのbufwriterを返す関数 + pub fn create_error_log(path_str: String) { + let path = Path::new(&path_str); + if !path.parent().unwrap().exists() { + create_dir(path.parent().unwrap()).ok(); + } + // 1行目は必ず実行したコマンド情報を入れておく。 + let mut ret = BufWriter::new(File::create(path).unwrap()); + + ret.write( + format!( + "user input: {:?}\n", + format_args!( + "{}", + env::args() + .map(|arg| arg) + .collect::>() + .join(" ") + ) + ) + .as_bytes(), + ) + .unwrap(); + ret.flush().ok(); } + + /// ERRORメッセージを表示する関数 + pub fn alert(w: &mut W, contents: String) -> io::Result<()> { + if !*QUIET_ERRORS_FLAG { + writeln!(w, "[ERROR] {}", contents) + } else { + Ok(()) + } + } + + /// WARNメッセージを表示する関数 pub fn warn(w: &mut W, contents: String) -> io::Result<()> { - writeln!(w, "[WARN] {}", contents) + if !*QUIET_ERRORS_FLAG { + writeln!(w, "[WARN] {}", contents) + } else { + Ok(()) + } + } + + /// エラーログへのERRORメッセージの出力数を確認して、0であったらファイルを削除する。1以上あればエラーを書き出した旨を標準出力に表示する + pub fn output_error_log_exist() { + println!( + "Generated error was output to {}. Please see the file for details.", + ERROR_LOG_PATH.to_string() + ); } } @@ -314,7 +373,7 @@ mod tests { let input = "TESTWarn!"; let stdout = std::io::stdout(); let mut stdout = stdout.lock(); - AlertMessage::alert(&mut stdout, input.to_string()).expect("[WARN] TESTWarn!"); + AlertMessage::warn(&mut stdout, input.to_string()).expect("[WARN] TESTWarn!"); } #[test] diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs index c675589a..b150a916 100644 --- a/src/detections/rule/count.rs +++ b/src/detections/rule/count.rs @@ -1,4 +1,5 @@ use crate::detections::print::AlertMessage; +use crate::detections::print::ERROR_LOG_PATH; use crate::detections::rule::AggResult; use crate::detections::rule::AggregationParseInfo; use crate::detections::rule::Message; @@ -6,6 +7,8 @@ use crate::detections::rule::RuleNode; use chrono::{DateTime, TimeZone, Utc}; use hashbrown::HashMap; use serde_json::Value; +use std::fs::OpenOptions; +use std::io::BufWriter; use std::num::ParseIntError; use std::path::Path; @@ -183,7 +186,12 @@ impl TimeFrameInfo { tnum.retain(|c| c != 'd'); } else { AlertMessage::alert( - &mut std::io::stderr().lock(), + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), format!("Timeframe is invalid. Input value:{}", value), ) .ok(); @@ -215,7 +223,12 @@ pub fn get_sec_timeframe(timeframe: &Option) -> Option { } Err(err) => { AlertMessage::alert( - &mut std::io::stderr().lock(), + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), format!("Timeframe number is invalid. timeframe.{}", err), ) .ok(); diff --git a/src/main.rs b/src/main.rs index 50abee7d..a591dff3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use chrono::{DateTime, Local}; use evtx::{EvtxParser, ParserSettings}; use hayabusa::detections::detection::{self, EvtxRecordInfo}; use hayabusa::detections::print::AlertMessage; +use hayabusa::detections::print::ERROR_LOG_PATH; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::filter; use hayabusa::omikuji::Omikuji; @@ -16,6 +17,9 @@ use pbr::ProgressBar; use serde_json::Value; use std::collections::{HashMap, HashSet}; use std::fmt::Display; +use std::fs::OpenOptions; +use std::io::BufWriter; +use std::path::Path; use std::sync::Arc; use std::{ fs::{self, File}, @@ -66,6 +70,19 @@ impl App { ); return; } + if let Some(csv_path) = configs::CONFIG.read().unwrap().args.value_of("output") { + if Path::new(csv_path).exists() { + AlertMessage::alert( + &mut std::io::stderr().lock(), + format!( + " The file {} already exists. Please specify a different filename.", + csv_path + ), + ) + .ok(); + return; + } + } if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") { if !filepath.ends_with(".evtx") { AlertMessage::alert( @@ -100,14 +117,22 @@ impl App { let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time); println!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()); println!(""); + AlertMessage::output_error_log_exist(); } fn collect_evtxfiles(&self, dirpath: &str) -> Vec { let entries = fs::read_dir(dirpath); if entries.is_err() { - let stderr = std::io::stderr(); - let mut stderr = stderr.lock(); - AlertMessage::alert(&mut stderr, format!("{}", entries.unwrap_err())).ok(); + AlertMessage::alert( + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), + format!("{}", entries.unwrap_err()), + ) + .ok(); return vec![]; } @@ -206,7 +231,16 @@ impl App { evtx_filepath, record_result.unwrap_err() ); - AlertMessage::alert(&mut std::io::stderr().lock(), errmsg).ok(); + AlertMessage::alert( + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), + errmsg, + ) + .ok(); continue; } diff --git a/src/yaml.rs b/src/yaml.rs index 4411afb2..9c389714 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -3,11 +3,14 @@ extern crate yaml_rust; use crate::detections::configs; use crate::detections::print::AlertMessage; +use crate::detections::print::ERROR_LOG_PATH; use crate::filter::RuleExclude; use std::collections::HashMap; use std::ffi::OsStr; use std::fs; +use std::fs::OpenOptions; use std::io; +use std::io::BufWriter; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use yaml_rust::Yaml; @@ -72,7 +75,12 @@ impl ParseYaml { let read_content = self.read_file(path); if read_content.is_err() { AlertMessage::warn( - &mut std::io::stdout().lock(), + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), format!( "fail to read file: {}\n{} ", entry.path().display(), @@ -87,7 +95,12 @@ impl ParseYaml { let yaml_contents = YamlLoader::load_from_str(&read_content.unwrap()); if yaml_contents.is_err() { AlertMessage::warn( - &mut std::io::stdout().lock(), + &mut BufWriter::new( + OpenOptions::new() + .append(true) + .open(ERROR_LOG_PATH.to_string()) + .unwrap(), + ), format!( "Failed to parse yml: {}\n{} ", entry.path().display(),