Merge pull request #310 from Yamato-Security/feature/output_errorlog#301
Feature/output errorlog#301
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -773,6 +773,7 @@ dependencies = [
|
||||
"hashbrown",
|
||||
"hhmmss",
|
||||
"lazy_static",
|
||||
"linecount",
|
||||
"linked-hash-map",
|
||||
"mopa",
|
||||
"num_cpus",
|
||||
@@ -1015,6 +1016,12 @@ version = "0.2.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
|
||||
|
||||
[[package]]
|
||||
name = "linecount"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5d5a4a243b9cf052d37af99679cc93b08a791f444a4a1b21bb4efcaf01847d8"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.3"
|
||||
|
||||
@@ -29,6 +29,7 @@ dotenv = "0.15.0"
|
||||
hhmmss = "*"
|
||||
pbr = "*"
|
||||
hashbrown = "0.11.2"
|
||||
linecount = "*"
|
||||
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "x86_64-w64-mingw32-gcc"
|
||||
|
||||
@@ -189,6 +189,8 @@ where
|
||||
mod tests {
|
||||
use crate::afterfact::emit_csv;
|
||||
use crate::detections::print;
|
||||
use crate::detections::print::AlertMessage;
|
||||
use crate::detections::print::ERROR_LOG_PATH;
|
||||
use chrono::{Local, TimeZone, Utc};
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
@@ -203,6 +205,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn test_emit_csv_output() {
|
||||
AlertMessage::create_error_log(ERROR_LOG_PATH.to_string());
|
||||
let testfilepath: &str = "test.evtx";
|
||||
let testrulepath: &str = "test-rule.yml";
|
||||
let test_title = "test_title";
|
||||
|
||||
@@ -68,6 +68,7 @@ fn build_app<'a>() -> ArgMatches<'a> {
|
||||
-t --thread-number=[NUMBER] 'Thread number (default: optimal number for performance)'
|
||||
-s --statistics 'Prints statistics of event IDs'
|
||||
-q --quiet 'Quiet mode. Do not display the launch banner'
|
||||
-Q --quiet-errors 'Quiet errors mode. Do not display errors or save error logs'
|
||||
--contributors 'Prints the list of contributors'";
|
||||
App::new(&program)
|
||||
.about("Hayabusa: Aiming to be the world's greatest Windows event log analysis tool!")
|
||||
@@ -140,7 +141,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 +158,7 @@ impl TargetEventTime {
|
||||
Err(err) => {
|
||||
AlertMessage::alert(
|
||||
&mut std::io::stderr().lock(),
|
||||
format!("endtimeline field: {}", err),
|
||||
format!("end-timeline field: {}", err),
|
||||
)
|
||||
.ok();
|
||||
None
|
||||
|
||||
@@ -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,6 +12,8 @@ use crate::yaml::ParseYaml;
|
||||
use hashbrown;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufWriter;
|
||||
use tokio::{runtime::Runtime, spawn, task::JoinHandle};
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -58,7 +61,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();
|
||||
|
||||
@@ -2,13 +2,20 @@ 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 linecount::count_lines;
|
||||
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::remove_file;
|
||||
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 +39,15 @@ pub struct AlertMessage {}
|
||||
lazy_static! {
|
||||
pub static ref MESSAGES: Mutex<Message> = 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 +196,68 @@ impl Message {
|
||||
}
|
||||
|
||||
impl AlertMessage {
|
||||
pub fn alert<W: Write>(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::<Vec<String>>()
|
||||
.join(" ")
|
||||
)
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
ret.flush().ok();
|
||||
}
|
||||
|
||||
/// ERRORメッセージを表示する関数。error_log_flagでfalseの場合は外部へのエラーログの書き込みは行わずに指定されたwを用いた出力のみ行う。trueの場合はwを用いた出力を行わずにエラーログへの出力を行う
|
||||
pub fn alert<W: Write>(w: &mut W, contents: String) -> io::Result<()> {
|
||||
if *QUIET_ERRORS_FLAG {
|
||||
writeln!(w, "[ERROR] {}", contents)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// WARNメッセージを表示する関数
|
||||
pub fn warn<W: Write>(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() {
|
||||
let error_log_path_str = ERROR_LOG_PATH.to_string();
|
||||
// 1行しかなかった場合は最初に書いたコマンド情報のみと判断して削除する
|
||||
if count_lines(File::open(&error_log_path_str).unwrap()).unwrap() == 1 {
|
||||
if remove_file(&error_log_path_str).is_err() {
|
||||
AlertMessage::alert(
|
||||
&mut std::io::stderr().lock(),
|
||||
format!("failed to remove file. filepath:{}", &error_log_path_str),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
return;
|
||||
}
|
||||
println!(
|
||||
"Generated error was output to {}. Please see the file for details.",
|
||||
&error_log_path_str
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +223,13 @@ pub fn get_sec_timeframe(timeframe: &Option<TimeFrameInfo>) -> Option<i64> {
|
||||
}
|
||||
Err(err) => {
|
||||
AlertMessage::alert(
|
||||
&mut std::io::stderr().lock(),
|
||||
format!("Timeframe number is invalid. timeframe.{}", err),
|
||||
&mut BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.append(true)
|
||||
.open(ERROR_LOG_PATH.to_string())
|
||||
.unwrap(),
|
||||
),
|
||||
format!("Timeframe number is invalid. timeframe: {}", err),
|
||||
)
|
||||
.ok();
|
||||
return Option::None;
|
||||
|
||||
42
src/main.rs
42
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,17 @@ 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(),
|
||||
" file name in --output already exist other file. Please input unique file path.".to_owned(),
|
||||
)
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
AlertMessage::create_error_log(ERROR_LOG_PATH.to_string());
|
||||
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
|
||||
if !filepath.ends_with(".evtx") {
|
||||
AlertMessage::alert(
|
||||
@@ -100,14 +115,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<PathBuf> {
|
||||
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![];
|
||||
}
|
||||
|
||||
@@ -171,6 +194,8 @@ impl App {
|
||||
pb.inc();
|
||||
}
|
||||
after_fact();
|
||||
println!("");
|
||||
AlertMessage::output_error_log_exist();
|
||||
}
|
||||
|
||||
// Windowsイベントログファイルを1ファイル分解析する。
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
17
src/yaml.rs
17
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(),
|
||||
|
||||
Reference in New Issue
Block a user