* 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 commit82e905e045. Revert "add condition impl #93" This reverts commit19ecc87377. * 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:
+132
-27
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user