Hotfix/no output colorcode in no true color#376 (#378)
* added color code emit_csv test * replaced HashMap and HashSet to hashbrown #368 * removed debug output in test #368 * added color option #376 * fixed process of output check #376 * removed color output check from test #376 * english updates * colored detections and rules count output by level #384 * refactoring in colored output process #384 * update usage #364 #376 * fixed markdown lint * added windows terminal bug evasion way #382 * update readme * fixed colored output test Co-authored-by: Tanaka Zakku <71482215+YamatoSecurity@users.noreply.github.com>
This commit is contained in:
+104
-60
@@ -11,6 +11,7 @@ use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::BufWriter;
|
||||
use std::io::Write;
|
||||
use std::process;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -39,6 +40,9 @@ pub struct DisplayFormat<'a> {
|
||||
|
||||
/// level_color.txtファイルを読み込み対応する文字色のマッピングを返却する関数
|
||||
pub fn set_output_color() -> Option<HashMap<String, Vec<u8>>> {
|
||||
if !configs::CONFIG.read().unwrap().args.is_present("color") {
|
||||
return None;
|
||||
}
|
||||
let read_result = utils::read_csv("config/level_color.txt");
|
||||
if read_result.is_err() {
|
||||
// color情報がない場合は通常の白色の出力が出てくるのみで動作への影響を与えない為warnとして処理する
|
||||
@@ -135,64 +139,69 @@ fn emit_csv<W: std::io::Write>(
|
||||
for (time, detect_infos) in messages.iter() {
|
||||
for detect_info in detect_infos {
|
||||
if displayflag {
|
||||
// カラーをつけない場合は255,255,255で出力する
|
||||
let mut output_color: Vec<u8> = 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();
|
||||
}
|
||||
let output_color =
|
||||
_get_output_color(&color_map.as_ref().unwrap(), &detect_info.level);
|
||||
wtr.serialize(DisplayFormat {
|
||||
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 {
|
||||
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),
|
||||
})?;
|
||||
}
|
||||
wtr.serialize(DisplayFormat {
|
||||
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出力時フォーマット
|
||||
wtr.serialize(CsvFormat {
|
||||
@@ -224,17 +233,25 @@ fn emit_csv<W: std::io::Write>(
|
||||
total_detect_counts_by_level,
|
||||
"Total".to_string(),
|
||||
"detections".to_string(),
|
||||
&color_map,
|
||||
);
|
||||
_print_unique_results(
|
||||
unique_detect_counts_by_level,
|
||||
"Unique".to_string(),
|
||||
"rules".to_string(),
|
||||
&color_map,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 与えられたユニークな検知数と全体の検知数の情報(レベル別と総計)を元に結果文を標準出力に表示する関数
|
||||
fn _print_unique_results(mut counts_by_level: Vec<u128>, head_word: String, tail_word: String) {
|
||||
fn _print_unique_results(
|
||||
mut counts_by_level: Vec<u128>,
|
||||
head_word: String,
|
||||
tail_word: String,
|
||||
color_map: &Option<HashMap<String, Vec<u8>>>,
|
||||
) {
|
||||
let mut wtr = BufWriter::new(io::stdout());
|
||||
let levels = Vec::from([
|
||||
"critical",
|
||||
"high",
|
||||
@@ -248,19 +265,45 @@ fn _print_unique_results(mut counts_by_level: Vec<u128>, head_word: String, tail
|
||||
counts_by_level.reverse();
|
||||
|
||||
// 全体の集計(levelの記載がないためformatの第二引数は空の文字列)
|
||||
println!(
|
||||
writeln!(
|
||||
wtr,
|
||||
"{} {}: {}",
|
||||
head_word,
|
||||
tail_word,
|
||||
counts_by_level.iter().sum::<u128>()
|
||||
);
|
||||
)
|
||||
.ok();
|
||||
for (i, level_name) in levels.iter().enumerate() {
|
||||
println!(
|
||||
let output_str;
|
||||
let output_raw_str = format!(
|
||||
"{} {} {}: {}",
|
||||
head_word, level_name, tail_word, counts_by_level[i]
|
||||
);
|
||||
if color_map.is_none() {
|
||||
output_str = output_raw_str;
|
||||
} else {
|
||||
let output_color =
|
||||
_get_output_color(&color_map.as_ref().unwrap(), &level_name.to_string());
|
||||
output_str = output_raw_str
|
||||
.truecolor(output_color[0], output_color[1], output_color[2])
|
||||
.to_string();
|
||||
}
|
||||
writeln!(wtr, "{}", output_str).ok();
|
||||
}
|
||||
wtr.flush().ok();
|
||||
}
|
||||
|
||||
/// levelに対応したtruecolorの値の配列を返す関数
|
||||
fn _get_output_color(color_map: &HashMap<String, Vec<u8>>, level: &String) -> Vec<u8> {
|
||||
// カラーをつけない場合は255,255,255で出力する
|
||||
let mut output_color: Vec<u8> = vec![255, 255, 255];
|
||||
let target_color = color_map.get(level);
|
||||
if target_color.is_some() {
|
||||
output_color = target_color.unwrap().to_vec();
|
||||
}
|
||||
return output_color;
|
||||
}
|
||||
|
||||
fn format_time(time: &DateTime<Utc>) -> String {
|
||||
if configs::CONFIG.read().unwrap().args.is_present("utc") {
|
||||
format_rfc(time)
|
||||
@@ -451,6 +494,7 @@ mod tests {
|
||||
+ " | "
|
||||
+ output
|
||||
+ "\n";
|
||||
|
||||
let mut file: Box<dyn io::Write> =
|
||||
Box::new(File::create("./test_emit_csv_display.txt".to_string()).unwrap());
|
||||
assert!(emit_csv(&mut file, true, None).is_ok());
|
||||
|
||||
+19
-18
@@ -53,28 +53,29 @@ fn build_app<'a>() -> ArgMatches<'a> {
|
||||
return ArgMatches::default();
|
||||
}
|
||||
|
||||
let usages = "-d --directory=[DIRECTORY] 'Directory of multiple .evtx files'
|
||||
-f --filepath=[FILEPATH] 'File path to one .evtx file'
|
||||
let usages = "-d --directory=[DIRECTORY] 'Directory of multiple .evtx files.'
|
||||
-f --filepath=[FILEPATH] 'File path to one .evtx file.'
|
||||
-r --rules=[RULEDIRECTORY/RULEFILE] 'Rule file or directory (default: ./rules)'
|
||||
-o --output=[CSV_TIMELINE] 'Save the timeline in CSV format. Example: results.csv'
|
||||
-v --verbose 'Output verbose information'
|
||||
-D --enable-deprecated-rules 'Enable sigma rules marked as deprecated'
|
||||
-n --enable-noisy-rules 'Enable rules marked as noisy'
|
||||
-m --min-level=[LEVEL] 'Minimum level for rules (default: informational)'
|
||||
--start-timeline=[STARTTIMELINE] 'Start time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
|
||||
--end-timeline=[ENDTIMELINE] 'End time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
|
||||
--rfc-2822 'Output date and time in RFC 2822 format. Example: Mon, 07 Aug 2006 12:34:56 -0600'
|
||||
--rfc-3339 'Output date and time in RFC 3339 format. Example: 2006-08-07T12:34:56.485214 -06:00'
|
||||
-u --utc 'Output time in UTC format (default: local time)'
|
||||
-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'
|
||||
-c --color 'Output with color. (Terminal needs to support True Color.)'
|
||||
-o --output=[CSV_TIMELINE] 'Save the timeline in CSV format. (example: results.csv)'
|
||||
-v --verbose 'Output verbose information.'
|
||||
-D --enable-deprecated-rules 'Enable sigma rules marked as deprecated.'
|
||||
-n --enable-noisy-rules 'Enable rules marked as noisy.'
|
||||
-m --min-level=[LEVEL] 'Minimum level for rules. (default: informational)'
|
||||
--start-timeline=[STARTTIMELINE] 'Start time of the event to load from event file. (example: '2018/11/28 12:00:00 +09:00')'
|
||||
--end-timeline=[ENDTIMELINE] 'End time of the event to load from event file. (example: '2018/11/28 12:00:00 +09:00')'
|
||||
--rfc-2822 'Output date and time in RFC 2822 format. (example: Mon, 07 Aug 2006 12:34:56 -0600)'
|
||||
--rfc-3339 'Output date and time in RFC 3339 format. (example: 2006-08-07T12:34:56.485214 -06:00)'
|
||||
-u --utc 'Output time in UTC format. (default: local time)'
|
||||
-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 save error logs.'
|
||||
--contributors 'Prints the list of contributors'";
|
||||
--contributors 'Prints the list of contributors.'";
|
||||
App::new(&program)
|
||||
.about("Hayabusa: Aiming to be the world's greatest Windows event log analysis tool!")
|
||||
.version("1.0.0")
|
||||
.author("Yamato-Security(https://github.com/Yamato-Security/hayabusa)")
|
||||
.version("1.1.0")
|
||||
.author("Yamato Security (https://github.com/Yamato-Security/hayabusa)")
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.usage(usages)
|
||||
.args_from_usage(usages)
|
||||
|
||||
Reference in New Issue
Block a user