Feature/colorlog#239 (#365)
* added color carete #239 * added hex library * added color config file parser #239 * added color output feature #239 * changed fast hashmap library * added color output description(Japanese) #239 * added color output description(English) #239 * fixed medium level typo * removed white color font level #239 * added trim and loose colorcode condition #239 * fixed hex convert error panic #239 - output warn and go next iterator when happen hex convert panic - added user input in hex convert warn output to use easily
This commit is contained in:
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -232,6 +232,17 @@ dependencies = [
|
|||||||
"bitflags",
|
"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]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
@@ -766,11 +777,13 @@ dependencies = [
|
|||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colored",
|
||||||
"csv",
|
"csv",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"evtx",
|
"evtx",
|
||||||
"flate2",
|
"flate2",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"hex 0.4.3",
|
||||||
"hhmmss",
|
"hhmmss",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
@@ -802,6 +815,12 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hhmmss"
|
name = "hhmmss"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1927,7 +1946,7 @@ checksum = "50e26b33762cd2ec755267c4a4af36adb0864b93afbe595ea8ff61b5528f4c11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"error-chain 0.11.0",
|
"error-chain 0.11.0",
|
||||||
"hex",
|
"hex 0.3.2",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ dotenv = "0.15.0"
|
|||||||
hhmmss = "*"
|
hhmmss = "*"
|
||||||
pbr = "*"
|
pbr = "*"
|
||||||
hashbrown = "0.11.2"
|
hashbrown = "0.11.2"
|
||||||
|
colored = "2.0.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
|
||||||
[target.x86_64-pc-windows-gnu]
|
[target.x86_64-pc-windows-gnu]
|
||||||
linker = "x86_64-w64-mingw32-gcc"
|
linker = "x86_64-w64-mingw32-gcc"
|
||||||
|
|||||||
@@ -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.
|
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.
|
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 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -243,6 +243,11 @@ CSVファイルとして保存する場合、以下の2つのフィールドが
|
|||||||
プログレス・バーは、複数のevtxファイルに対してのみ機能します。
|
プログレス・バーは、複数のevtxファイルに対してのみ機能します。
|
||||||
解析したevtxファイルの数と割合をリアルタイムで表示します。
|
解析したevtxファイルの数と割合をリアルタイムで表示します。
|
||||||
|
|
||||||
|
## 標準出力へのカラー設定
|
||||||
|
Hayabusaの結果はLevel毎に文字色を変えることができます。
|
||||||
|
`.\config\level_color.txt`の値を変更することで文字色を変えることができます。
|
||||||
|
形式は`level名,(6桁のRGBのカラーhex)`です。
|
||||||
|
|
||||||
# Hayabusa ルール
|
# Hayabusa ルール
|
||||||
Hayabusa検知ルールはSigmaのようなYML形式で記述されています。`rules`ディレクトリに入っていますが、将来的には[https://github.com/Yamato-Security/hayabusa-rules](https://github.com/Yamato-Security/hayabusa-rules)のレポジトリで管理する予定なので、ルールのissueとpull requestはhayabusaのレポジトリではなく、ルールレポジトリへお願いします。
|
Hayabusa検知ルールはSigmaのようなYML形式で記述されています。`rules`ディレクトリに入っていますが、将来的には[https://github.com/Yamato-Security/hayabusa-rules](https://github.com/Yamato-Security/hayabusa-rules)のレポジトリで管理する予定なので、ルールのissueとpull requestはhayabusaのレポジトリではなく、ルールレポジトリへお願いします。
|
||||||
|
|
||||||
|
|||||||
5
config/level_color.txt
Normal file
5
config/level_color.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
level,colorcode
|
||||||
|
critical,ff0000
|
||||||
|
high,ffff00
|
||||||
|
medium,00ff00
|
||||||
|
low,00ffff
|
||||||
126
src/afterfact.rs
126
src/afterfact.rs
@@ -1,7 +1,10 @@
|
|||||||
use crate::detections::configs;
|
use crate::detections::configs;
|
||||||
use crate::detections::print;
|
use crate::detections::print;
|
||||||
use crate::detections::print::AlertMessage;
|
use crate::detections::print::AlertMessage;
|
||||||
|
use crate::detections::utils;
|
||||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
use chrono::{DateTime, Local, TimeZone, Utc};
|
||||||
|
use colored::*;
|
||||||
|
use hashbrown::HashMap;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -26,11 +29,48 @@ pub struct CsvFormat<'a> {
|
|||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct DisplayFormat<'a> {
|
pub struct DisplayFormat<'a> {
|
||||||
timestamp: &'a str,
|
timestamp: &'a str,
|
||||||
computer: &'a str,
|
pub computer: &'a str,
|
||||||
event_i_d: &'a str,
|
pub event_i_d: &'a str,
|
||||||
level: &'a str,
|
pub level: &'a str,
|
||||||
rule_title: &'a str,
|
pub rule_title: &'a str,
|
||||||
details: &'a str,
|
pub details: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// level_color.txtファイルを読み込み対応する文字色のマッピングを返却する関数
|
||||||
|
pub fn set_output_color() -> Option<HashMap<String, Vec<u8>>> {
|
||||||
|
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<String, Vec<u8>> = 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() {
|
pub fn after_fact() {
|
||||||
@@ -63,13 +103,17 @@ pub fn after_fact() {
|
|||||||
// 標準出力に出力する場合
|
// 標準出力に出力する場合
|
||||||
Box::new(BufWriter::new(io::stdout()))
|
Box::new(BufWriter::new(io::stdout()))
|
||||||
};
|
};
|
||||||
|
let color_map = set_output_color();
|
||||||
if let Err(err) = emit_csv(&mut target, displayflag) {
|
if let Err(err) = emit_csv(&mut target, displayflag, color_map) {
|
||||||
fn_emit_csv_err(Box::new(err));
|
fn_emit_csv_err(Box::new(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_csv<W: std::io::Write>(writer: &mut W, displayflag: bool) -> io::Result<()> {
|
fn emit_csv<W: std::io::Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
displayflag: bool,
|
||||||
|
color_map: Option<HashMap<String, Vec<u8>>>,
|
||||||
|
) -> io::Result<()> {
|
||||||
let mut wtr;
|
let mut wtr;
|
||||||
if displayflag {
|
if displayflag {
|
||||||
wtr = csv::WriterBuilder::new()
|
wtr = csv::WriterBuilder::new()
|
||||||
@@ -88,13 +132,63 @@ fn emit_csv<W: std::io::Write>(writer: &mut W, displayflag: bool) -> io::Result<
|
|||||||
for (time, detect_infos) in messages.iter() {
|
for (time, detect_infos) in messages.iter() {
|
||||||
for detect_info in detect_infos {
|
for detect_info in detect_infos {
|
||||||
if displayflag {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
wtr.serialize(DisplayFormat {
|
wtr.serialize(DisplayFormat {
|
||||||
timestamp: &format!("{} ", &format_time(time)),
|
timestamp: &format!(
|
||||||
level: &format!(" {} ", &detect_info.level),
|
"{} ",
|
||||||
computer: &format!(" {} ", &detect_info.computername),
|
&format_time(time).truecolor(
|
||||||
event_i_d: &format!(" {} ", &detect_info.eventid),
|
output_color[0],
|
||||||
rule_title: &format!(" {} ", &detect_info.alert),
|
output_color[1],
|
||||||
details: &format!(" {}", &detect_info.detail),
|
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 {
|
} else {
|
||||||
// csv出力時フォーマット
|
// csv出力時フォーマット
|
||||||
@@ -266,7 +360,7 @@ mod tests {
|
|||||||
+ "\n";
|
+ "\n";
|
||||||
let mut file: Box<dyn io::Write> =
|
let mut file: Box<dyn io::Write> =
|
||||||
Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap());
|
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") {
|
match read_to_string("./test_emit_csv.csv") {
|
||||||
Err(_) => panic!("Failed to open file."),
|
Err(_) => panic!("Failed to open file."),
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
@@ -337,7 +431,7 @@ mod tests {
|
|||||||
+ "\n";
|
+ "\n";
|
||||||
let mut file: Box<dyn io::Write> =
|
let mut file: Box<dyn io::Write> =
|
||||||
Box::new(File::create("./test_emit_csv_display.txt".to_string()).unwrap());
|
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") {
|
match read_to_string("./test_emit_csv_display.txt") {
|
||||||
Err(_) => panic!("Failed to open file."),
|
Err(_) => panic!("Failed to open file."),
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use crate::detections::configs;
|
|||||||
use crate::detections::utils;
|
use crate::detections::utils;
|
||||||
use crate::detections::utils::get_serde_number_to_string;
|
use crate::detections::utils::get_serde_number_to_string;
|
||||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
use chrono::{DateTime, Local, TimeZone, Utc};
|
||||||
|
use hashbrown::HashMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::create_dir;
|
use std::fs::create_dir;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|||||||
Reference in New Issue
Block a user