Merge branch 'main' into 597-moved-update-rules-option-process

This commit is contained in:
DustInDark
2022-06-29 00:46:48 +09:00
19 changed files with 349 additions and 236 deletions
+33 -22
View File
@@ -1,5 +1,5 @@
use crate::detections::configs;
use crate::detections::configs::TERM_SIZE;
use crate::detections::configs::{CURRENT_EXE_PATH, TERM_SIZE};
use crate::detections::print;
use crate::detections::print::{AlertMessage, IS_HIDE_RECORD_ID};
use crate::detections::utils;
@@ -64,7 +64,12 @@ lazy_static! {
/// level_color.txtファイルを読み込み対応する文字色のマッピングを返却する関数
pub fn set_output_color() -> HashMap<String, Color> {
let read_result = utils::read_csv("config/level_color.txt");
let read_result = utils::read_csv(
CURRENT_EXE_PATH
.join("config/level_color.txt")
.to_str()
.unwrap(),
);
let mut color_map: HashMap<String, Color> = HashMap::new();
if configs::CONFIG.read().unwrap().args.no_color {
return color_map;
@@ -283,18 +288,20 @@ fn emit_csv<W: std::io::Write>(
//ヘッダーのみを出力
if plus_header {
write!(disp_wtr_buf, "{}", _get_serialized_disp_output(None)).ok();
plus_header = false;
}
disp_wtr_buf
.set_color(
ColorSpec::new().set_fg(_get_output_color(&color_map, &detect_info.level)),
write_color_buffer(
&disp_wtr,
get_writable_color(None),
&_get_serialized_disp_output(None),
true,
)
.ok();
write!(
disp_wtr_buf,
"{}",
_get_serialized_disp_output(Some(dispformat))
plus_header = false;
}
write_color_buffer(
&disp_wtr,
get_writable_color(_get_output_color(&color_map, &detect_info.level)),
&_get_serialized_disp_output(Some(dispformat)),
false,
)
.ok();
} else {
@@ -354,7 +361,6 @@ fn emit_csv<W: std::io::Write>(
}
}
if displayflag {
disp_wtr.print(&disp_wtr_buf)?;
println!();
} else {
wtr.flush()?;
@@ -382,8 +388,9 @@ fn emit_csv<W: std::io::Write>(
disp_wtr_buf.clear();
write_color_buffer(
&disp_wtr,
get_writable_color(Some(Color::Green)),
get_writable_color(Some(Color::Rgb(0, 255, 0))),
"Results Summary:",
true,
)
.ok();
@@ -407,6 +414,7 @@ fn emit_csv<W: std::io::Write>(
&disp_wtr,
get_writable_color(None),
&format!("Total events: {}", all_record_cnt),
true,
)
.ok();
write_color_buffer(
@@ -416,10 +424,10 @@ fn emit_csv<W: std::io::Write>(
"Data reduction: {} events ({:.2}%)",
reducted_record_cnt, reducted_percent
),
true,
)
.ok();
println!();
println!();
_print_unique_results(
total_detect_counts_by_level,
@@ -472,7 +480,7 @@ fn _get_serialized_disp_output(dispformat: Option<DisplayFormat>) -> String {
if configs::CONFIG.read().unwrap().args.full_data {
titles.push("RecordInformation");
}
return format!("{}\n", titles.join("|"));
return titles.join("|");
}
let mut disp_serializer = csv::WriterBuilder::new()
.double_quote(false)
@@ -522,8 +530,9 @@ fn _print_unique_results(
"{} {}: {}",
head_word,
tail_word,
counts_by_level.iter().sum::<u128>()
counts_by_level.iter().sum::<u128>(),
),
true,
)
.ok();
@@ -539,6 +548,7 @@ fn _print_unique_results(
&BufferWriter::stdout(ColorChoice::Always),
_get_output_color(color_map, level_name),
&output_raw_str,
true,
)
.ok();
}
@@ -695,8 +705,7 @@ mod tests {
use crate::afterfact::emit_csv;
use crate::afterfact::format_time;
use crate::detections::print;
use crate::detections::print::DetectInfo;
use crate::detections::print::CH_CONFIG;
use crate::detections::print::{DetectInfo, Message};
use chrono::{Local, TimeZone, Utc};
use hashbrown::HashMap;
use serde_json::Value;
@@ -712,6 +721,8 @@ mod tests {
}
fn test_emit_csv_output() {
let mock_ch_filter =
Message::create_output_filter_config("config/channel_abbreviations.txt", true, false);
let test_filepath: &str = "test.evtx";
let test_rulepath: &str = "test-rule.yml";
let test_title = "test_title";
@@ -750,7 +761,7 @@ mod tests {
level: test_level.to_string(),
computername: test_computername.to_string(),
eventid: test_eventid.to_string(),
channel: CH_CONFIG
channel: mock_ch_filter
.get("Security")
.unwrap_or(&String::default())
.to_string(),
@@ -821,7 +832,7 @@ mod tests {
let test_timestamp = Utc
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
.unwrap();
let expect_header = "Timestamp|Computer|Channel|EventID|Level|RecordID|RuleTitle|Details\n";
let expect_header = "Timestamp|Computer|Channel|EventID|Level|RecordID|RuleTitle|Details";
let expect_tz = test_timestamp.with_timezone(&Local);
let expect_no_header = expect_tz
@@ -845,7 +856,7 @@ mod tests {
+ "|"
+ test_recinfo
+ "\n";
assert_eq!(_get_serialized_disp_output(None,), expect_header);
assert_eq!(_get_serialized_disp_output(None), expect_header);
assert_eq!(
_get_serialized_disp_output(Some(DisplayFormat {
timestamp: &format_time(&test_timestamp, false),
+17 -5
View File
@@ -8,6 +8,7 @@ use hashbrown::HashMap;
use hashbrown::HashSet;
use lazy_static::lazy_static;
use regex::Regex;
use std::env::current_exe;
use std::path::PathBuf;
use std::sync::RwLock;
use terminal_size::{terminal_size, Height, Width};
@@ -32,6 +33,8 @@ lazy_static! {
pub static ref TERM_SIZE: Option<(Width, Height)> = terminal_size();
pub static ref TARGET_EXTENSIONS: HashSet<String> =
get_target_extensions(CONFIG.read().unwrap().args.evtx_file_ext.as_ref());
pub static ref CURRENT_EXE_PATH: PathBuf =
current_exe().unwrap().parent().unwrap().to_path_buf();
pub static ref EXCLUDE_STATUS: HashSet<String> =
convert_option_vecs_to_hs(CONFIG.read().unwrap().args.exclude_status.as_ref());
}
@@ -84,7 +87,7 @@ pub struct Config {
/// Specify custom rule config folder (default: ./rules/config)
#[clap(
short = 'c',
long,
long = "rules-config",
default_value = "./rules/config",
hide_default_value = true,
value_name = "RULE_CONFIG_DIRECTORY"
@@ -188,11 +191,10 @@ pub struct Config {
/// Tune alert levels (default: ./rules/config/level_tuning.txt)
#[clap(
long = "level-tuning",
default_value = "./rules/config/level_tuning.txt",
hide_default_value = true,
value_name = "LEVEL_TUNING_FILE"
)]
pub level_tuning: PathBuf,
pub level_tuning: Option<Option<String>>,
/// Quiet mode: do not display the launch banner
#[clap(short, long)]
@@ -234,8 +236,18 @@ impl ConfigReader<'_> {
app: build_cmd,
args: parse,
headless_help: String::default(),
event_timeline_config: load_eventcode_info("config/statistics_event_info.txt"),
target_eventids: load_target_ids("config/target_eventids.txt"),
event_timeline_config: load_eventcode_info(
CURRENT_EXE_PATH
.join("config/statistics_event_info.txt")
.to_str()
.unwrap(),
),
target_eventids: load_target_ids(
CURRENT_EXE_PATH
.join("config/target_eventids.txt")
.to_str()
.unwrap(),
),
}
}
}
+3
View File
@@ -394,6 +394,7 @@ impl Detection {
&BufferWriter::stdout(ColorChoice::Always),
Some(Color::Red),
&format!("Rule parsing errors: {}", err_rc),
true,
)
.ok();
}
@@ -421,6 +422,7 @@ impl Detection {
rate,
deprecated_flag
),
true,
)
.ok();
}
@@ -434,6 +436,7 @@ impl Detection {
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!("{} rules: {}", key, value),
true,
)
.ok();
});
+11 -2
View File
@@ -1,5 +1,6 @@
extern crate lazy_static;
use crate::detections::configs;
use crate::detections::configs::CURRENT_EXE_PATH;
use crate::detections::utils;
use crate::detections::utils::get_serde_number_to_string;
use crate::detections::utils::write_color_buffer;
@@ -53,12 +54,18 @@ lazy_static! {
pub static ref STATISTICS_FLAG: bool = configs::CONFIG.read().unwrap().args.statistics;
pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().args.logon_summary;
pub static ref TAGS_CONFIG: HashMap<String, String> = Message::create_output_filter_config(
"config/output_tag.txt",
CURRENT_EXE_PATH
.join("config/output_tag.txt")
.to_str()
.unwrap(),
true,
configs::CONFIG.read().unwrap().args.all_tags
);
pub static ref CH_CONFIG: HashMap<String, String> = Message::create_output_filter_config(
"config/channel_abbreviations.txt",
CURRENT_EXE_PATH
.join("config/channel_abbreviations.txt")
.to_str()
.unwrap(),
false,
configs::CONFIG.read().unwrap().args.all_tags
);
@@ -327,6 +334,7 @@ impl AlertMessage {
&BufferWriter::stderr(ColorChoice::Always),
None,
&format!("[ERROR] {}", contents),
true,
)
}
@@ -336,6 +344,7 @@ impl AlertMessage {
&BufferWriter::stderr(ColorChoice::Always),
None,
&format!("[WARN] {}", contents),
true,
)
}
}
+5 -5
View File
@@ -523,8 +523,8 @@ mod tests {
- ホスト アプリケーション
ImagePath:
min_length: 1234321
regexes: ./rules/config/regex/detectlist_suspicous_services.txt
allowlist: ./rules/config/regex/allowlist_legitimate_services.txt
regexes: ./../../../rules/config/regex/detectlist_suspicous_services.txt
allowlist: ./../../../rules/config/regex/allowlist_legitimate_services.txt
falsepositives:
- unknown
level: medium
@@ -1111,7 +1111,7 @@ mod tests {
selection:
EventID: 4103
Channel:
- allowlist: ./rules/config/regex/allowlist_legitimate_services.txt
- allowlist: ./../../../rules/config/regex/allowlist_legitimate_services.txt
details: 'command=%CommandLine%'
"#;
@@ -1145,7 +1145,7 @@ mod tests {
selection:
EventID: 4103
Channel:
- allowlist: ./rules/config/regex/allowlist_legitimate_services.txt
- allowlist: ./../../../rules/config/regex/allowlist_legitimate_services.txt
details: 'command=%CommandLine%'
"#;
@@ -1179,7 +1179,7 @@ mod tests {
selection:
EventID: 4103
Channel:
- allowlist: ./rules/config/regex/allowlist_legitimate_services.txt
- allowlist: ./../../../rules/config/regex/allowlist_legitimate_services.txt
details: 'command=%CommandLine%'
"#;
+20 -4
View File
@@ -3,6 +3,8 @@ extern crate csv;
extern crate regex;
use crate::detections::configs;
use crate::detections::configs::CURRENT_EXE_PATH;
use termcolor::Color;
use tokio::runtime::Builder;
@@ -66,7 +68,16 @@ pub fn value_to_string(value: &Value) -> Option<String> {
}
pub fn read_txt(filename: &str) -> Result<Vec<String>, String> {
let f = File::open(filename);
let filepath = if filename.starts_with("./") {
CURRENT_EXE_PATH
.join(filename)
.to_str()
.unwrap()
.to_string()
} else {
filename.to_string()
};
let f = File::open(filepath);
if f.is_err() {
let errmsg = format!("Cannot open file. [file:{}]", filename);
return Result::Err(errmsg);
@@ -245,10 +256,15 @@ pub fn write_color_buffer(
wtr: &BufferWriter,
color: Option<Color>,
output_str: &str,
newline_flag: bool,
) -> io::Result<()> {
let mut buf = wtr.buffer();
buf.set_color(ColorSpec::new().set_fg(color)).ok();
writeln!(buf, "{}", output_str).ok();
if newline_flag {
writeln!(buf, "{}", output_str).ok();
} else {
write!(buf, "{}", output_str).ok();
}
wtr.print(&buf)
}
@@ -432,7 +448,7 @@ mod tests {
#[test]
fn test_check_regex() {
let regexes: Vec<Regex> =
utils::read_txt("./rules/config/regex/detectlist_suspicous_services.txt")
utils::read_txt("./../../../rules/config/regex/detectlist_suspicous_services.txt")
.unwrap()
.into_iter()
.map(|regex_str| Regex::new(&regex_str).unwrap())
@@ -448,7 +464,7 @@ mod tests {
fn test_check_allowlist() {
let commandline = "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\"";
let allowlist: Vec<Regex> =
utils::read_txt("./rules/config/regex/allowlist_legitimate_services.txt")
utils::read_txt("./../../../rules/config/regex/allowlist_legitimate_services.txt")
.unwrap()
.into_iter()
.map(|allow_str| Regex::new(&allow_str).unwrap())
+77 -20
View File
@@ -10,6 +10,7 @@ use bytesize::ByteSize;
use chrono::{DateTime, Datelike, Local};
use evtx::{EvtxParser, ParserSettings};
use hashbrown::{HashMap, HashSet};
use hayabusa::detections::configs::CURRENT_EXE_PATH;
use hayabusa::detections::configs::{load_pivot_keywords, TargetEventTime, TARGET_EXTENSIONS};
use hayabusa::detections::detection::{self, EvtxRecordInfo};
use hayabusa::detections::pivot::PivotKeyword;
@@ -77,7 +78,12 @@ impl App {
fn exec(&mut self) {
if *PIVOT_KEYWORD_LIST_FLAG {
load_pivot_keywords("config/pivot_keywords.txt");
load_pivot_keywords(
CURRENT_EXE_PATH
.join("config/pivot_keywords.txt")
.to_str()
.unwrap(),
);
}
let analysis_start_time: DateTime<Local> = Local::now();
@@ -115,6 +121,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
None,
"Rules updated successfully.",
true,
)
.ok();
}
@@ -126,14 +133,30 @@ impl App {
println!();
return;
}
if !Path::new("./config").exists() {
// 実行時のexeファイルのパスをベースに変更する必要があるためデフォルトの値であった場合はそのexeファイルと同一階層を探すようにする
if !CURRENT_EXE_PATH.join("config").exists() {
AlertMessage::alert(
"Hayabusa could not find the config directory.\nPlease run it from the Hayabusa root directory.\nExample: ./hayabusa-1.0.0-windows-x64.exe"
"Hayabusa could not find the config directory.\nPlease make sure that it is in the same directory as the hayabusa executable."
)
.ok();
return;
}
// ワーキングディレクトリ以外からの実行の際にrules-configオプションの指定がないとエラーが発生することを防ぐための処理
if configs::CONFIG
.read()
.unwrap()
.args
.config
.to_str()
.unwrap()
== "./rules/config"
{
configs::CONFIG.write().unwrap().args.config = CURRENT_EXE_PATH.join("rules/config");
}
// ワーキングディレクトリ以外からの実行の際にrules-configオプションの指定がないとエラーが発生することを防ぐための処理
if configs::CONFIG.read().unwrap().args.rules.to_str().unwrap() == "./rules" {
configs::CONFIG.write().unwrap().args.rules = CURRENT_EXE_PATH.join("rules");
}
if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output {
let pivot_key_unions = PIVOT_KEYWORD.read().unwrap();
@@ -168,6 +191,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
None,
"Generating Event ID Statistics",
true,
)
.ok();
println!();
@@ -177,6 +201,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
None,
"Generating Logons Summary",
true,
)
.ok();
println!();
@@ -221,18 +246,21 @@ impl App {
} else if configs::CONFIG.read().unwrap().args.contributors {
self.print_contributors();
return;
} else if std::env::args()
.into_iter()
.any(|arg| arg.contains("level-tuning"))
{
let level_tuning_config_path = configs::CONFIG
} else if configs::CONFIG.read().unwrap().args.level_tuning.is_some() {
let level_tuning_val = &configs::CONFIG
.read()
.unwrap()
.args
.level_tuning
.as_path()
.display()
.to_string();
.clone()
.unwrap();
let level_tuning_config_path = match level_tuning_val {
Some(path) => path.to_owned(),
_ => CURRENT_EXE_PATH
.join("./rules/config/level_tuning.txt")
.display()
.to_string(),
};
if Path::new(&level_tuning_config_path).exists() {
if let Err(err) = LevelTuning::run(
@@ -260,6 +288,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
None,
&configs::CONFIG.read().unwrap().headless_help,
true,
)
.ok();
return;
@@ -272,6 +301,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()),
true,
)
.ok();
println!();
@@ -324,17 +354,30 @@ impl App {
)
.ok();
});
write_color_buffer(&BufferWriter::stdout(ColorChoice::Always), None, &output).ok();
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&output,
true,
)
.ok();
} else {
//標準出力の場合
let output = "The following pivot keywords were found:".to_string();
write_color_buffer(&BufferWriter::stdout(ColorChoice::Always), None, &output).ok();
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&output,
true,
)
.ok();
pivot_key_unions.iter().for_each(|(key, pivot_keyword)| {
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&create_output(String::default(), key, pivot_keyword),
true,
)
.ok();
});
@@ -418,10 +461,15 @@ impl App {
}
fn print_contributors(&self) {
match fs::read_to_string("./contributors.txt") {
match fs::read_to_string(CURRENT_EXE_PATH.join("contributors.txt")) {
Ok(contents) => {
write_color_buffer(&BufferWriter::stdout(ColorChoice::Always), None, &contents)
.ok();
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&contents,
true,
)
.ok();
}
Err(err) => {
AlertMessage::alert(&format!("{}", err)).ok();
@@ -440,6 +488,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!("Analyzing event files: {:?}", evtx_files.len()),
true,
)
.ok();
@@ -655,7 +704,7 @@ impl App {
/// output logo
fn output_logo(&self) {
let fp = &"art/logo.txt".to_string();
let fp = CURRENT_EXE_PATH.join("art/logo.txt");
let content = fs::read_to_string(fp).unwrap_or_default();
let output_color = if configs::CONFIG.read().unwrap().args.no_color {
None
@@ -666,6 +715,7 @@ impl App {
&BufferWriter::stdout(ColorChoice::Always),
output_color,
&content,
true,
)
.ok();
}
@@ -681,8 +731,15 @@ impl App {
match eggs.get(exec_datestr) {
None => {}
Some(path) => {
let content = fs::read_to_string(path).unwrap_or_default();
write_color_buffer(&BufferWriter::stdout(ColorChoice::Always), None, &content).ok();
let egg_path = CURRENT_EXE_PATH.join(path);
let content = fs::read_to_string(egg_path).unwrap_or_default();
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&content,
true,
)
.ok();
}
}
}
+2
View File
@@ -62,6 +62,7 @@ impl LevelTuning {
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!("path: {}", path),
true,
)
.ok();
let mut content = match fs::read_to_string(&path) {
@@ -101,6 +102,7 @@ impl LevelTuning {
rule["level"].as_str().unwrap(),
new_level
),
true,
)
.ok();
}