WIP: Feature/count sigma rule #93 (#113)

* Feature/call error message struct#66 (#69)

* change  way to use write trait #66

* change call error message struct #66

* erase finished TODO #66

* erase comment in error message format test #66

* resolve conflict #66

* Feature/call error message struct#66 (#71)

* change ERROR writeln struct #66

* under constructing

* add statistics template

* fix

* add comment

* add condition impl #93

* fix erased get_descendants and remove unnecessaly struct #93

* erased finished TODO comment

* erased finished TODO comment

* Revert "fix erased get_descendants and remove unnecessaly struct #93"

This reverts commit 82e905e045.

Revert "add condition impl #93"

This reverts commit 19ecc87377.

* add doc comment to rule function

* fix and add test doc commet

* add doc to AggregaationParseInfo

* add struct count in aggregation condition. #93

* add evaluate aggregation condition func provisional architecture. #93

* add countup function #93

* fix key to count hashmap #93

* add judge aggregation condition function #93

* fix  error #93

* fix test #93

* share compile error ver

* fix detection.rs compile error

* fix timeframe parse

* add countup process in select

* fix select argument

* add test countup

* add test count judge #93

* add SIGMA windows count field and by keyword #93

* fix reference record in countup/judgecount #93

* add timedata in countup schema #93

* Refact: split code for matcher from rule.rs

* Reafact: combine multiple declared functions

* Refact: split code for SelectionNode from rule.rs

* Refact: mv test code for SelectionNode from rule.rs

* Refact: mv condition's code from rule.rs

* add count to detection #93

* fix compile error

* fix source to test ng. #93

* erase unused variable #93

* fix count architecture #93

* fix comment and compile error

* erase dust (response  to review)

* erase dust (response to review)

* reduce calling Rulenode function (response to review)

* add aggregation output func

* erase dust(response to review) and add agg condition String func

* change error output

* reduce call RuleNode function(response to review)

* To reduce call RuleNode function

* fix test name

* fix coflicted resolve miss

* add code comment in timeframe count.

* add sort record timedata in timeframe(response to review)

* fix unnecesasry result in ArgResult

* add no field and by value count test

* create count test no field and by with timeframe

* erase duplicated timeframe data in RuleNode

* fix test error no field and no by count with timeframe

* fix test name

* add test case of exist field and by count.

* fix by count test and add test count othervalue in timeframe

* add test

* fix judge_timeframe logic when indexout

* fix test name and add count test field and by with timeframe

* adjust #120

* move associated count function from rulenode

* fix error when resolve conflict

* fix no output bug if exist output

Co-authored-by: HajimeTakai <takai.wa.hajime@gmail.com>
Co-authored-by: itiB <is0312vx@ed.ritsumei.ac.jp>
This commit is contained in:
DustInDark
2021-07-16 07:20:44 +09:00
committed by GitHub
parent 65b714b81b
commit 330cbb58ca
9 changed files with 1127 additions and 188 deletions
+132 -27
View File
@@ -1,4 +1,8 @@
extern crate regex;
use crate::detections::print::Message;
use chrono::{DateTime, TimeZone, Utc};
use std::{collections::HashMap, sync::Arc, vec};
use serde_json::Value;
@@ -8,7 +12,11 @@ mod matchers;
mod selectionnodes;
use self::selectionnodes::SelectionNode;
mod aggregation_parser;
use self::aggregation_parser::AggregationParseInfo;
mod condition_parser;
mod count;
use self::count::TimeFrameInfo;
pub fn create_rule(yaml: Yaml) -> RuleNode {
return RuleNode::new(yaml);
@@ -18,6 +26,7 @@ pub fn create_rule(yaml: Yaml) -> RuleNode {
pub struct RuleNode {
pub yaml: Yaml,
detection: Option<DetectionNode>,
countdata: HashMap<String, HashMap<String, Vec<DateTime<Utc>>>>,
}
unsafe impl Sync for RuleNode {}
@@ -27,6 +36,7 @@ impl RuleNode {
return RuleNode {
yaml: yaml,
detection: Option::None,
countdata: HashMap::new(),
};
}
@@ -53,12 +63,35 @@ impl RuleNode {
}
}
pub fn select(&self, event_record: &Value) -> bool {
pub fn select(&mut self, filepath: &String, event_record: &Value) -> bool {
if self.detection.is_none() {
return false;
}
return self.detection.as_ref().unwrap().select(event_record);
let result = self.detection.as_ref().unwrap().select(event_record);
if result {
count::count(self, filepath, event_record);
}
return result;
}
/// aggregation conditionが存在するかを返す関数
pub fn has_agg_condition(&self) -> bool {
return self
.detection
.as_ref()
.unwrap()
.aggregation_condition
.is_some();
}
/// Aggregation Conditionの結果を配列で返却する関数
pub fn judge_satisfy_aggcondition(&self) -> Vec<AggResult> {
let mut ret = Vec::new();
if !self.has_agg_condition() {
return ret;
}
for filepath in self.countdata.keys() {
ret.append(&mut count::aggregation_condition_select(&self, &filepath));
}
return ret;
}
}
@@ -66,7 +99,8 @@ impl RuleNode {
struct DetectionNode {
pub name_to_selection: HashMap<String, Arc<Box<dyn SelectionNode + Send + Sync>>>,
pub condition: Option<Box<dyn SelectionNode + Send + Sync>>,
pub aggregation_condition: Option<aggregation_parser::AggregationParseInfo>,
pub aggregation_condition: Option<AggregationParseInfo>,
pub timeframe: Option<TimeFrameInfo>,
}
impl DetectionNode {
@@ -75,6 +109,7 @@ impl DetectionNode {
name_to_selection: HashMap::new(),
condition: Option::None,
aggregation_condition: Option::None,
timeframe: Option::None,
};
}
@@ -82,6 +117,12 @@ impl DetectionNode {
// selection nodeの初期化
self.parse_name_to_selection(detection_yaml)?;
//timeframeに指定されている値を取得
let timeframe = &detection_yaml["timeframe"].as_str();
if timeframe.is_some() {
self.timeframe = Some(TimeFrameInfo::parse_tframe(timeframe.unwrap().to_string()));
}
// conditionに指定されている式を取得
let condition = &detection_yaml["condition"].as_str();
let condition_str = if let Some(cond_str) = condition {
@@ -151,7 +192,7 @@ impl DetectionNode {
continue;
}
// condition等、特殊なキーワードを無視する。
if name == "condition" {
if name == "condition" || name == "timeframe" {
continue;
}
@@ -229,6 +270,39 @@ impl DetectionNode {
}
}
#[derive(Debug)]
/// countなどのaggregationの結果を出力する構造体
pub struct AggResult {
/// evtx file path
pub filepath: String,
/// countなどの値
pub data: i32,
/// (countの括弧内の記載)_(count byで指定された条件)で設定されたキー
pub key: String,
///検知したブロックの最初のレコードの時間
pub start_timedate: DateTime<Utc>,
///条件式の情報
pub condition_op_num: String,
}
impl AggResult {
pub fn new(
filepath: String,
data: i32,
key: String,
start_timedate: DateTime<Utc>,
condition_op_num: String,
) -> AggResult {
return AggResult {
filepath: filepath,
data: data,
key: key,
start_timedate: start_timedate,
condition_op_num: condition_op_num,
};
}
}
#[cfg(test)]
mod tests {
use crate::detections::rule::create_rule;
@@ -263,10 +337,10 @@ mod tests {
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), true);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -291,10 +365,10 @@ mod tests {
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), false);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -319,10 +393,10 @@ mod tests {
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), false);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -400,10 +474,10 @@ mod tests {
}
}"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), true);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -457,10 +531,10 @@ mod tests {
}
}"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), false);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -521,10 +595,10 @@ mod tests {
}
"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), true);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -563,10 +637,10 @@ mod tests {
}
"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), true);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -606,10 +680,10 @@ mod tests {
}
"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), false);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -668,10 +742,10 @@ mod tests {
}
"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), true);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -730,10 +804,10 @@ mod tests {
}
"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), false);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -774,10 +848,10 @@ mod tests {
}
}"#;
let rule_node = parse_rule_from_str(rule_str);
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&record), true);
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -824,4 +898,35 @@ mod tests {
Err(vec!["not found detection node".to_string()])
);
}
/// countで対象の数値確認を行うためのテスト用関数
fn check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) {
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test = rule_yaml.next().unwrap();
let mut rule_node = create_rule(test);
let _init = rule_node.init();
match serde_json::from_str(record_str) {
Ok(record) => {
let result = rule_node.select(&"testpath".to_string(), &record);
assert_eq!(
rule_node.detection.unwrap().aggregation_condition.is_some(),
true
);
assert_eq!(result, true);
assert_eq!(
*&rule_node
.countdata
.get("testpath")
.unwrap()
.get(key)
.unwrap()
.len() as i32,
expect_count
);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
}
}
}
}