From 341a5e4f86e27fc7cc09567c253e905409f63f35 Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Tue, 30 Nov 2021 22:54:36 +0900
Subject: [PATCH 01/22] feature fillter no use rules
---
config/exclude-rules.txt | 0
config/noisy-rules.txt | 0
src/detections/configs.rs | 1 +
src/detections/detection.rs | 10 ++++--
src/fillter.rs | 6 ++++
src/lib.rs | 1 +
src/main.rs | 22 ++++++++++++
src/yaml.rs | 67 +++++++++++++++++++++++++++++++------
8 files changed, 95 insertions(+), 12 deletions(-)
create mode 100644 config/exclude-rules.txt
create mode 100644 config/noisy-rules.txt
create mode 100644 src/fillter.rs
diff --git a/config/exclude-rules.txt b/config/exclude-rules.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/config/noisy-rules.txt b/config/noisy-rules.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/src/detections/configs.rs b/src/detections/configs.rs
index 02499876..ce158ba2 100644
--- a/src/detections/configs.rs
+++ b/src/detections/configs.rs
@@ -58,6 +58,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)
diff --git a/src/detections/detection.rs b/src/detections/detection.rs
index b6233769..7e610c42 100644
--- a/src/detections/detection.rs
+++ b/src/detections/detection.rs
@@ -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::fillter::RuleFill;
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 {
+ pub fn parse_rule_files(
+ level: String,
+ rulespath: Option<&str>,
+ fill_ids: RuleFill,
+ ) -> Vec {
// ルールファイルのパースを実行
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, fill_ids);
if result_readdir.is_err() {
AlertMessage::alert(
&mut std::io::stderr().lock(),
diff --git a/src/fillter.rs b/src/fillter.rs
new file mode 100644
index 00000000..0cd68b2a
--- /dev/null
+++ b/src/fillter.rs
@@ -0,0 +1,6 @@
+use std::collections::HashMap;
+
+#[derive(Clone, Debug)]
+pub struct RuleFill {
+ pub no_use_rule: HashMap,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 044dac24..9bf20692 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,6 @@
pub mod afterfact;
pub mod detections;
+pub mod fillter;
pub mod notify;
pub mod omikuji;
pub mod timeline;
diff --git a/src/main.rs b/src/main.rs
index 6b6b34c0..12d4c8a8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,10 +6,12 @@ use evtx::{EvtxParser, ParserSettings};
use hayabusa::detections::detection;
use hayabusa::detections::detection::EvtxRecordInfo;
use hayabusa::detections::print::AlertMessage;
+use hayabusa::fillter;
use hayabusa::omikuji::Omikuji;
use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timeline::Timeline};
use hhmmss::Hhmmss;
+use std::collections::HashMap;
use std::{
fs::{self, File},
path::PathBuf,
@@ -119,9 +121,29 @@ fn analysis_files(evtx_files: Vec) {
.unwrap_or("INFO")
.to_uppercase();
println!("Analyzing event files: {:?}", evtx_files.len());
+
+ //除外ルール前処理
+ let mut ids = String::from_utf8(fs::read("config/exclude-rules.txt").unwrap()).unwrap();
+ if !configs::CONFIG
+ .read()
+ .unwrap()
+ .args
+ .is_present("show-noisyalerts")
+ {
+ ids += &String::from_utf8(fs::read("config/noisy-rules.txt").unwrap()).unwrap();
+ }
+
+ let mut fill_ids = fillter::RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+
+ for v in ids.split_whitespace().next() {
+ fill_ids.no_use_rule.insert(v.to_string(), true);
+ }
let rule_files = detection::Detection::parse_rule_files(
level,
configs::CONFIG.read().unwrap().args.value_of("rules"),
+ fill_ids,
);
let mut detection = detection::Detection::new(rule_files);
for evtx_file in evtx_files {
diff --git a/src/yaml.rs b/src/yaml.rs
index b0ef5065..e67ac40b 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -3,6 +3,7 @@ extern crate yaml_rust;
use crate::detections::configs;
use crate::detections::print::AlertMessage;
+use crate::fillter::RuleFill;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
@@ -42,13 +43,18 @@ impl ParseYaml {
Ok(file_content)
}
- pub fn read_dir>(&mut self, path: P, level: &str) -> io::Result {
+ pub fn read_dir>(
+ &mut self,
+ path: P,
+ level: &str,
+ fill_ids: RuleFill,
+ ) -> io::Result {
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, fill_ids.clone())?;
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,18 @@ impl ParseYaml {
return Option::None;
}
+ //除外されたルールは無視する
+ match fill_ids
+ .no_use_rule
+ .get(&yaml_doc["id"].as_str().unwrap().to_string())
+ {
+ Some(_) => (),
+ None => {
+ self.ignorerule_count += 1;
+ return Option::None;
+ }
+ }
+
return Option::Some((filepath, yaml_doc));
})
.collect();
@@ -148,7 +167,14 @@ mod tests {
#[test]
fn test_read_dir_yaml() {
let mut yaml = yaml::ParseYaml::new();
- &yaml.read_dir("test_files/rules/yaml/".to_string(), &"".to_owned());
+ let mut fill_ids = fillter::RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ &yaml.read_dir(
+ "test_files/rules/yaml/".to_string(),
+ &"".to_owned(),
+ fill_ids,
+ );
assert_ne!(yaml.files.len(), 0);
}
@@ -183,7 +209,10 @@ 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();
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ yaml.read_dir(path.to_path_buf(), &"", fill_ids).unwrap();
assert_eq!(yaml.files.len(), 4);
}
@@ -191,36 +220,54 @@ 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(), &"INFO").unwrap();
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ yaml.read_dir(path.to_path_buf(), &"INFO", fill_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();
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ yaml.read_dir(path.to_path_buf(), &"LOW", fill_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();
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ yaml.read_dir(path.to_path_buf(), &"MEDIUM", fill_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();
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ yaml.read_dir(path.to_path_buf(), &"HIGH", fill_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();
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashMap::new(),
+ };
+ yaml.read_dir(path.to_path_buf(), &"CRITICAL", fill_ids)
+ .unwrap();
assert_eq!(yaml.files.len(), 1);
}
}
From 838a935d34e11bce146cfe8e2054a2998a9168f3 Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 2 Dec 2021 00:33:19 +0900
Subject: [PATCH 02/22] pass test
---
src/detections/detection.rs | 5 ++++-
src/main.rs | 2 +-
src/yaml.rs | 34 ++++++++++++++++++----------------
3 files changed, 23 insertions(+), 18 deletions(-)
diff --git a/src/detections/detection.rs b/src/detections/detection.rs
index 7e610c42..97ab8a9b 100644
--- a/src/detections/detection.rs
+++ b/src/detections/detection.rs
@@ -266,6 +266,9 @@ impl Detection {
fn test_parse_rule_files() {
let level = "INFO";
let opt_rule_path = Some("./test_files/rules/level_yaml");
- let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path);
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
+ };
+ let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, fill_ids);
assert_eq!(5, cole.len());
}
diff --git a/src/main.rs b/src/main.rs
index 12d4c8a8..075d116e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -134,7 +134,7 @@ fn analysis_files(evtx_files: Vec) {
}
let mut fill_ids = fillter::RuleFill {
- no_use_rule: HashMap::new(),
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
for v in ids.split_whitespace().next() {
diff --git a/src/yaml.rs b/src/yaml.rs
index e67ac40b..9d5ece71 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -140,7 +140,7 @@ impl ParseYaml {
//除外されたルールは無視する
match fill_ids
.no_use_rule
- .get(&yaml_doc["id"].as_str().unwrap().to_string())
+ .get(&yaml_doc["id"].as_str().unwrap_or("").to_string())
{
Some(_) => (),
None => {
@@ -161,16 +161,18 @@ impl ParseYaml {
mod tests {
use crate::yaml;
+ use crate::yaml::RuleFill;
+ use std::collections::HashMap;
use std::path::Path;
use yaml_rust::YamlLoader;
#[test]
fn test_read_dir_yaml() {
let mut yaml = yaml::ParseYaml::new();
- let mut fill_ids = fillter::RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
- &yaml.read_dir(
+ let _ = &yaml.read_dir(
"test_files/rules/yaml/".to_string(),
&"".to_owned(),
fill_ids,
@@ -209,8 +211,8 @@ mod tests {
fn test_default_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let mut fill_ids = RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
yaml.read_dir(path.to_path_buf(), &"", fill_ids).unwrap();
assert_eq!(yaml.files.len(), 4);
@@ -220,8 +222,8 @@ mod tests {
fn test_info_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let mut fill_ids = RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
yaml.read_dir(path.to_path_buf(), &"INFO", fill_ids)
.unwrap();
@@ -231,8 +233,8 @@ mod tests {
fn test_low_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let mut fill_ids = RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
yaml.read_dir(path.to_path_buf(), &"LOW", fill_ids).unwrap();
assert_eq!(yaml.files.len(), 4);
@@ -241,8 +243,8 @@ mod tests {
fn test_medium_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let mut fill_ids = RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
yaml.read_dir(path.to_path_buf(), &"MEDIUM", fill_ids)
.unwrap();
@@ -252,8 +254,8 @@ mod tests {
fn test_high_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let mut fill_ids = RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
yaml.read_dir(path.to_path_buf(), &"HIGH", fill_ids)
.unwrap();
@@ -263,8 +265,8 @@ mod tests {
fn test_critical_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let mut fill_ids = RuleFill {
- no_use_rule: HashMap::new(),
+ let fill_ids = RuleFill {
+ no_use_rule: HashMap::from([("".to_string(), true)]),
};
yaml.read_dir(path.to_path_buf(), &"CRITICAL", fill_ids)
.unwrap();
From b9c415eab5b45a25d53b0389b60a17c0dd24e64a Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 2 Dec 2021 00:43:31 +0900
Subject: [PATCH 03/22] add
---
src/yaml.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/yaml.rs b/src/yaml.rs
index 9d5ece71..ecb241d3 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -225,7 +225,7 @@ mod tests {
let fill_ids = RuleFill {
no_use_rule: HashMap::from([("".to_string(), true)]),
};
- yaml.read_dir(path.to_path_buf(), &"INFO", fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"informational", fill_ids)
.unwrap();
assert_eq!(yaml.files.len(), 5);
}
From c961c3768c506c5611e7c63914d8add0ec3fbac3 Mon Sep 17 00:00:00 2001
From: ichiichi11
Date: Sat, 4 Dec 2021 18:46:11 +0900
Subject: [PATCH 04/22] change from hashmap to hashset and remove unnecessary
copy.
---
src/detections/detection.rs | 6 +++---
src/fillter.rs | 6 ++++--
src/main.rs | 8 ++++----
src/yaml.rs | 34 +++++++++++++++++-----------------
4 files changed, 28 insertions(+), 26 deletions(-)
diff --git a/src/detections/detection.rs b/src/detections/detection.rs
index b6e3afd0..d615c4ea 100644
--- a/src/detections/detection.rs
+++ b/src/detections/detection.rs
@@ -55,7 +55,7 @@ impl Detection {
pub fn parse_rule_files(
level: String,
rulespath: Option<&str>,
- fill_ids: RuleFill,
+ fill_ids: &RuleFill,
) -> Vec {
// ルールファイルのパースを実行
let mut rulefile_loader = ParseYaml::new();
@@ -274,8 +274,8 @@ fn test_parse_rule_files() {
let level = "informational";
let opt_rule_path = Some("./test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: std::collections::HashSet::new(),
};
- let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, fill_ids);
+ let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, &fill_ids);
assert_eq!(5, cole.len());
}
diff --git a/src/fillter.rs b/src/fillter.rs
index 0cd68b2a..058bd0fc 100644
--- a/src/fillter.rs
+++ b/src/fillter.rs
@@ -1,6 +1,8 @@
-use std::collections::HashMap;
+use std::collections::HashSet;
+
+
#[derive(Clone, Debug)]
pub struct RuleFill {
- pub no_use_rule: HashMap,
+ pub no_use_rule: HashSet,
}
diff --git a/src/main.rs b/src/main.rs
index 8ad991af..6c257089 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,7 +12,7 @@ use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timeline::Timeline};
use hhmmss::Hhmmss;
use serde_json::Value;
-use std::collections::HashMap;
+use std::collections::HashSet;
use std::{
fs::{self, File},
path::PathBuf,
@@ -135,16 +135,16 @@ fn analysis_files(evtx_files: Vec) {
}
let mut fill_ids = fillter::RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
for v in ids.split_whitespace().next() {
- fill_ids.no_use_rule.insert(v.to_string(), true);
+ fill_ids.no_use_rule.insert(v.to_string());
}
let rule_files = detection::Detection::parse_rule_files(
level,
configs::CONFIG.read().unwrap().args.value_of("rules"),
- fill_ids,
+ &fill_ids,
);
let mut detection = detection::Detection::new(rule_files);
for evtx_file in evtx_files {
diff --git a/src/yaml.rs b/src/yaml.rs
index c32d6950..98fccf0a 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -47,14 +47,14 @@ impl ParseYaml {
&mut self,
path: P,
level: &str,
- fill_ids: RuleFill,
+ fill_ids: &RuleFill,
) -> io::Result {
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, fill_ids.clone())?;
+ self.read_dir(entry.path(), level, fill_ids)?;
return io::Result::Ok(ret);
}
// ファイル以外は無視
@@ -162,7 +162,7 @@ mod tests {
use crate::yaml;
use crate::yaml::RuleFill;
- use std::collections::HashMap;
+ use std::collections::HashSet;
use std::path::Path;
use yaml_rust::YamlLoader;
@@ -170,12 +170,12 @@ mod tests {
fn test_read_dir_yaml() {
let mut yaml = yaml::ParseYaml::new();
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
let _ = &yaml.read_dir(
"test_files/rules/yaml/".to_string(),
&"".to_owned(),
- fill_ids,
+ &fill_ids,
);
assert_ne!(yaml.files.len(), 0);
}
@@ -212,9 +212,9 @@ mod tests {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"", fill_ids).unwrap();
+ yaml.read_dir(path.to_path_buf(), &"", &fill_ids).unwrap();
assert_eq!(yaml.files.len(), 5);
}
@@ -223,9 +223,9 @@ mod tests {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"informational", fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"informational", &fill_ids)
.unwrap();
assert_eq!(yaml.files.len(), 5);
}
@@ -234,9 +234,9 @@ mod tests {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"LOW", fill_ids).unwrap();
+ yaml.read_dir(path.to_path_buf(), &"LOW", &fill_ids).unwrap();
assert_eq!(yaml.files.len(), 4);
}
#[test]
@@ -244,9 +244,9 @@ mod tests {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"MEDIUM", fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"MEDIUM", &fill_ids)
.unwrap();
assert_eq!(yaml.files.len(), 3);
}
@@ -255,9 +255,9 @@ mod tests {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"HIGH", fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"HIGH", &fill_ids)
.unwrap();
assert_eq!(yaml.files.len(), 2);
}
@@ -266,9 +266,9 @@ mod tests {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
let fill_ids = RuleFill {
- no_use_rule: HashMap::from([("".to_string(), true)]),
+ no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"CRITICAL", fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"CRITICAL", &fill_ids)
.unwrap();
assert_eq!(yaml.files.len(), 1);
}
From 916921455349f0ff6232acc49360dec1275004c5 Mon Sep 17 00:00:00 2001
From: ichiichi11
Date: Sat, 4 Dec 2021 19:09:41 +0900
Subject: [PATCH 05/22] fix bug.
---
src/main.rs | 3 ++-
src/yaml.rs | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 6c257089..15255e6e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -131,6 +131,7 @@ fn analysis_files(evtx_files: Vec) {
.args
.is_present("show-noisyalerts")
{
+ ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最後の行が一行にまとめられてしまう。
ids += &String::from_utf8(fs::read("config/noisy-rules.txt").unwrap()).unwrap();
}
@@ -138,7 +139,7 @@ fn analysis_files(evtx_files: Vec) {
no_use_rule: HashSet::new(),
};
- for v in ids.split_whitespace().next() {
+ for v in ids.split_whitespace() {
fill_ids.no_use_rule.insert(v.to_string());
}
let rule_files = detection::Detection::parse_rule_files(
diff --git a/src/yaml.rs b/src/yaml.rs
index 98fccf0a..8c89ec01 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -142,8 +142,8 @@ impl ParseYaml {
.no_use_rule
.get(&yaml_doc["id"].as_str().unwrap_or("").to_string())
{
- Some(_) => (),
- None => {
+ None => (),
+ Some(_) => {
self.ignorerule_count += 1;
return Option::None;
}
From 191d1df9f05512cbb6fd0234187f93bb3df03705 Mon Sep 17 00:00:00 2001
From: ichiichi11
Date: Sat, 4 Dec 2021 19:23:50 +0900
Subject: [PATCH 06/22] add exclude files and fix bugs.
---
config/exclude-rules.txt | 2 ++
src/fillter.rs | 2 --
src/main.rs | 9 +++++++--
src/yaml.rs | 19 ++++++++++---------
4 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/config/exclude-rules.txt b/config/exclude-rules.txt
index e69de29b..69422602 100644
--- a/config/exclude-rules.txt
+++ b/config/exclude-rules.txt
@@ -0,0 +1,2 @@
+4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6
+c92f1896-d1d2-43c3-92d5-7a5b35c217bb
\ No newline at end of file
diff --git a/src/fillter.rs b/src/fillter.rs
index 058bd0fc..d3ddb429 100644
--- a/src/fillter.rs
+++ b/src/fillter.rs
@@ -1,7 +1,5 @@
use std::collections::HashSet;
-
-
#[derive(Clone, Debug)]
pub struct RuleFill {
pub no_use_rule: HashSet,
diff --git a/src/main.rs b/src/main.rs
index 15255e6e..86050cbd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -131,7 +131,7 @@ fn analysis_files(evtx_files: Vec) {
.args
.is_present("show-noisyalerts")
{
- ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最後の行が一行にまとめられてしまう。
+ ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最後の行が一行にまとめられてしまう。
ids += &String::from_utf8(fs::read("config/noisy-rules.txt").unwrap()).unwrap();
}
@@ -140,7 +140,12 @@ fn analysis_files(evtx_files: Vec) {
};
for v in ids.split_whitespace() {
- fill_ids.no_use_rule.insert(v.to_string());
+ let v = v.to_string();
+ if v.is_empty() {
+ // 空行は無視する。
+ continue;
+ }
+ fill_ids.no_use_rule.insert(v);
}
let rule_files = detection::Detection::parse_rule_files(
level,
diff --git a/src/yaml.rs b/src/yaml.rs
index 8c89ec01..da18486c 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -138,14 +138,14 @@ impl ParseYaml {
}
//除外されたルールは無視する
- match fill_ids
- .no_use_rule
- .get(&yaml_doc["id"].as_str().unwrap_or("").to_string())
- {
- None => (),
- Some(_) => {
- self.ignorerule_count += 1;
- return Option::None;
+ let rule_id = &yaml_doc["id"].as_str();
+ if rule_id.is_some() {
+ match fill_ids.no_use_rule.get(&rule_id.unwrap_or("").to_string()) {
+ None => (),
+ Some(_) => {
+ self.ignorerule_count += 1;
+ return Option::None;
+ }
}
}
@@ -236,7 +236,8 @@ mod tests {
let fill_ids = RuleFill {
no_use_rule: HashSet::new(),
};
- yaml.read_dir(path.to_path_buf(), &"LOW", &fill_ids).unwrap();
+ yaml.read_dir(path.to_path_buf(), &"LOW", &fill_ids)
+ .unwrap();
assert_eq!(yaml.files.len(), 4);
}
#[test]
From 493c5ddec1265d3b334646b0017dce2114e1f806 Mon Sep 17 00:00:00 2001
From: DustInDark
Date: Tue, 7 Dec 2021 01:52:27 +0900
Subject: [PATCH 07/22] Trivia/eastereggs#212 (#266)
* add ninja arts #212
* add takoyakiday eggs #212
* add christmas eggs #212
* add happy newyear eggs #212
* changed encode from UTF-8 BOM to UTF-8
* add output easteregg #212
- changed analysis datetime from Utc to Local
- added output easteregg #213
* changed happynewyear arts #212
* fix ninja day #212
* fix christmas #212
---
art/christmas.txt | 13 +++++++++++++
art/happynewyear.txt | 10 ++++++++++
art/logo.txt | 2 +-
art/ninja.txt | 37 +++++++++++++++++++++++++++++++++++++
art/takoyaki.txt | 43 +++++++++++++++++++++++++++++++++++++++++++
src/main.rs | 33 +++++++++++++++++++++++++++++----
6 files changed, 133 insertions(+), 5 deletions(-)
create mode 100644 art/christmas.txt
create mode 100644 art/happynewyear.txt
create mode 100644 art/ninja.txt
create mode 100644 art/takoyaki.txt
diff --git a/art/christmas.txt b/art/christmas.txt
new file mode 100644
index 00000000..48a94f93
--- /dev/null
+++ b/art/christmas.txt
@@ -0,0 +1,13 @@
+ /⌒\、__/⌒ ̄}
+ \__(__)__/
+ 〃〓/ ̄ > <  ̄\〓〃
+ ミ☆/: (:: ::):: >☆彡
+ ★≡〃\/〉:: ::〈\/ ≡〃★
+ ●※○ ^^^^^^^^ ○※●
+〃≡★ Merry Christmas ★≡〃
+ ☆〓 〓☆
+ 〃≡★ (;) ★≡〃
+ ●※○- ,_】【_, ,-○※●
+ ★〃≡〓 ○ 〓≡〃★
+ ミ☆-★※★-☆彡
+ ●
\ No newline at end of file
diff --git a/art/happynewyear.txt b/art/happynewyear.txt
new file mode 100644
index 00000000..14c2bc91
--- /dev/null
+++ b/art/happynewyear.txt
@@ -0,0 +1,10 @@
+ _〆
+ (∴)
+ ( ̄ ̄ ̄)
+ <( ̄ ̄ ̄ ̄)>
+ [二◆二二◆二]
+ |◇ ● ◇|
+ |◆ ◆|
+ |____|
+
+ A Happy New Year!!
diff --git a/art/logo.txt b/art/logo.txt
index 9e74bef3..3e2f102e 100644
--- a/art/logo.txt
+++ b/art/logo.txt
@@ -1,4 +1,4 @@
-██╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██╗███████╗ █████╗
+██╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██╗███████╗ █████╗
██║ ██║██╔══██╗╚██╗ ██╔╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗
███████║███████║ ╚████╔╝ ███████║██████╔╝██║ ██║███████╗███████║
██╔══██║██╔══██║ ╚██╔╝ ██╔══██║██╔══██╗██║ ██║╚════██║██╔══██║
diff --git a/art/ninja.txt b/art/ninja.txt
new file mode 100644
index 00000000..505ddce6
--- /dev/null
+++ b/art/ninja.txt
@@ -0,0 +1,37 @@
+
+ Today is Ninja Day (2/22)!
+
+ .`,I>>+<;"'
+ .,}u#zcccccz*#W&jI.
+ `\&zccccccccccccccz&B~
+ `u8zcccccccccccccccccc*%v.
+ "BMcccccccccccccccccccczWBn
+ . '$Wccccccccccccccccccccc*&1
+ 't$@x. !@zccccMccccccccccccccccccM/
+ ^|$%M8i. +Wcccc&McccccccccccccMccccc$[
+ .1&v&$M^ `cccc*$#WWMMM##***zzc%*ccccB$
+ ^c$#8$} ,*ccc8@xn@x]}{11{f$cn#%cccz8<
+ ;B$B@z' IBccz$%(z$x}[[[]])$#/-$ccccW(
+ .u%M$$< '$zcW$t .`^",""`'. c*ccz@^
+ 'c$$*%c' }Wz%$i ..'`'. 1Mcz%;
+ -$@B$%, cz%$Mr1|v&&&&88#j[;,tWzB)
+ ,%Bzz@{ 'W8$#cccccz&WMcccccc88@z..^,l~+?-+!,'
+ .f$&c#*'.z$zcccccc$:c#ccccc8$%tM&WWMMMMMW%$$#'
+ l@8W$B,.tWzccccc@,?&ccczWc_{tzBB%%8%B@$$@r]`
+ 'c$$$${~$@Wzcccc']8czWB8@, .'^+v$$$$$$$%j~`
+ `?*$$$$$$$MM%8M*c j88%MW$$%uz@$%f!``""^`.
+ ^f$$$$$8W#z8%zc*W$r *Wz*z*M%zcccz#M&%x`
+ .ItB$&WM*z$&cccz@$%&W$; vMM#vW@*cccccccccz\:`.
+ ;@$$B*cccccM$*cccW$$~c$' ~n}?(@*ccccccccccccc#8#{,.
+ '"_zWM#ccccccccB%ccczB$$$B1+`,$$$#cccccccccccccccczM%B[.
+ `/8zcccccccccccccM$WccczB$z#Mz@`z$WcccczWcccccccccccccc#@B:
+ [B#cccccccccccccccc%$*ccc&MnnBz@`%$8cccc8&cccccccccccccccz8$!
+ .r%zcccccccccccccccccz$%cc8@nnnun%#$$WcccM@zccccccccccccccccc&$i
+ .u@zcccccccccccccccccccW$W&$Wnnnn8/Wu%Mcc*$Mccccccccccccccccccc8$]
+ z$WcccccccccccccccccccccB$$BvnnnM")vnu@&z%Wccccccccccccccccccccz%$j.
+ l$@zcccccccccccccccccccz&$&znnnn#/ tznnu*B@*cccccccccccccccccccccz@$#'
+ c$Wcccccccccc*Wzccccc#8B#unnnnnc$$cWBunnnn#$BW*ccccccccccccccz*ccc#$$W'
+ '$@zccccccccccz$BzczW%Wvnnnnnnv%$$$*M$@nnnnnuW$$@&zcccccccccc#@*cccc%$$#
+ [$#ccccccccccccB$%WMcnnnnnnnnz$$$B&cc#@8nnnnnnu#@$$&*cccccccMB*ccccc#$$$,
+ @%ccccccccccccz$#cxcnnnnnnnnM$$$@zcccc*$8nnnnnnnnW8$$%MMMM*#&zccccccc@$$|
+ "$*cccccccccccc#$cnx@WnnnnnnW$$$$Wccccc#@$8unnnn*@Wu&@$$$$$$@#cccccccc&$$W
diff --git a/art/takoyaki.txt b/art/takoyaki.txt
new file mode 100644
index 00000000..29320d36
--- /dev/null
+++ b/art/takoyaki.txt
@@ -0,0 +1,43 @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@#------@@@@@@@@--------@@@@@@@@--------@@@@@@@@--------@@@@@@@@--------@@@@@@@@------#@@
+@@* @@@@@@@% @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
+@@* @@@@@@@% @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
+@@#------********--------********--------********--------********--------********=-----#@@
+@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
+@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
+@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
+@@@@@@@@@-------=@@@@@@@%-------=@@@@@@@@-------=@@@@@@@@-------=@@@@@@@@-------=@@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=:@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.+@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@= *@@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%:-@@@@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.*@@@@@@@@@@@@
+@@@@@@@@@@@%*=-:::-=*@@@@@@@@@%*=-:::-+#@@@@@@@@@#+=-::-=+#@@@@@@@@@#+--::: =%@@@@@@@@@@@@
+@@@@@@@@@*: = :#@@@@+. -%@@@@= =@@@@%- .+@@@@@@@@@@
+@@@@@@@@: :: #* -@%. - *= =@* -. =#. *@+ :: -#: .#@@@@@@@@
+@@@@@@@. : -. . : .= : : = . %@@@@@@@
+@@@@@@+ =*. -+=. .++ =+= :*= =+- -*- .++- . -@@@@@@@
+@@@@@@= *@@@@=-#@@@@+:+@% .*@@@%==#@@@@=:*@# :#@@@#==%@@@%--#@+ -%@@@*-=@@@@#--%@-:@@@@@@@
+@@@@-== #@@@@@@@@@@@@@@@+ %@@@@@@@@@@@@@@@- .@@@@@@@@@@@@@@@@. :@@@@@@@@@@@@@@@@ :=:@@@@@
+@@@@#. =*#@@@@@@@@@@@* *-.%@@@@@@@@@@@@@= #::@@@@@@@@@@@@@@:.# -@@@@@@@@@@@%*=. .*@@@@@
+@@@@@@#- .:-=+*#%*::%@@*.-#@@@@@@@@@+.-%@@=.=%@@@@@@@@%=.=@@@=.+%#*+=-:. :*@@@@@@@
+@@@@@@@@@*- ..::- :====-. .=++++++: .-====-. :::.. :*@@@@@@@@@@
+@@@@@@@@@@@@%+-. .-+#@@@@@@@@@@@@@
+@@@@@@@@@@@@@@@@@%#*+-::. ..:-=*#%@@@@@@@@@@@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@%##***++++=============++++***##%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@======@@#--%@@#=+@@+=#@#=--=#@#==@@==#@@+-=@@@+=*@@==%==#@@@*====+%@@@#--%@@*=*@%==%==@@
+@@*+ ##@@. .@@* .@= :@- .+*: -# %% +@# -@@- =@: =@ *@@@- -#+ #@@. .@@= .@* % %@
+@@@# @@@: =- -@* .@@ #@@% @# *@% *. *@- =@@ #@@@= =@@= -@- -= -@@+ .#@:.@@
+@@@# @@+ :: ** .@: :@+ -- +@@# #@@: .:: %- =%. =@ *@@@- :+- .%* :: +@@+ @@@=-@@
+@@@%++@@*+%@@#+*%+*@@*+#@@*++*@@@@@**@@@++@@@*+##+#@%++%++%@@@#+++*#@@*+%@@%+*@@%**@@@*+@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@@@@@@@@:.......@@@@@@@@:.......@@@@@@@@:.......@@@@@@@@:.......%@@@@@@@:.......@@@@@@@@@
+@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
+@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
+@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
+@@#:::::-########::::::::########::::::::########::::::::########::::::::########::::::#@@
+@@* .@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
+@@* .@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
+@@%+++++*@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++%@@
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index dd4d59df..07b9270f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,8 @@
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;
@@ -12,6 +13,7 @@ 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,
@@ -22,9 +24,15 @@ use std::{
const MAX_DETECT_RECORDS: usize = 40000;
fn main() {
+ let analysis_start_time: DateTime = 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 +41,6 @@ fn main() {
);
return;
}
- let analysis_start_time: DateTime = Utc::now();
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
if !filepath.ends_with(".evtx") {
AlertMessage::alert(
@@ -64,7 +71,7 @@ fn main() {
print_contributors();
return;
}
- let analysis_end_time: DateTime = Utc::now();
+ let analysis_end_time: DateTime = Local::now();
let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time);
println!("Elapsed Time: {}", &analysis_duration.hhmmssxxx());
println!("");
@@ -237,12 +244,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;
From c8473b766887bce8ac6cdfb8ed9e06521870ed79 Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Wed, 8 Dec 2021 23:16:46 +0900
Subject: [PATCH 08/22] remove comment
---
config/noisy-rules.txt | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/config/noisy-rules.txt b/config/noisy-rules.txt
index 6e03bcf7..f25d1a33 100644
--- a/config/noisy-rules.txt
+++ b/config/noisy-rules.txt
@@ -1,5 +1,5 @@
-0f06a3a5-6a09-413f-8743-e6cf35561297 # sysmon_wmi_event_subscription.yml
-b0d77106-7bb0-41fe-bd94-d1752164d066 # win_rare_schtasks_creations.yml
-66bfef30-22a5-4fcd-ad44-8d81e60922ae # win_rare_service_installs.yml
-e98374a6-e2d9-4076-9b5c-11bdb2569995 # win_susp_failed_logons_single_source.yml
-6309ffc4-8fa2-47cf-96b8-a2f72e58e538 # win_susp_failed_logons_single_source2.yml
\ No newline at end of file
+0f06a3a5-6a09-413f-8743-e6cf35561297
+b0d77106-7bb0-41fe-bd94-d1752164d066
+66bfef30-22a5-4fcd-ad44-8d81e60922ae
+e98374a6-e2d9-4076-9b5c-11bdb2569995
+6309ffc4-8fa2-47cf-96b8-a2f72e58e538
\ No newline at end of file
From b9831ca38a14823c2c0f7ad27103aa9a02c15222 Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 9 Dec 2021 00:57:40 +0900
Subject: [PATCH 09/22] add test for exclude rules
---
src/detections/detection.rs | 10 ++++----
src/fillter.rs | 30 ++++++++++++++++++++++++
src/main.rs | 27 +---------------------
src/yaml.rs | 46 +++++++++++++++++--------------------
4 files changed, 56 insertions(+), 57 deletions(-)
diff --git a/src/detections/detection.rs b/src/detections/detection.rs
index 52dab334..1904d5fe 100644
--- a/src/detections/detection.rs
+++ b/src/detections/detection.rs
@@ -11,7 +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::fillter::RuleFill;
+use crate::fillter;
use crate::yaml::ParseYaml;
use std::sync::Arc;
@@ -55,7 +55,7 @@ impl Detection {
pub fn parse_rule_files(
level: String,
rulespath: Option<&str>,
- fill_ids: &RuleFill,
+ fill_ids: &fillter::RuleFill,
) -> Vec {
// ルールファイルのパースを実行
let mut rulefile_loader = ParseYaml::new();
@@ -275,9 +275,7 @@ impl Detection {
fn test_parse_rule_files() {
let level = "informational";
let opt_rule_path = Some("./test_files/rules/level_yaml");
- let fill_ids = RuleFill {
- no_use_rule: std::collections::HashSet::new(),
- };
- let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, &fill_ids);
+ let cole =
+ Detection::parse_rule_files(level.to_owned(), opt_rule_path, &fillter::exclude_ids());
assert_eq!(5, cole.len());
}
diff --git a/src/fillter.rs b/src/fillter.rs
index d3ddb429..7b61d175 100644
--- a/src/fillter.rs
+++ b/src/fillter.rs
@@ -1,6 +1,36 @@
+use crate::detections::configs;
use std::collections::HashSet;
+use std::fs;
#[derive(Clone, Debug)]
pub struct RuleFill {
pub no_use_rule: HashSet,
}
+
+pub fn exclude_ids() -> RuleFill {
+ let mut ids = String::from_utf8(fs::read("config/exclude-rules.txt").unwrap()).unwrap();
+ if !configs::CONFIG
+ .read()
+ .unwrap()
+ .args
+ .is_present("show-noisyalerts")
+ {
+ ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最後の行が一行にまとめられてしまう。
+ ids += &String::from_utf8(fs::read("config/noisy-rules.txt").unwrap()).unwrap();
+ }
+
+ let mut fill_ids = RuleFill {
+ no_use_rule: HashSet::new(),
+ };
+
+ for v in ids.split_whitespace() {
+ let v = v.to_string();
+ if v.is_empty() {
+ // 空行は無視する。
+ continue;
+ }
+ fill_ids.no_use_rule.insert(v);
+ }
+
+ return fill_ids;
+}
diff --git a/src/main.rs b/src/main.rs
index f4e86cf6..525256eb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,7 +13,6 @@ use hayabusa::{detections::configs, timeline::timeline::Timeline};
use hhmmss::Hhmmss;
use pbr::ProgressBar;
use serde_json::Value;
-use std::collections::HashSet;
use std::{
fs::{self, File},
path::PathBuf,
@@ -124,34 +123,10 @@ fn analysis_files(evtx_files: Vec) {
.to_uppercase();
println!("Analyzing event files: {:?}", evtx_files.len());
- //除外ルール前処理
- let mut ids = String::from_utf8(fs::read("config/exclude-rules.txt").unwrap()).unwrap();
- if !configs::CONFIG
- .read()
- .unwrap()
- .args
- .is_present("show-noisyalerts")
- {
- ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最後の行が一行にまとめられてしまう。
- ids += &String::from_utf8(fs::read("config/noisy-rules.txt").unwrap()).unwrap();
- }
-
- let mut fill_ids = fillter::RuleFill {
- no_use_rule: HashSet::new(),
- };
-
- for v in ids.split_whitespace() {
- let v = v.to_string();
- if v.is_empty() {
- // 空行は無視する。
- continue;
- }
- fill_ids.no_use_rule.insert(v);
- }
let rule_files = detection::Detection::parse_rule_files(
level,
configs::CONFIG.read().unwrap().args.value_of("rules"),
- &fill_ids,
+ &fillter::exclude_ids(),
);
let mut pb = ProgressBar::new(evtx_files.len() as u64);
let mut detection = detection::Detection::new(rule_files);
diff --git a/src/yaml.rs b/src/yaml.rs
index da18486c..a06aac28 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -160,6 +160,7 @@ impl ParseYaml {
#[cfg(test)]
mod tests {
+ use crate::fillter;
use crate::yaml;
use crate::yaml::RuleFill;
use std::collections::HashSet;
@@ -211,10 +212,8 @@ mod tests {
fn test_default_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
- yaml.read_dir(path.to_path_buf(), &"", &fill_ids).unwrap();
+ yaml.read_dir(path.to_path_buf(), &"", &fillter::exclude_ids())
+ .unwrap();
assert_eq!(yaml.files.len(), 5);
}
@@ -222,21 +221,19 @@ mod tests {
fn test_info_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
- yaml.read_dir(path.to_path_buf(), &"informational", &fill_ids)
- .unwrap();
+ yaml.read_dir(
+ path.to_path_buf(),
+ &"informational",
+ &fillter::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");
- let fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
- yaml.read_dir(path.to_path_buf(), &"LOW", &fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"LOW", &fillter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 4);
}
@@ -244,10 +241,7 @@ mod tests {
fn test_medium_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
- yaml.read_dir(path.to_path_buf(), &"MEDIUM", &fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"MEDIUM", &fillter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 3);
}
@@ -255,10 +249,7 @@ mod tests {
fn test_high_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
- yaml.read_dir(path.to_path_buf(), &"HIGH", &fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"HIGH", &fillter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 2);
}
@@ -266,11 +257,16 @@ mod tests {
fn test_critical_level_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
let path = Path::new("test_files/rules/level_yaml");
- let fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
- yaml.read_dir(path.to_path_buf(), &"CRITICAL", &fill_ids)
+ yaml.read_dir(path.to_path_buf(), &"CRITICAL", &fillter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 1);
}
+ #[test]
+ fn test_exclude_rules_file() {
+ let mut yaml = yaml::ParseYaml::new();
+ let path = Path::new("test_files/rules/exclude_rules");
+ yaml.read_dir(path.to_path_buf(), &"", &fillter::exclude_ids())
+ .unwrap();
+ assert_eq!(yaml.ignorerule_count, 1);
+ }
}
From 3f11e426bad4319c87d8ce060e0a52a04850eca8 Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 9 Dec 2021 01:06:50 +0900
Subject: [PATCH 10/22] add test rule file
---
test_files/rules/exclude_rules/1.yml | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 test_files/rules/exclude_rules/1.yml
diff --git a/test_files/rules/exclude_rules/1.yml b/test_files/rules/exclude_rules/1.yml
new file mode 100644
index 00000000..76e3e73d
--- /dev/null
+++ b/test_files/rules/exclude_rules/1.yml
@@ -0,0 +1,19 @@
+title: Sysmon Check command lines
+id : 4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6
+description: hogehoge
+enabled: true
+author: Yea
+logsource:
+ product: windows
+detection:
+ selection:
+ EventLog: Sysmon
+ EventID: 1
+ CommandLine: '*'
+ condition: selection
+falsepositives:
+ - unknown
+level: medium
+output: 'CommandLine=%CommandLine%¥nParentImage=%ParentImage%'
+creation_date: 2020/11/8
+updated_date: 2020/11/8
\ No newline at end of file
From 360d80b578113a9510a88d2d437e00a91ffa62bc Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 9 Dec 2021 01:15:01 +0900
Subject: [PATCH 11/22] clear
---
src/yaml.rs | 2 +-
test_files/rules/exclude_rules/1.yml | 19 -------------------
test_files/rules/yaml/1.yml | 1 +
3 files changed, 2 insertions(+), 20 deletions(-)
delete mode 100644 test_files/rules/exclude_rules/1.yml
diff --git a/src/yaml.rs b/src/yaml.rs
index a06aac28..e724fb6c 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -264,7 +264,7 @@ mod tests {
#[test]
fn test_exclude_rules_file() {
let mut yaml = yaml::ParseYaml::new();
- let path = Path::new("test_files/rules/exclude_rules");
+ let path = Path::new("test_files/rules/yaml");
yaml.read_dir(path.to_path_buf(), &"", &fillter::exclude_ids())
.unwrap();
assert_eq!(yaml.ignorerule_count, 1);
diff --git a/test_files/rules/exclude_rules/1.yml b/test_files/rules/exclude_rules/1.yml
deleted file mode 100644
index 76e3e73d..00000000
--- a/test_files/rules/exclude_rules/1.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-title: Sysmon Check command lines
-id : 4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6
-description: hogehoge
-enabled: true
-author: Yea
-logsource:
- product: windows
-detection:
- selection:
- EventLog: Sysmon
- EventID: 1
- CommandLine: '*'
- condition: selection
-falsepositives:
- - unknown
-level: medium
-output: 'CommandLine=%CommandLine%¥nParentImage=%ParentImage%'
-creation_date: 2020/11/8
-updated_date: 2020/11/8
\ No newline at end of file
diff --git a/test_files/rules/yaml/1.yml b/test_files/rules/yaml/1.yml
index 5f844d26..c34d0bc2 100644
--- a/test_files/rules/yaml/1.yml
+++ b/test_files/rules/yaml/1.yml
@@ -1,4 +1,5 @@
title: Sysmon Check command lines
+id : 4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6
description: hogehoge
enabled: true
author: Yea
From db3616b56dbaab010ce39de023b66e94858f1e6c Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 9 Dec 2021 01:29:23 +0900
Subject: [PATCH 12/22] add test rule files
---
.../builtin/win_hidden_user_creation.yml | 1 -
...win_user_added_to_local_administrators.yml | 1 -
rules/sigma/builtin/win_user_creation.yml | 1 -
.../other/win_exchange_cve_2021_42321.yml | 1 -
src/yaml.rs | 4 +--
test_files/rules/yaml/1.yml | 4 +--
test_files/rules/yaml/exclude1.yml | 19 +++++++++++
test_files/rules/yaml/exclude2.yml | 21 ++++++++++++
test_files/rules/yaml/exclude3.yml | 28 +++++++++++++++
test_files/rules/yaml/exclude4.yml | 30 ++++++++++++++++
test_files/rules/yaml/exclude5.yml | 31 +++++++++++++++++
test_files/rules/yaml/noisy1.yml | 25 ++++++++++++++
test_files/rules/yaml/noisy2.yml | 31 +++++++++++++++++
test_files/rules/yaml/noisy3.yml | 26 ++++++++++++++
test_files/rules/yaml/noisy4.yml | 33 ++++++++++++++++++
test_files/rules/yaml/noisy5.yml | 34 +++++++++++++++++++
16 files changed, 281 insertions(+), 9 deletions(-)
create mode 100644 test_files/rules/yaml/exclude1.yml
create mode 100644 test_files/rules/yaml/exclude2.yml
create mode 100644 test_files/rules/yaml/exclude3.yml
create mode 100644 test_files/rules/yaml/exclude4.yml
create mode 100644 test_files/rules/yaml/exclude5.yml
create mode 100644 test_files/rules/yaml/noisy1.yml
create mode 100644 test_files/rules/yaml/noisy2.yml
create mode 100644 test_files/rules/yaml/noisy3.yml
create mode 100644 test_files/rules/yaml/noisy4.yml
create mode 100644 test_files/rules/yaml/noisy5.yml
diff --git a/rules/sigma/builtin/win_hidden_user_creation.yml b/rules/sigma/builtin/win_hidden_user_creation.yml
index 526c96dd..45f43c4a 100644
--- a/rules/sigma/builtin/win_hidden_user_creation.yml
+++ b/rules/sigma/builtin/win_hidden_user_creation.yml
@@ -1,4 +1,3 @@
-
title: Hidden Local User Creation
author: Christian Burkard
date: 2021/05/03
diff --git a/rules/sigma/builtin/win_user_added_to_local_administrators.yml b/rules/sigma/builtin/win_user_added_to_local_administrators.yml
index 4fe138b6..06b76c48 100644
--- a/rules/sigma/builtin/win_user_added_to_local_administrators.yml
+++ b/rules/sigma/builtin/win_user_added_to_local_administrators.yml
@@ -1,4 +1,3 @@
-
title: User Added to Local Administrators
author: Florian Roth
date: 2017/03/14
diff --git a/rules/sigma/builtin/win_user_creation.yml b/rules/sigma/builtin/win_user_creation.yml
index aaa45500..27ec53cc 100644
--- a/rules/sigma/builtin/win_user_creation.yml
+++ b/rules/sigma/builtin/win_user_creation.yml
@@ -1,4 +1,3 @@
-
title: Local User Creation
author: Patrick Bareiss
date: 2019/04/18
diff --git a/rules/sigma/other/win_exchange_cve_2021_42321.yml b/rules/sigma/other/win_exchange_cve_2021_42321.yml
index 77e2a949..e17e37cf 100644
--- a/rules/sigma/other/win_exchange_cve_2021_42321.yml
+++ b/rules/sigma/other/win_exchange_cve_2021_42321.yml
@@ -1,4 +1,3 @@
-
title: Possible Exploitation of Exchange RCE CVE-2021-42321
author: Florian Roth, @testanull
date: 2021/11/18
diff --git a/src/yaml.rs b/src/yaml.rs
index e724fb6c..7420410c 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -264,9 +264,9 @@ mod tests {
#[test]
fn test_exclude_rules_file() {
let mut yaml = yaml::ParseYaml::new();
- let path = Path::new("test_files/rules/yaml");
+ let path = Path::new("test_files/rules/");
yaml.read_dir(path.to_path_buf(), &"", &fillter::exclude_ids())
.unwrap();
- assert_eq!(yaml.ignorerule_count, 1);
+ assert_eq!(yaml.ignorerule_count, 10);
}
}
diff --git a/test_files/rules/yaml/1.yml b/test_files/rules/yaml/1.yml
index c34d0bc2..23a32d6a 100644
--- a/test_files/rules/yaml/1.yml
+++ b/test_files/rules/yaml/1.yml
@@ -1,5 +1,4 @@
title: Sysmon Check command lines
-id : 4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6
description: hogehoge
enabled: true
author: Yea
@@ -16,5 +15,4 @@ falsepositives:
level: medium
output: 'CommandLine=%CommandLine%¥nParentImage=%ParentImage%'
creation_date: 2020/11/8
-updated_date: 2020/11/8
-
+updated_date: 2020/11/8
\ No newline at end of file
diff --git a/test_files/rules/yaml/exclude1.yml b/test_files/rules/yaml/exclude1.yml
new file mode 100644
index 00000000..76e3e73d
--- /dev/null
+++ b/test_files/rules/yaml/exclude1.yml
@@ -0,0 +1,19 @@
+title: Sysmon Check command lines
+id : 4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6
+description: hogehoge
+enabled: true
+author: Yea
+logsource:
+ product: windows
+detection:
+ selection:
+ EventLog: Sysmon
+ EventID: 1
+ CommandLine: '*'
+ condition: selection
+falsepositives:
+ - unknown
+level: medium
+output: 'CommandLine=%CommandLine%¥nParentImage=%ParentImage%'
+creation_date: 2020/11/8
+updated_date: 2020/11/8
\ No newline at end of file
diff --git a/test_files/rules/yaml/exclude2.yml b/test_files/rules/yaml/exclude2.yml
new file mode 100644
index 00000000..e17e37cf
--- /dev/null
+++ b/test_files/rules/yaml/exclude2.yml
@@ -0,0 +1,21 @@
+title: Possible Exploitation of Exchange RCE CVE-2021-42321
+author: Florian Roth, @testanull
+date: 2021/11/18
+description: Detects log entries that appear in exploitation attempts against MS Exchange
+ RCE CVE-2021-42321
+detection:
+ condition: 'Cmdlet failed. Cmdlet Get-App, '
+falsepositives:
+- Unknown, please report false positives via https://github.com/SigmaHQ/sigma/issues
+id: c92f1896-d1d2-43c3-92d5-7a5b35c217bb
+level: critical
+logsource:
+ product: windows
+ service: msexchange-management
+references:
+- https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-42321
+status: experimental
+tags:
+- attack.lateral_movement
+- attack.t1210
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/exclude3.yml b/test_files/rules/yaml/exclude3.yml
new file mode 100644
index 00000000..45f43c4a
--- /dev/null
+++ b/test_files/rules/yaml/exclude3.yml
@@ -0,0 +1,28 @@
+title: Hidden Local User Creation
+author: Christian Burkard
+date: 2021/05/03
+description: Detects the creation of a local hidden user account which should not
+ happen for event ID 4720.
+detection:
+ SELECTION_1:
+ EventID: 4720
+ SELECTION_2:
+ TargetUserName: '*$'
+ condition: (SELECTION_1 and SELECTION_2)
+falsepositives:
+- unknown
+fields:
+- EventCode
+- AccountName
+id: 7b449a5e-1db5-4dd0-a2dc-4e3a67282538
+level: high
+logsource:
+ product: windows
+ service: security
+references:
+- https://twitter.com/SBousseaden/status/1387743867663958021
+status: experimental
+tags:
+- attack.persistence
+- attack.t1136.001
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/exclude4.yml b/test_files/rules/yaml/exclude4.yml
new file mode 100644
index 00000000..06b76c48
--- /dev/null
+++ b/test_files/rules/yaml/exclude4.yml
@@ -0,0 +1,30 @@
+title: User Added to Local Administrators
+author: Florian Roth
+date: 2017/03/14
+description: This rule triggers on user accounts that are added to the local Administrators
+ group, which could be legitimate activity or a sign of privilege escalation activity
+detection:
+ SELECTION_1:
+ EventID: 4732
+ SELECTION_2:
+ TargetUserName: Administr*
+ SELECTION_3:
+ TargetSid: S-1-5-32-544
+ SELECTION_4:
+ SubjectUserName: '*$'
+ condition: ((SELECTION_1 and (SELECTION_2 or SELECTION_3)) and not (SELECTION_4))
+falsepositives:
+- Legitimate administrative activity
+id: c265cf08-3f99-46c1-8d59-328247057d57
+level: medium
+logsource:
+ product: windows
+ service: security
+modified: 2021/07/07
+status: stable
+tags:
+- attack.privilege_escalation
+- attack.t1078
+- attack.persistence
+- attack.t1098
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/exclude5.yml b/test_files/rules/yaml/exclude5.yml
new file mode 100644
index 00000000..27ec53cc
--- /dev/null
+++ b/test_files/rules/yaml/exclude5.yml
@@ -0,0 +1,31 @@
+title: Local User Creation
+author: Patrick Bareiss
+date: 2019/04/18
+description: Detects local user creation on windows servers, which shouldn't happen
+ in an Active Directory environment. Apply this Sigma Use Case on your windows server
+ logs and not on your DC logs.
+detection:
+ SELECTION_1:
+ EventID: 4720
+ condition: SELECTION_1
+falsepositives:
+- Domain Controller Logs
+- Local accounts managed by privileged account management tools
+fields:
+- EventCode
+- AccountName
+- AccountDomain
+id: 66b6be3d-55d0-4f47-9855-d69df21740ea
+level: low
+logsource:
+ product: windows
+ service: security
+modified: 2020/08/23
+references:
+- https://patrick-bareiss.com/detecting-local-user-creation-in-ad-with-sigma/
+status: experimental
+tags:
+- attack.persistence
+- attack.t1136
+- attack.t1136.001
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/noisy1.yml b/test_files/rules/yaml/noisy1.yml
new file mode 100644
index 00000000..6ea217b6
--- /dev/null
+++ b/test_files/rules/yaml/noisy1.yml
@@ -0,0 +1,25 @@
+title: WMI Event Subscription
+author: Tom Ueltschi (@c_APT_ure)
+date: 2019/01/12
+description: Detects creation of WMI event subscription persistence method
+detection:
+ SELECTION_1:
+ EventID: 19
+ SELECTION_2:
+ EventID: 20
+ SELECTION_3:
+ EventID: 21
+ condition: (SELECTION_1 or SELECTION_2 or SELECTION_3)
+falsepositives:
+- exclude legitimate (vetted) use of WMI event subscription in your network
+id: 0f06a3a5-6a09-413f-8743-e6cf35561297
+level: high
+logsource:
+ category: wmi_event
+ product: windows
+status: experimental
+tags:
+- attack.t1084
+- attack.persistence
+- attack.t1546.003
+ruletype: SIGMA
\ No newline at end of file
diff --git a/test_files/rules/yaml/noisy2.yml b/test_files/rules/yaml/noisy2.yml
new file mode 100644
index 00000000..2296fba4
--- /dev/null
+++ b/test_files/rules/yaml/noisy2.yml
@@ -0,0 +1,31 @@
+title: Rare Schtasks Creations
+author: Florian Roth
+date: 2017/03/23
+description: Detects rare scheduled tasks creations that only appear a few times per
+ time frame and could reveal password dumpers, backdoor installs or other types of
+ malicious code
+detection:
+ SELECTION_1:
+ EventID: 4698
+ condition: SELECTION_1 | count() by TaskName < 5
+falsepositives:
+- Software installation
+- Software updates
+id: b0d77106-7bb0-41fe-bd94-d1752164d066
+level: low
+logsource:
+ definition: The Advanced Audit Policy setting Object Access > Audit Other Object
+ Access Events has to be configured to allow this detection (not in the baseline
+ recommendations by Microsoft). We also recommend extracting the Command field
+ from the embedded XML in the event data.
+ product: windows
+ service: security
+status: experimental
+tags:
+- attack.execution
+- attack.privilege_escalation
+- attack.persistence
+- attack.t1053
+- car.2013-08-001
+- attack.t1053.005
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/noisy3.yml b/test_files/rules/yaml/noisy3.yml
new file mode 100644
index 00000000..7e2071a0
--- /dev/null
+++ b/test_files/rules/yaml/noisy3.yml
@@ -0,0 +1,26 @@
+title: Rare Service Installs
+author: Florian Roth
+date: 2017/03/08
+description: Detects rare service installs that only appear a few times per time frame
+ and could reveal password dumpers, backdoor installs or other types of malicious
+ services
+detection:
+ SELECTION_1:
+ EventID: 7045
+ condition: SELECTION_1 | count() by ServiceFileName < 5
+falsepositives:
+- Software installation
+- Software updates
+id: 66bfef30-22a5-4fcd-ad44-8d81e60922ae
+level: low
+logsource:
+ product: windows
+ service: system
+status: experimental
+tags:
+- attack.persistence
+- attack.privilege_escalation
+- attack.t1050
+- car.2013-09-005
+- attack.t1543.003
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/noisy4.yml b/test_files/rules/yaml/noisy4.yml
new file mode 100644
index 00000000..39bbd1a3
--- /dev/null
+++ b/test_files/rules/yaml/noisy4.yml
@@ -0,0 +1,33 @@
+title: Failed Logins with Different Accounts from Single Source System
+author: Florian Roth
+date: 2017/01/10
+description: Detects suspicious failed logins with different user accounts from a
+ single source system
+detection:
+ SELECTION_1:
+ EventID: 529
+ SELECTION_2:
+ EventID: 4625
+ SELECTION_3:
+ TargetUserName: '*'
+ SELECTION_4:
+ WorkstationName: '*'
+ condition: ((SELECTION_1 or SELECTION_2) and SELECTION_3 and SELECTION_4) | count(TargetUserName)
+ by WorkstationName > 3
+falsepositives:
+- Terminal servers
+- Jump servers
+- Other multiuser systems like Citrix server farms
+- Workstations with frequently changing users
+id: e98374a6-e2d9-4076-9b5c-11bdb2569995
+level: medium
+logsource:
+ product: windows
+ service: security
+modified: 2021/09/21
+status: experimental
+tags:
+- attack.persistence
+- attack.privilege_escalation
+- attack.t1078
+ruletype: SIGMA
diff --git a/test_files/rules/yaml/noisy5.yml b/test_files/rules/yaml/noisy5.yml
new file mode 100644
index 00000000..ddfc134a
--- /dev/null
+++ b/test_files/rules/yaml/noisy5.yml
@@ -0,0 +1,34 @@
+title: Failed Logins with Different Accounts from Single Source System
+author: Florian Roth
+date: 2017/01/10
+description: Detects suspicious failed logins with different user accounts from a
+ single source system
+detection:
+ SELECTION_1:
+ EventID: 4776
+ SELECTION_2:
+ TargetUserName: '*'
+ SELECTION_3:
+ Workstation: '*'
+ condition: (SELECTION_1 and SELECTION_2 and SELECTION_3) | count(TargetUserName)
+ by Workstation > 3
+falsepositives:
+- Terminal servers
+- Jump servers
+- Other multiuser systems like Citrix server farms
+- Workstations with frequently changing users
+id: 6309ffc4-8fa2-47cf-96b8-a2f72e58e538
+level: medium
+logsource:
+ product: windows
+ service: security
+modified: 2021/09/21
+related:
+- id: e98374a6-e2d9-4076-9b5c-11bdb2569995
+ type: derived
+status: experimental
+tags:
+- attack.persistence
+- attack.privilege_escalation
+- attack.t1078
+ruletype: SIGMA
From a2495b6b50470995075948b2c36f0fca428f44bc Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Thu, 9 Dec 2021 01:35:53 +0900
Subject: [PATCH 13/22] fix miss
---
src/yaml.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/yaml.rs b/src/yaml.rs
index 7420410c..0e9ad2e8 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -264,7 +264,7 @@ mod tests {
#[test]
fn test_exclude_rules_file() {
let mut yaml = yaml::ParseYaml::new();
- let path = Path::new("test_files/rules/");
+ let path = Path::new("test_files/rules/yaml");
yaml.read_dir(path.to_path_buf(), &"", &fillter::exclude_ids())
.unwrap();
assert_eq!(yaml.ignorerule_count, 10);
From 9941a5dc907e6e519d2e1abec5ae8c46a2da68d9 Mon Sep 17 00:00:00 2001
From: Yamato Security <71482215+YamatoSecurity@users.noreply.github.com>
Date: Thu, 9 Dec 2021 09:28:54 +0900
Subject: [PATCH 14/22] Update sigmac tool readme and ruletype name (#267)
---
tools/sigmac/README-English.md | 5 ++---
tools/sigmac/README-Japanese.md | 2 +-
tools/sigmac/hayabusa.py | 2 +-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/tools/sigmac/README-English.md b/tools/sigmac/README-English.md
index 458dc898..869601bd 100644
--- a/tools/sigmac/README-English.md
+++ b/tools/sigmac/README-English.md
@@ -47,8 +47,7 @@ cp splitter.py $sigma_path
### Convert Rule
-Conversion rules can be created by executing `convert.sh`.
-The rules will be created to hayabusa_rules folder.
+`convert.sh` will convert sigma rules to hayabusa rules and save them in a new `hayabusa_rules` folder.
```sh
export sigma_path=/path/to/sigma_repository
@@ -71,4 +70,4 @@ sigma/rules/windows/process_creation/process_creation_apt_turla_commands_medium.
## Sigma rule parsing errors
-Some rules will have been able to be converted but will cause parsing errors. We will continue to fix these bugs but for the meantime the majority of Sigma rules do work so please ignore the errors for now.
+Some rules will have been able to be converted but will cause parsing errors or will not be usable due to various bugs. We will continue to fix these bugs but for the meantime the majority of Sigma rules do work so please ignore the errors for now.
diff --git a/tools/sigmac/README-Japanese.md b/tools/sigmac/README-Japanese.md
index 8baca41b..bfb89ee9 100644
--- a/tools/sigmac/README-Japanese.md
+++ b/tools/sigmac/README-Japanese.md
@@ -48,7 +48,7 @@ cp splitter.py $sigma_path
* 注意:`/path/to/sigma_repository`そのままではなくて、自分のSigmaレポジトリのパスを指定してください。
### ルールの変換
-convert.shを実行することでルールの変換が実行されます。変換されたルールはhayabusa_rulesフォルダに作成されます。
+`convert.sh`を実行することでルールの変換が実行されます。変換されたルールは`hayabusa_rules`フォルダに作成されます。
```sh
export sigma_path=/path/to/sigma_repository
diff --git a/tools/sigmac/hayabusa.py b/tools/sigmac/hayabusa.py
index ae53c407..3e56ed9e 100644
--- a/tools/sigmac/hayabusa.py
+++ b/tools/sigmac/hayabusa.py
@@ -268,7 +268,7 @@ class HayabusaBackend(SingleTextQueryBackend):
# parsed.sigmaParser.parsedyamlがOrderedDictならこんなことしなくていい、後で別のやり方があるか調べる
# 順番固定してもいいかも
bs.write("title: " + parsed_yaml["title"]+"\n")
- bs.write("ruletype: SIGMA\n")
+ bs.write("ruletype: Sigma\n")
del parsed_yaml["title"]
# detectionの部分をクリアする前にtimeflameだけ確保しておく。
From a00a114101ab33eda2e75c9cf2ddab57cccea821 Mon Sep 17 00:00:00 2001
From: kazuminn
Date: Fri, 10 Dec 2021 23:01:47 +0900
Subject: [PATCH 15/22] refactor : rename variables and fix typo and add test
(#270)
---
src/detections/detection.rs | 9 +++----
src/fillter.rs | 36 -------------------------
src/filter.rs | 44 +++++++++++++++++++++++++++++++
src/lib.rs | 2 +-
src/main.rs | 4 +--
src/yaml.rs | 52 ++++++++++++++++++++++---------------
6 files changed, 82 insertions(+), 65 deletions(-)
delete mode 100644 src/fillter.rs
create mode 100644 src/filter.rs
diff --git a/src/detections/detection.rs b/src/detections/detection.rs
index 1904d5fe..10a1542a 100644
--- a/src/detections/detection.rs
+++ b/src/detections/detection.rs
@@ -11,7 +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::fillter;
+use crate::filter;
use crate::yaml::ParseYaml;
use std::sync::Arc;
@@ -55,12 +55,12 @@ impl Detection {
pub fn parse_rule_files(
level: String,
rulespath: Option<&str>,
- fill_ids: &fillter::RuleFill,
+ exclude_ids: &filter::RuleExclude,
) -> Vec {
// ルールファイルのパースを実行
let mut rulefile_loader = ParseYaml::new();
let result_readdir =
- rulefile_loader.read_dir(rulespath.unwrap_or(DIRPATH_RULES), &level, fill_ids);
+ rulefile_loader.read_dir(rulespath.unwrap_or(DIRPATH_RULES), &level, exclude_ids);
if result_readdir.is_err() {
AlertMessage::alert(
&mut std::io::stderr().lock(),
@@ -275,7 +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, &fillter::exclude_ids());
+ let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, &filter::exclude_ids());
assert_eq!(5, cole.len());
}
diff --git a/src/fillter.rs b/src/fillter.rs
deleted file mode 100644
index 7b61d175..00000000
--- a/src/fillter.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use crate::detections::configs;
-use std::collections::HashSet;
-use std::fs;
-
-#[derive(Clone, Debug)]
-pub struct RuleFill {
- pub no_use_rule: HashSet,
-}
-
-pub fn exclude_ids() -> RuleFill {
- let mut ids = String::from_utf8(fs::read("config/exclude-rules.txt").unwrap()).unwrap();
- if !configs::CONFIG
- .read()
- .unwrap()
- .args
- .is_present("show-noisyalerts")
- {
- ids += "\n"; // 改行を入れないとexclude-rulesの一番最後の行とnoisy-rules.txtの一番最後の行が一行にまとめられてしまう。
- ids += &String::from_utf8(fs::read("config/noisy-rules.txt").unwrap()).unwrap();
- }
-
- let mut fill_ids = RuleFill {
- no_use_rule: HashSet::new(),
- };
-
- for v in ids.split_whitespace() {
- let v = v.to_string();
- if v.is_empty() {
- // 空行は無視する。
- continue;
- }
- fill_ids.no_use_rule.insert(v);
- }
-
- return fill_ids;
-}
diff --git a/src/filter.rs b/src/filter.rs
new file mode 100644
index 00000000..113283cf
--- /dev/null
+++ b/src/filter.rs
@@ -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,
+}
+
+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;
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9bf20692..9bd8f144 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,6 @@
pub mod afterfact;
pub mod detections;
-pub mod fillter;
+pub mod filter;
pub mod notify;
pub mod omikuji;
pub mod timeline;
diff --git a/src/main.rs b/src/main.rs
index 56b4b6c5..b28c350c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,7 +7,7 @@ use evtx::{EvtxParser, ParserSettings};
use hayabusa::detections::detection;
use hayabusa::detections::detection::EvtxRecordInfo;
use hayabusa::detections::print::AlertMessage;
-use hayabusa::fillter;
+use hayabusa::filter;
use hayabusa::omikuji::Omikuji;
use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timeline::Timeline};
@@ -133,7 +133,7 @@ fn analysis_files(evtx_files: Vec) {
let rule_files = detection::Detection::parse_rule_files(
level,
configs::CONFIG.read().unwrap().args.value_of("rules"),
- &fillter::exclude_ids(),
+ &filter::exclude_ids(),
);
let mut pb = ProgressBar::new(evtx_files.len() as u64);
let mut detection = detection::Detection::new(rule_files);
diff --git a/src/yaml.rs b/src/yaml.rs
index 0e9ad2e8..33b50a6a 100644
--- a/src/yaml.rs
+++ b/src/yaml.rs
@@ -3,7 +3,7 @@ extern crate yaml_rust;
use crate::detections::configs;
use crate::detections::print::AlertMessage;
-use crate::fillter::RuleFill;
+use crate::filter::RuleExclude;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
@@ -47,14 +47,14 @@ impl ParseYaml {
&mut self,
path: P,
level: &str,
- fill_ids: &RuleFill,
+ exclude_ids: &RuleExclude,
) -> io::Result {
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, fill_ids)?;
+ self.read_dir(entry.path(), level, exclude_ids)?;
return io::Result::Ok(ret);
}
// ファイル以外は無視
@@ -140,7 +140,10 @@ impl ParseYaml {
//除外されたルールは無視する
let rule_id = &yaml_doc["id"].as_str();
if rule_id.is_some() {
- match fill_ids.no_use_rule.get(&rule_id.unwrap_or("").to_string()) {
+ match exclude_ids
+ .no_use_rule
+ .get(&rule_id.unwrap_or("").to_string())
+ {
None => (),
Some(_) => {
self.ignorerule_count += 1;
@@ -160,9 +163,9 @@ impl ParseYaml {
#[cfg(test)]
mod tests {
- use crate::fillter;
+ use crate::filter;
use crate::yaml;
- use crate::yaml::RuleFill;
+ use crate::yaml::RuleExclude;
use std::collections::HashSet;
use std::path::Path;
use yaml_rust::YamlLoader;
@@ -170,13 +173,13 @@ mod tests {
#[test]
fn test_read_dir_yaml() {
let mut yaml = yaml::ParseYaml::new();
- let fill_ids = RuleFill {
+ let exclude_ids = RuleExclude {
no_use_rule: HashSet::new(),
};
let _ = &yaml.read_dir(
"test_files/rules/yaml/".to_string(),
&"".to_owned(),
- &fill_ids,
+ &exclude_ids,
);
assert_ne!(yaml.files.len(), 0);
}
@@ -212,7 +215,7 @@ 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(), &"", &fillter::exclude_ids())
+ yaml.read_dir(path.to_path_buf(), &"", &filter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 5);
}
@@ -221,19 +224,15 @@ 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",
- &fillter::exclude_ids(),
- )
- .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", &fillter::exclude_ids())
+ yaml.read_dir(path.to_path_buf(), &"LOW", &filter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 4);
}
@@ -241,7 +240,7 @@ mod tests {
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", &fillter::exclude_ids())
+ yaml.read_dir(path.to_path_buf(), &"MEDIUM", &filter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 3);
}
@@ -249,7 +248,7 @@ mod tests {
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", &fillter::exclude_ids())
+ yaml.read_dir(path.to_path_buf(), &"HIGH", &filter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 2);
}
@@ -257,16 +256,27 @@ mod tests {
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", &fillter::exclude_ids())
+ yaml.read_dir(path.to_path_buf(), &"CRITICAL", &filter::exclude_ids())
.unwrap();
assert_eq!(yaml.files.len(), 1);
}
#[test]
- fn test_exclude_rules_file() {
+ 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(), &"", &fillter::exclude_ids())
+ 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);
+ }
}
From 2ff94b6e2c4260c1a43251387932bdcad52ad128 Mon Sep 17 00:00:00 2001
From: DustInDark
Date: Sat, 11 Dec 2021 00:26:50 +0900
Subject: [PATCH 16/22] added win_rare_schtask_creation to noisy-rule #263
(#277)
---
config/noisy-rules.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/config/noisy-rules.txt b/config/noisy-rules.txt
index f25d1a33..1fa83b45 100644
--- a/config/noisy-rules.txt
+++ b/config/noisy-rules.txt
@@ -2,4 +2,5 @@
b0d77106-7bb0-41fe-bd94-d1752164d066
66bfef30-22a5-4fcd-ad44-8d81e60922ae
e98374a6-e2d9-4076-9b5c-11bdb2569995
-6309ffc4-8fa2-47cf-96b8-a2f72e58e538
\ No newline at end of file
+6309ffc4-8fa2-47cf-96b8-a2f72e58e538
+b20f6158-9438-41be-83da-a5a16ac90c2b
\ No newline at end of file
From 8ab54883de906de14deca6be9d3aeb28dda1f985 Mon Sep 17 00:00:00 2001
From: Tanaka Zakku <71482215+YamatoSecurity@users.noreply.github.com>
Date: Sat, 11 Dec 2021 00:54:39 +0900
Subject: [PATCH 17/22] English readme update
---
README-English.md | 203 ++++++++++++++++-------
screenshots/hayabusa-results-summary.png | Bin 0 -> 34101 bytes
screenshots/hayabusa-results.png | Bin 0 -> 204009 bytes
screenshots/hayabusa-start.png | Bin 0 -> 82959 bytes
4 files changed, 140 insertions(+), 63 deletions(-)
create mode 100644 screenshots/hayabusa-results-summary.png
create mode 100644 screenshots/hayabusa-results.png
create mode 100644 screenshots/hayabusa-start.png
diff --git a/README-English.md b/README-English.md
index 1779d279..937c72d6 100644
--- a/README-English.md
+++ b/README-English.md
@@ -6,75 +6,58 @@
-# Hayabusa
-Hayabusa is a very fast Windows event analyzer used for creating forensic timelines and performing threat hunting based on IoCs written in either hayabusa or SIGMA rules. It can be run live, offline, pushed out as agents to be run on endpoints in an enterprise after an incident.
-
# About Hayabusa
-Hayabusa ("falcon" in Japanese) was written by the Yamato Security group in Japan. First inspired by the DeepblueCLI Windows event log analyzer, we started in 2020 porting it over to Rust for the RustyBlue project, then created SIGMA-like flexible signatures based in YAML, and then added a backend to SIGMA to support converting SIGMA rules into hayabusa rules. Supporting multi-threading, (to our knowledge) it is currently the fastest forensics timeline generator and threat hunting tool as well supports the most features in SIGMA. It can analyze multiple Windows event logs and consolidate the results into one timeline for easy analysis. It will output in CSV to be imported into tools like Timeline Explorer and Excel for analysis.
+Hayabusa ("falcon" in Japanese) is a **Windows event log fast forensics timeline generator** and **threat hunting tool** created by the [Yamato Security](https://yamatosecurity.connpass.com/) group in Japan. It is written in [Rust](https://www.rust-lang.org/) and supports multi-threading in order to be as fast as possible. It supports converted [sigma](https://github.com/SigmaHQ/sigma) and hayabusa detection rules written in YAML in order to be as easily customizable and extensible as possible. It can be run either on a live system or by gathering logs from multiple systems. The output will be consolidated into a single CSV timeline for easy analysis in Excel or [timeline explorer](https://ericzimmerman.github.io/#!index.md).
+
+## Fast forensics timeline generation
+Windows event log analysis has traditionally been a very long and tedious process because Windows event logs are 1) in a data format that is hard to analyze and 2) the majority of data is noise and not useful for investigations. Hayabusa's main goal is to extract out only useful data and present it in an easy-to-read format that is usable not only by professionally trained analysts but any Windows system administrator.
+Hayabusa is not intended to be a replacement for tools like [Evtx Explorer](https://ericzimmerman.github.io/#!index.md) or [Event Log Explorer](https://eventlogxp.com/) for slower deep-dive analysis but is intended for letting analysts get 80% of their work done in 20% of the time.
+
+## Threat hunting
+Hayabusa currently has over 1000 detection rules and the ultimate goal is to be able to push out hayabusa agents to all Windows endpoints after an incident or for periodic threat hunting and have them alert back to a central server.
+
+# About the development
+ First inspired by the [DeepBlueCLI](https://github.com/sans-blue-team/DeepBlueCLI) Windows event log analyzer, we started in 2020 porting it over to Rust for the [RustyBlue](https://github.com/Yamato-Security/RustyBlue) project, then created sigma-like flexible detection signatures written in YAML, and then added a backend to sigma to support converting sigma rules into our hayabusa rule format.
# Screenshots
-Add screenshots here.
+Startup:
+
+
+
+
+Terminal output:
+
+
+
+
+Results summary:
+
+
+
# Features
-* Cross-platform support: Windows, Linux, macOS (Intel + ARM)
-* Faster than a hayabusa falcon!
-* English and Japanese support
+* Cross-platform support: Windows, Linux, macOS
+* Developed in Rust to be memory safe and faster than a hayabusa falcon!
* Multi-thread support
-* Creating event timelines for forensic investigations and incident response
+* Creates a single easy-to-analyze CSV timeline for forensic investigations and incident response
* Threat hunting based on IoC signatures written in easy to read/create/edit YAML based hayabusa rules
-* SIGMA support to convert SIGMA rules to hayabusa rules
+* Sigma rule support to convert sigma rules to hayabusa rules
+* Currently it supports the most sigma rules compared to other similar tools and even supports count rules
* Event log statistics (Useful for getting a picture of what types of events there are and for tuning your log settings)
+* Rule tuning configuration by excluding bad rules or noisy rules
+
+# Planned Features
+* Enterprise-wide hunting on all endpoints
+* Japanese language support
+* MITRE ATT&CK mapping
+* MITRE ATT&CK heatmap generation
+* User logon and failed logon summary
+* Input from JSON logs
+* Output to JSON -> import to Elastic Stack/Splunk
# Downloads
You can download pre-compiled binaries for the Windows, Linux and macOS at [Releases.](https://github.com/Yamato-Security/hayabusa/releases)
-# Usage
-## Command line options
-````
-USAGE:
- hayabusa.exe [FLAGS] [OPTIONS]
-
-FLAGS:
- --credits Prints a list of contributors
- -h, --help Prints help information
- --rfc-2822 Output date and time in RFC 2822 format. Example: Mon, 07 Aug 2006 12:34:56 -0600
- -s, --statistics Prints statistics for event logs
- -u, --utc Output time in UTC format (default: local time)
- -V, --version Prints version information
-
-OPTIONS:
- --csv-timeline Save timeline to CSV file
- -d, --directory Event log files directory
- -f, --filepath Event file path
- --human-readable-timeline Human readable timeline
- -l, --lang Output language
- -t, --threadnum Number of threads (Default is the number of CPU cores)
-````
-
-## Usage examples
-* Run hayabusa against one Windows event log file:
-````
-hayabusa.exe --filepath=eventlog.evtx
-````
-
-* Run hayabusa against a directory with multiple Windows event log files:
-````
-hayabusa.exe --directory=.\evtx
-````
-
-* Export to a CSV file:
-````
-hayabusa.exe --directory=.\evtx --csv-timeline results.csv
-````
-
-# Hayabusa rules
-Hayabusa attack detection rules are written in a SIGMA-like YAML format.
-
-Please read [AboutRuleCreation-English.md](./doc/AboutRuleCreation-English.md) to understand about how to create rules.
-
-All of the rules are in the `rules` folder.
-You can check out the current rules to use as a template in creating new ones.
-
# Compiling from source
If you have rust installed, you can compile from source with the following command.
@@ -82,8 +65,88 @@ If you have rust installed, you can compile from source with the following comma
cargo build --release
````
+# Usage
+## Command line options
+````
+USAGE:
+ -f --filepath=[FILEPATH] 'File path to one .evtx file'
+ --csv-timeline=[CSV_TIMELINE] 'Save the timeline in CSV format'
+ --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'
+ --verbose 'Output verbose information to target event file path and rule file'
+ -q 'Quiet mode. Do not display the launch banner'
+ -r --rules=[RULEDIRECTORY] 'Rule file directory (default: ./rules)'
+ -m --min-level=[LEVEL] 'Minimum level for rules (default: informational)' (Possiblities are: informational, low, medium, high, critical)
+ -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)' (Usually there is no performance benefit in increasing the number of threads but you may want to lower to a smaller number to reduce CPU load.)
+ --contributors 'Prints the list of contributors'
+````
+
+## Usage examples
+* Run hayabusa against one Windows event log file:
+````
+hayabusa.exe -f eventlog.evtx
+````
+
+* Run hayabusa against the sample-evtx directory with multiple Windows event log files:
+````
+hayabusa.exe -d .\sample-evtx
+````
+
+* Export to a single CSV file for further analysis with excel or timeline explorer:
+````
+hayabusa.exe -d .\sample-evtx --csv-timeline results.csv
+````
+
+* Only run hayabusa rules:
+````
+hayabusa.exe -d .\sample-evtx --csv-timeline results.csv -r ./rules/hayabusa
+````
+
+* Only run sigma rules and show noisy alerts (disabled by default):
+````
+hayabusa.exe -d .\sample-evtx --csv-timeline results.csv -r ./rules/sigma --show-noisyalerts
+````
+
+* Only run rules to analyze logons and output in the UTC timezone:
+````
+hayabusa.exe -d .\sample-evtx --csv-timeline results.csv -r ./rules/hayabusa/default/events/Security/Logons -u
+````
+
+* Run on a live Windows machine (requires Administrator privileges) and only detect alerts (potentially malicious behavior):
+````
+hayabusa.exe -d C:\Windows\System32\winevt\Logs -m low
+````
+
+* Get event ID statistics:
+````
+hayabusa.exe -d C:\Windows\System32\winevt\Logs -s
+````
+
+## Testing hayabusa out on sample evtx files
+We have provided some sample evtx files for you to test hayabusa and/or create new rules at [https://github.com/Yamato-Security/hayabusa-sample-evtx](https://github.com/Yamato-Security/hayabusa-sample-evtx)
+
+# Hayabusa rules
+Hayabusa detection rules are written in a sigma-like YAML format.
+
+Please read [AboutRuleCreation-English.md](./doc/AboutRuleCreation-English.md) to understand about their format how to create rules.
+
+All of the rules are in the `rules` folder.
+`informational` level rules are considered `events`, while anything `low` and higher are considered `alerts`.
+The hayabusa rule directory structure is separated into 3 directories: `default` for logs that are turned on by default, `non-default` for logs that need to be turned on through group policy, and `sysmon` for logs that are generated by [sysmon](https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon).
+
+Please check out the current rules to use as a template in creating new ones or for checking the detection logic.
+
+## Detection rule tuning
+Like firewalls and IDSes, any signature-based tool will require some tuning to fit your environment so you may need to permanently or temporarily exclude certain rules.
+You can add a rule ID (Example: 4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6) to the `config/exclude-rules.txt` in order to ignore any rule you do not need.
+You can also add a rule ID to `config/noisy-rules.txt` in order to ignore the rule by default but still be able to use the rule with the `n` or `--show-noisyalerts` option.
+
# Other Windows event log analyzers and related projects
-There is no "one tool to rule them all" and we have found that each have their own merits so we recommend checking out these other great tools and projects and see which ones you like.
+There is no "one tool to rule them all" and we have found that each has its own merits so we recommend checking out these other great tools and projects and seeing which ones you like.
- [APT-Hunter](https://github.com/ahmedkhlief/APT-Hunter) - Attack detection tool written in Python.
- [Chainsaw](https://github.com/countercept/chainsaw) - A similar SIGMA based attack detection tool written in Rust.
@@ -94,17 +157,31 @@ There is no "one tool to rule them all" and we have found that each have their o
- [EVTX parser](https://github.com/omerbenamram/evtx) - the Rust library we used written by [@OBenamram](https://twitter.com/obenamram).
- [LogonTracer](https://github.com/JPCERTCC/LogonTracer) - A graphical interface to visualize logons to detect lateral movement by [JPCERTCC](https://twitter.com/jpcert_en).
- [RustyBlue](https://github.com/Yamato-Security/RustyBlue) - Rust port of DeepBlueCLI by [Eric Conrad](https://twitter.com/eric_conrad).
-- [SIGMA](https://github.com/SigmaHQ/sigma) - Generic SIEM rules.
+- [Sigma](https://github.com/SigmaHQ/sigma) - Community based generic SIEM rules.
- [so-import-evtx](https://docs.securityonion.net/en/2.3/so-import-evtx.html) - Import evtx files into Security Onion.
- [Timeline Explorer](https://ericzimmerman.github.io/#!index.md) - The best CSV timeline analyzer by [Eric Zimmerman](https://twitter.com/ericrzimmerman).
+- [Windows Event Log Analysis - Analyst Reference](https://www.forwarddefense.com/media/attachments/2021/05/15/windows-event-log-analyst-reference.pdf) - by Forward Defense's Steve Anson.
- [Zircolite](https://github.com/wagga40/Zircolite) - SIGMA based attack detection tool written in Python.
-## License
+## Comparison to other similar tools that support sigma
+It is not possible to do a perfect comparison as these tools support a different number of sigma rules.
+Hayabusa supports the largest number of sigma rules as well as will run additional hayabusa rules so will may take more time than other tools that do not do as much analysis.
+Also, time and memory usage will differ dramatically depending on what sample event log files are used, command-line options, rule tuning, etc... so please understand that results will vary.
-Hayabusa is released under GPLv3 and all rules are release under the Detection Rule License (DRL) 1.1
+The following were taken based on approximately 500 logs (130MB) from our sample-evtx repository at 2021/12/09.
-## Contributing
+| | Elapsed Time | Memory Usage | Total Sigma Events Detected | Unique Sigma Events Detected |
+| :---: | :---: | :---: | :---: | :---: |
+| Chainsaw | 10 seconds | 75 MB | 552 | 170 |
+| Hayabusa | xx | xx | 9783 | 265 |
+| Zircolite | 55 seconds | 400 MB | 1954 | 237 |
-We would love any form of contributing. Pull requests and rule creation are the best but feature requests, notifying us of bugs, etc... are also very welcome.
+# License
+
+Hayabusa is released under GPLv3 and all rules are released under the Detection Rule License (DRL) 1.1
+
+# Contributing
+
+We would love any form of contribution. Pull requests, rule creation and sample evtx logs are the best but feature requests, notifying us of bugs, etc... are also very welcome.
At the least, if you like our tool then please give us a star on Github and show your support!
diff --git a/screenshots/hayabusa-results-summary.png b/screenshots/hayabusa-results-summary.png
new file mode 100644
index 0000000000000000000000000000000000000000..00b3e73d1815c0e5f54e00b1c621097e405b5e20
GIT binary patch
literal 34101
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
TumI0`eFRl}s?a278TP*bzzobt
literal 0
HcmV?d00001
diff --git a/screenshots/hayabusa-results.png b/screenshots/hayabusa-results.png
new file mode 100644
index 0000000000000000000000000000000000000000..29299386e435ba758252d4cc246d39159cb41412
GIT binary patch
literal 204009
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