Skip to content

Commit

Permalink
feat: exclude rules when target evtx(channel) does not exists.
Browse files Browse the repository at this point in the history
  • Loading branch information
fukusuket committed Apr 29, 2024
1 parent 64baed7 commit e780aea
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
174 changes: 174 additions & 0 deletions src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use crate::detections::configs::{self, StoredStatic};
use crate::detections::message::{AlertMessage, ERROR_LOG_STACK};
use crate::detections::rule::RuleNode;
use evtx::EvtxParser;
use hashbrown::HashMap;
use regex::Regex;
use std::collections::HashSet;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use yaml_rust::Yaml;

#[derive(Debug)]
pub struct DataFilterRule {
Expand Down Expand Up @@ -79,3 +84,172 @@ impl RuleExclude {
}
}
}

fn peek_channel_from_evtx_first_record(
evtx_files: &Vec<PathBuf>,
) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
let mut channels = HashSet::new();
for path in evtx_files {
let mut parser = EvtxParser::from_path(path)?;
let mut records = parser.records_json_value();
match records.next() {
Some(Ok(rec)) => channels.insert(
rec.data["Event"]["System"]["Channel"]
.to_string()
.replace('"', ""),
),
_ => continue,
};
}
Ok(channels)
}

fn extract_channel_from_rules(
rule_files: &Vec<RuleNode>,
evtx_channels: &HashSet<String>,
) -> Vec<String> {
fn visit_value(
key: &str,
value: &Yaml,
evtx_channels: &HashSet<String>,
stacked_channels: &mut Vec<String>,
) {
match *value {
Yaml::String(ref s) => {
if key == "Channel" && evtx_channels.contains(s) {
stacked_channels.push(s.clone());
}
}
Yaml::Hash(ref map) => {
for (k, v) in map {
visit_value(k.as_str().unwrap(), v, evtx_channels, stacked_channels);
}
}
Yaml::Array(ref seq) => {
for v in seq {
visit_value(key, v, evtx_channels, stacked_channels);
}
}
_ => {}
}
}
let mut stacked_channels = vec![];
let mut filtered_rulespathes = vec![];
for rule in rule_files {
let before_visit_len = stacked_channels.len();
visit_value("", &rule.yaml, evtx_channels, &mut stacked_channels);
if before_visit_len < stacked_channels.len() {
filtered_rulespathes.push(rule.rulepath.to_string());
}
}
filtered_rulespathes
}

pub fn filter_rules_by_evtx_channel(
evtx_files: &Vec<PathBuf>,
rule_nodes: &Vec<RuleNode>,
) -> Vec<String> {
let channels = peek_channel_from_evtx_first_record(evtx_files);
match channels {
Ok(ch) => extract_channel_from_rules(rule_nodes, &ch),
_ => vec![],
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use yaml_rust::YamlLoader;

#[test]
fn test_peek_channel_from_evtx_first_record_invalid_evtx() {
let evtx_files = vec![PathBuf::from("test_files/evtx/test1.evtx")];
let result = peek_channel_from_evtx_first_record(&evtx_files);
assert!(result.is_err());
}

#[test]
fn test_extract_channel_from_rules_hash_match() {
let rule_str = r#"
detection:
selection1:
Channel: 'Microsoft-Windows-Sysmon/Operational'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test_yaml_data = rule_yaml.next().unwrap();
let rule = RuleNode::new("test_files/evtx/test1.evtx".to_string(), test_yaml_data);
let rule_files = vec![rule];
let evtx_channels = HashSet::from_iter(vec!["Microsoft-Windows-Sysmon/Operational".into()]);
let result = extract_channel_from_rules(&rule_files, &evtx_channels);
assert_eq!(result, vec!["test_files/evtx/test1.evtx"]);
}

#[test]
fn test_extract_channel_from_rules_hash_not_match() {
let rule_str = r#"
detection:
selection1:
Channel: 'Security'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test_yaml_data = rule_yaml.next().unwrap();
let rule = RuleNode::new("test_files/evtx/test1.evtx".to_string(), test_yaml_data);
let rule_files = vec![rule];
let evtx_channels = HashSet::from_iter(vec!["Microsoft-Windows-Sysmon/Operational".into()]);
let result = extract_channel_from_rules(&rule_files, &evtx_channels);
assert_eq!(result.len(), 0);
}

#[test]
fn test_extract_channel_from_rules_array_match() {
let rule_str = r#"
detection:
selection1:
Channel:
- 'Security'
- 'Microsoft-Windows-Sysmon/Operational'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test_yaml_data = rule_yaml.next().unwrap();
let rule = RuleNode::new("test_files/evtx/test1.evtx".to_string(), test_yaml_data);
let rule_files = vec![rule];
let evtx_channels = HashSet::from_iter(vec!["Microsoft-Windows-Sysmon/Operational".into()]);
let result = extract_channel_from_rules(&rule_files, &evtx_channels);
assert_eq!(result, vec!["test_files/evtx/test1.evtx"]);
}

#[test]
fn test_extract_channel_from_rules_array_not_match() {
let rule_str = r#"
detection:
selection1:
Channel:
- 'Security'
- 'System'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test_yaml_data = rule_yaml.next().unwrap();
let rule = RuleNode::new("test_files/evtx/test1.evtx".to_string(), test_yaml_data);
let rule_files = vec![rule];
let evtx_channels = HashSet::from_iter(vec!["Microsoft-Windows-Sysmon/Operational".into()]);
let result = extract_channel_from_rules(&rule_files, &evtx_channels);
assert_eq!(result.len(), 0);
}

#[test]
fn test_filter_rules_by_evtx_channel_invalid_evtx() {
let evtx_files = vec![PathBuf::from("test_files/evtx/test1.evtx")];
let rule_str = r#"
detection:
selection1:
Channel: 'Microsoft-Windows-Sysmon/Operational'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test_yaml_data = rule_yaml.next().unwrap();
let rule = RuleNode::new("test_files/evtx/test1.evtx".to_string(), test_yaml_data);
let rule_nodes = vec![rule];
let result = filter_rules_by_evtx_channel(&evtx_files, &rule_nodes);
assert_eq!(result.len(), 0);
}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use hayabusa::detections::utils;
use hayabusa::detections::utils::{
check_setting_path, get_writable_color, output_and_data_stack_for_html, output_profile_name,
};
use hayabusa::filter::filter_rules_by_evtx_channel;
use hayabusa::options::htmlreport::{self, HTML_REPORTER};
use hayabusa::options::pivot::create_output;
use hayabusa::options::pivot::PIVOT_KEYWORD;
Expand Down Expand Up @@ -1452,6 +1453,8 @@ impl App {
if is_show_progress {
pb.enable_steady_tick(Duration::from_millis(300));
}
let filtered_rulepathes = filter_rules_by_evtx_channel(&evtx_files, &rule_files);
rule_files.retain(|r| filtered_rulepathes.contains(&r.rulepath));
self.rule_keys = self.get_all_keys(&rule_files);
let mut detection = detection::Detection::new(rule_files);
let mut tl = Timeline::new();
Expand Down

0 comments on commit e780aea

Please sign in to comment.