Merge branch 'main' into feature/start_finish_time
This commit is contained in:
@@ -64,6 +64,7 @@ fn build_app<'a>() -> ArgMatches<'a> {
|
||||
-u --utc 'Output time in UTC format (default: local time)'
|
||||
-d --directory=[DIRECTORY] 'Directory of multiple .evtx files'
|
||||
-s --statistics 'Prints statistics of event IDs'
|
||||
-n --show-noisyalerts 'do not exclude noisy rules'
|
||||
-t --threadnum=[NUM] 'Thread number (default: optimal number for performance)'
|
||||
--contributors 'Prints the list of contributors'";
|
||||
App::new(&program)
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::detections::print::MESSAGES;
|
||||
use crate::detections::rule;
|
||||
use crate::detections::rule::RuleNode;
|
||||
use crate::detections::utils::get_serde_number_to_string;
|
||||
use crate::filter;
|
||||
use crate::yaml::ParseYaml;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -51,10 +52,15 @@ impl Detection {
|
||||
}
|
||||
|
||||
// ルールファイルをパースします。
|
||||
pub fn parse_rule_files(level: String, rulespath: Option<&str>) -> Vec<RuleNode> {
|
||||
pub fn parse_rule_files(
|
||||
level: String,
|
||||
rulespath: Option<&str>,
|
||||
exclude_ids: &filter::RuleExclude,
|
||||
) -> Vec<RuleNode> {
|
||||
// ルールファイルのパースを実行
|
||||
let mut rulefile_loader = ParseYaml::new();
|
||||
let result_readdir = rulefile_loader.read_dir(rulespath.unwrap_or(DIRPATH_RULES), &level);
|
||||
let result_readdir =
|
||||
rulefile_loader.read_dir(rulespath.unwrap_or(DIRPATH_RULES), &level, exclude_ids);
|
||||
if result_readdir.is_err() {
|
||||
AlertMessage::alert(
|
||||
&mut std::io::stderr().lock(),
|
||||
@@ -269,6 +275,6 @@ impl Detection {
|
||||
fn test_parse_rule_files() {
|
||||
let level = "informational";
|
||||
let opt_rule_path = Some("./test_files/rules/level_yaml");
|
||||
let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path);
|
||||
let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, &filter::exclude_ids());
|
||||
assert_eq!(5, cole.len());
|
||||
}
|
||||
|
||||
44
src/filter.rs
Normal file
44
src/filter.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use crate::detections::configs;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RuleExclude {
|
||||
pub no_use_rule: HashSet<String>,
|
||||
}
|
||||
|
||||
pub fn exclude_ids() -> RuleExclude {
|
||||
let mut ids;
|
||||
match fs::read("config/exclude-rules.txt") {
|
||||
Ok(file) => ids = String::from_utf8(file).unwrap(),
|
||||
Err(_) => panic!("config/exclude-rules.txt does not exist"),
|
||||
};
|
||||
|
||||
if !configs::CONFIG
|
||||
.read()
|
||||
.unwrap()
|
||||
.args
|
||||
.is_present("show-noisyalerts")
|
||||
{
|
||||
ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最初の行が一行にまとめられてしまう。
|
||||
match fs::read("config/noisy-rules.txt") {
|
||||
Ok(file) => ids += &String::from_utf8(file).unwrap(),
|
||||
Err(_) => panic!("config/noisy-rules.txt does not exist"),
|
||||
};
|
||||
}
|
||||
|
||||
let mut exclude_ids = RuleExclude {
|
||||
no_use_rule: HashSet::new(),
|
||||
};
|
||||
|
||||
for v in ids.split_whitespace() {
|
||||
let v = v.to_string();
|
||||
if v.is_empty() {
|
||||
// 空行は無視する。
|
||||
continue;
|
||||
}
|
||||
exclude_ids.no_use_rule.insert(v);
|
||||
}
|
||||
|
||||
return exclude_ids;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod afterfact;
|
||||
pub mod detections;
|
||||
pub mod filter;
|
||||
pub mod notify;
|
||||
pub mod omikuji;
|
||||
pub mod timeline;
|
||||
|
||||
39
src/main.rs
39
src/main.rs
@@ -1,17 +1,20 @@
|
||||
extern crate serde;
|
||||
extern crate serde_derive;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::Datelike;
|
||||
use chrono::{DateTime, Local};
|
||||
use evtx::{EvtxParser, ParserSettings};
|
||||
use hayabusa::detections::detection;
|
||||
use hayabusa::detections::detection::EvtxRecordInfo;
|
||||
use hayabusa::detections::print::AlertMessage;
|
||||
use hayabusa::filter;
|
||||
use hayabusa::omikuji::Omikuji;
|
||||
use hayabusa::{afterfact::after_fact, detections::utils};
|
||||
use hayabusa::{detections::configs, timeline::timeline::Timeline};
|
||||
use hhmmss::Hhmmss;
|
||||
use pbr::ProgressBar;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::PathBuf,
|
||||
@@ -19,12 +22,18 @@ use std::{
|
||||
};
|
||||
|
||||
// 一度にtimelineやdetectionを実行する行数
|
||||
const MAX_DETECT_RECORDS: usize = 40000;
|
||||
const MAX_DETECT_RECORDS: usize = 5000;
|
||||
|
||||
fn main() {
|
||||
let analysis_start_time: DateTime<Local> = Local::now();
|
||||
if !configs::CONFIG.read().unwrap().args.is_present("q") {
|
||||
output_logo();
|
||||
println!("");
|
||||
output_eggs(&format!(
|
||||
"{:02}/{:02}",
|
||||
&analysis_start_time.month().to_owned(),
|
||||
&analysis_start_time.day().to_owned()
|
||||
));
|
||||
}
|
||||
if configs::CONFIG.read().unwrap().args.args.len() == 0 {
|
||||
println!(
|
||||
@@ -33,7 +42,6 @@ fn main() {
|
||||
);
|
||||
return;
|
||||
}
|
||||
let analysis_start_time: DateTime<Utc> = Utc::now();
|
||||
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
|
||||
if !filepath.ends_with(".evtx") {
|
||||
AlertMessage::alert(
|
||||
@@ -64,7 +72,7 @@ fn main() {
|
||||
print_contributors();
|
||||
return;
|
||||
}
|
||||
let analysis_end_time: DateTime<Utc> = Utc::now();
|
||||
let analysis_end_time: DateTime<Local> = Local::now();
|
||||
let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time);
|
||||
println!("Elapsed Time: {}", &analysis_duration.hhmmssxxx());
|
||||
println!("");
|
||||
@@ -120,11 +128,12 @@ fn analysis_files(evtx_files: Vec<PathBuf>) {
|
||||
.value_of("level")
|
||||
.unwrap_or("informational")
|
||||
.to_uppercase();
|
||||
println!("Analyzing event files: {:?}", evtx_files.len());
|
||||
|
||||
println!("Analyzing Event Files: {:?}", evtx_files.len());
|
||||
let rule_files = detection::Detection::parse_rule_files(
|
||||
level,
|
||||
configs::CONFIG.read().unwrap().args.value_of("rules"),
|
||||
&filter::exclude_ids(),
|
||||
);
|
||||
let mut pb = ProgressBar::new(evtx_files.len() as u64);
|
||||
let mut detection = detection::Detection::new(rule_files);
|
||||
@@ -248,12 +257,30 @@ fn _output_with_omikuji(omikuji: Omikuji) {
|
||||
println!("{}", content);
|
||||
}
|
||||
|
||||
/// output logo
|
||||
fn output_logo() {
|
||||
let fp = &format!("art/logo.txt");
|
||||
let content = fs::read_to_string(fp).unwrap();
|
||||
let content = fs::read_to_string(fp).unwrap_or("".to_owned());
|
||||
println!("{}", content);
|
||||
}
|
||||
|
||||
/// output easter egg arts
|
||||
fn output_eggs(exec_datestr: &str) {
|
||||
let mut eggs: HashMap<&str, &str> = HashMap::new();
|
||||
eggs.insert("01/01", "art/happynewyear.txt");
|
||||
eggs.insert("02/22", "art/ninja.txt");
|
||||
eggs.insert("08/08", "art/takoyaki.txt");
|
||||
eggs.insert("12/25", "art/christmas.txt");
|
||||
|
||||
match eggs.get(exec_datestr) {
|
||||
None => {}
|
||||
Some(path) => {
|
||||
let content = fs::read_to_string(path).unwrap_or("".to_owned());
|
||||
println!("{}", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::collect_evtxfiles;
|
||||
|
||||
76
src/yaml.rs
76
src/yaml.rs
@@ -3,6 +3,7 @@ extern crate yaml_rust;
|
||||
|
||||
use crate::detections::configs;
|
||||
use crate::detections::print::AlertMessage;
|
||||
use crate::filter::RuleExclude;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
@@ -42,13 +43,18 @@ impl ParseYaml {
|
||||
Ok(file_content)
|
||||
}
|
||||
|
||||
pub fn read_dir<P: AsRef<Path>>(&mut self, path: P, level: &str) -> io::Result<String> {
|
||||
pub fn read_dir<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
level: &str,
|
||||
exclude_ids: &RuleExclude,
|
||||
) -> io::Result<String> {
|
||||
let mut entries = fs::read_dir(path)?;
|
||||
let yaml_docs = entries.try_fold(vec![], |mut ret, entry| {
|
||||
let entry = entry?;
|
||||
// フォルダは再帰的に呼び出す。
|
||||
if entry.file_type()?.is_dir() {
|
||||
self.read_dir(entry.path(), level)?;
|
||||
self.read_dir(entry.path(), level, exclude_ids)?;
|
||||
return io::Result::Ok(ret);
|
||||
}
|
||||
// ファイル以外は無視
|
||||
@@ -115,6 +121,7 @@ impl ParseYaml {
|
||||
.unwrap_or(&0)
|
||||
+ 1,
|
||||
);
|
||||
|
||||
if configs::CONFIG.read().unwrap().args.is_present("verbose") {
|
||||
println!("Loaded yml file path: {}", filepath);
|
||||
}
|
||||
@@ -130,6 +137,21 @@ impl ParseYaml {
|
||||
return Option::None;
|
||||
}
|
||||
|
||||
//除外されたルールは無視する
|
||||
let rule_id = &yaml_doc["id"].as_str();
|
||||
if rule_id.is_some() {
|
||||
match exclude_ids
|
||||
.no_use_rule
|
||||
.get(&rule_id.unwrap_or("").to_string())
|
||||
{
|
||||
None => (),
|
||||
Some(_) => {
|
||||
self.ignorerule_count += 1;
|
||||
return Option::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Option::Some((filepath, yaml_doc));
|
||||
})
|
||||
.collect();
|
||||
@@ -141,14 +163,24 @@ impl ParseYaml {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::filter;
|
||||
use crate::yaml;
|
||||
use crate::yaml::RuleExclude;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use yaml_rust::YamlLoader;
|
||||
|
||||
#[test]
|
||||
fn test_read_dir_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
&yaml.read_dir("test_files/rules/yaml/".to_string(), &"".to_owned());
|
||||
let exclude_ids = RuleExclude {
|
||||
no_use_rule: HashSet::new(),
|
||||
};
|
||||
let _ = &yaml.read_dir(
|
||||
"test_files/rules/yaml/".to_string(),
|
||||
&"".to_owned(),
|
||||
&exclude_ids,
|
||||
);
|
||||
assert_ne!(yaml.files.len(), 0);
|
||||
}
|
||||
|
||||
@@ -183,7 +215,8 @@ mod tests {
|
||||
fn test_default_level_read_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/level_yaml");
|
||||
yaml.read_dir(path.to_path_buf(), &"").unwrap();
|
||||
yaml.read_dir(path.to_path_buf(), &"", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.files.len(), 5);
|
||||
}
|
||||
|
||||
@@ -191,36 +224,59 @@ mod tests {
|
||||
fn test_info_level_read_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/level_yaml");
|
||||
yaml.read_dir(path.to_path_buf(), &"informational").unwrap();
|
||||
yaml.read_dir(path.to_path_buf(), &"informational", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.files.len(), 5);
|
||||
}
|
||||
#[test]
|
||||
fn test_low_level_read_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/level_yaml");
|
||||
|
||||
yaml.read_dir(path.to_path_buf(), &"LOW").unwrap();
|
||||
yaml.read_dir(path.to_path_buf(), &"LOW", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.files.len(), 4);
|
||||
}
|
||||
#[test]
|
||||
fn test_medium_level_read_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/level_yaml");
|
||||
yaml.read_dir(path.to_path_buf(), &"MEDIUM").unwrap();
|
||||
yaml.read_dir(path.to_path_buf(), &"MEDIUM", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.files.len(), 3);
|
||||
}
|
||||
#[test]
|
||||
fn test_high_level_read_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/level_yaml");
|
||||
yaml.read_dir(path.to_path_buf(), &"HIGH").unwrap();
|
||||
yaml.read_dir(path.to_path_buf(), &"HIGH", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.files.len(), 2);
|
||||
}
|
||||
#[test]
|
||||
fn test_critical_level_read_yaml() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/level_yaml");
|
||||
yaml.read_dir(path.to_path_buf(), &"CRITICAL").unwrap();
|
||||
yaml.read_dir(path.to_path_buf(), &"CRITICAL", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.files.len(), 1);
|
||||
}
|
||||
#[test]
|
||||
fn test_all_exclude_rules_file() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/yaml");
|
||||
yaml.read_dir(path.to_path_buf(), &"", &filter::exclude_ids())
|
||||
.unwrap();
|
||||
assert_eq!(yaml.ignorerule_count, 10);
|
||||
}
|
||||
#[test]
|
||||
fn test_none_exclude_rules_file() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/yaml");
|
||||
let exclude_ids = RuleExclude {
|
||||
no_use_rule: HashSet::new(),
|
||||
};
|
||||
yaml.read_dir(path.to_path_buf(), &"", &exclude_ids)
|
||||
.unwrap();
|
||||
assert_eq!(yaml.ignorerule_count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user