Merge branch 'main' into feature/start_finish_time
This commit is contained in:
30
Cargo.lock
generated
30
Cargo.lock
generated
@@ -17,6 +17,17 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
@@ -698,6 +709,17 @@ dependencies = [
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.22.0"
|
||||
@@ -733,6 +755,9 @@ name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hayabusa"
|
||||
@@ -745,6 +770,7 @@ dependencies = [
|
||||
"dotenv",
|
||||
"evtx",
|
||||
"flate2",
|
||||
"hashbrown",
|
||||
"hhmmss",
|
||||
"lazy_static",
|
||||
"linked-hash-map",
|
||||
@@ -1457,7 +1483,7 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
@@ -1505,7 +1531,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "hayabusa"
|
||||
version = "1.0.0"
|
||||
authors = ["akiranishikawa <nishikawa@kagosec.net>"]
|
||||
edition = "2018"
|
||||
authors = ["Yamato Security @yamatosecurity"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -28,9 +28,13 @@ slack-hook = "0.8"
|
||||
dotenv = "0.15.0"
|
||||
hhmmss = "*"
|
||||
pbr = "*"
|
||||
hashbrown = "0.11.2"
|
||||
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "x86_64-w64-mingw32-gcc"
|
||||
|
||||
[target.i686-pc-windows-gnu]
|
||||
linker = "i686-w64-mingw32-gcc"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
@@ -113,7 +113,7 @@ fn emit_csv<W: std::io::Write>(writer: &mut W, displayflag: bool) -> io::Result<
|
||||
|
||||
wtr.flush()?;
|
||||
println!("");
|
||||
println!("Total events detected: {:?}", detect_count);
|
||||
println!("Total events: {:?}", detect_count);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ use crate::detections::print::AlertMessage;
|
||||
use crate::detections::utils;
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::{App, AppSettings, ArgMatches};
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::HashSet;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::RwLock;
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: RwLock<ConfigReader> = RwLock::new(ConfigReader::new());
|
||||
@@ -16,12 +17,13 @@ lazy_static! {
|
||||
levelmap.insert("CRITICAL".to_owned(), 5);
|
||||
return levelmap;
|
||||
};
|
||||
pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig =
|
||||
load_eventkey_alias("config/eventkey_alias.txt");
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigReader {
|
||||
pub args: ArgMatches<'static>,
|
||||
pub event_key_alias_config: EventKeyAliasConfig,
|
||||
pub event_timeline_config: EventInfoConfig,
|
||||
pub target_eventids: TargetEventIds,
|
||||
}
|
||||
@@ -30,7 +32,6 @@ impl ConfigReader {
|
||||
pub fn new() -> Self {
|
||||
ConfigReader {
|
||||
args: build_app(),
|
||||
event_key_alias_config: load_eventkey_alias("config/eventkey_alias.txt"),
|
||||
event_timeline_config: load_eventcode_info("config/timeline_event_info.txt"),
|
||||
target_eventids: load_target_ids("config/target_eventids.txt"),
|
||||
}
|
||||
@@ -60,12 +61,13 @@ fn build_app<'a>() -> ArgMatches<'a> {
|
||||
--endtimeline=[ENDTIMELINE]'End time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
|
||||
-q 'Quiet mode. Do not display the launch banner'
|
||||
-r --rules=[RULEDIRECTORY] 'Rule file directory (default: ./rules)'
|
||||
-L --level=[LEVEL] 'Minimum level for rules (default: INFORMATIONAL)'
|
||||
-m --min-level=[LEVEL] 'Minimum level for rules (default: informational)'
|
||||
-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)'
|
||||
--show-deprecated 'do not exclude rules with YAML's status deprecated'
|
||||
--contributors 'Prints the list of contributors'";
|
||||
App::new(&program)
|
||||
.about("Hayabusa: Aiming to be the world's greatest Windows event log analysis tool!")
|
||||
@@ -198,21 +200,23 @@ impl TargetEventTime {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventKeyAliasConfig {
|
||||
key_to_eventkey: HashMap<String, String>,
|
||||
key_to_split_eventkey: HashMap<String, Vec<usize>>,
|
||||
}
|
||||
|
||||
impl EventKeyAliasConfig {
|
||||
pub fn new() -> EventKeyAliasConfig {
|
||||
return EventKeyAliasConfig {
|
||||
key_to_eventkey: HashMap::new(),
|
||||
key_to_split_eventkey: HashMap::new(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_event_key(&self, alias: String) -> Option<&String> {
|
||||
return self.key_to_eventkey.get(&alias);
|
||||
pub fn get_event_key(&self, alias: &String) -> Option<&String> {
|
||||
return self.key_to_eventkey.get(alias);
|
||||
}
|
||||
|
||||
pub fn get_event_key_values(&self) -> Vec<(&String, &String)> {
|
||||
return self.key_to_eventkey.iter().map(|e| e).collect();
|
||||
pub fn get_event_key_split(&self, alias: &String) -> Option<&Vec<usize>> {
|
||||
return self.key_to_split_eventkey.get(alias);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +240,12 @@ fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig {
|
||||
config
|
||||
.key_to_eventkey
|
||||
.insert(alias.to_owned(), event_key.to_owned());
|
||||
let splits = event_key.split(".").map(|s| s.len()).collect();
|
||||
config
|
||||
.key_to_split_eventkey
|
||||
.insert(alias.to_owned(), splits);
|
||||
});
|
||||
config.key_to_eventkey.shrink_to_fit();
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -273,14 +282,6 @@ impl EventInfoConfig {
|
||||
pub fn get_event_id(&self, eventid: &String) -> Option<&EventInfo> {
|
||||
return self.eventinfo.get(eventid);
|
||||
}
|
||||
|
||||
pub fn get_event_info(&self) -> Vec<(&String, &EventInfo)> {
|
||||
return self.eventinfo.iter().map(|e| e).collect();
|
||||
}
|
||||
|
||||
// pub fn get_event_key_values(&self) -> Vec<(&String, &String)> {
|
||||
// return self.timeline_eventcode_info.iter().map(|e| e).collect();
|
||||
// }
|
||||
}
|
||||
|
||||
fn load_eventcode_info(path: &str) -> EventInfoConfig {
|
||||
@@ -312,27 +313,26 @@ fn load_eventcode_info(path: &str) -> EventInfoConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::detections::configs;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn singleton_read_and_write() {
|
||||
let message =
|
||||
"EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }";
|
||||
configs::CONFIG.write().unwrap().event_key_alias_config =
|
||||
configs::load_eventkey_alias("test_files/config/eventkey_alias.txt");
|
||||
|
||||
let display = format!(
|
||||
"{}",
|
||||
format_args!(
|
||||
"{:?}",
|
||||
configs::CONFIG.write().unwrap().event_key_alias_config
|
||||
)
|
||||
);
|
||||
assert_eq!(message, display);
|
||||
}
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn singleton_read_and_write() {
|
||||
// let message =
|
||||
// "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }";
|
||||
// configs::EVENT_KEY_ALIAS_CONFIG =
|
||||
// configs::load_eventkey_alias("test_files/config/eventkey_alias.txt");
|
||||
// let display = format!(
|
||||
// "{}",
|
||||
// format_args!(
|
||||
// "{:?}",
|
||||
// configs::CONFIG.write().unwrap().event_key_alias_config
|
||||
// )
|
||||
// );
|
||||
// assert_eq!(message, display);
|
||||
// }
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn target_event_time_filter() {
|
||||
|
||||
@@ -31,12 +31,11 @@ impl EvtxRecordInfo {
|
||||
return EvtxRecordInfo {
|
||||
evtx_filepath: evtx_filepath,
|
||||
record: record,
|
||||
data_string,
|
||||
data_string: data_string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO テストケースかかなきゃ...
|
||||
#[derive(Debug)]
|
||||
pub struct Detection {
|
||||
rules: Vec<RuleNode>,
|
||||
@@ -181,7 +180,7 @@ impl Detection {
|
||||
println!("{} alerts: {}", levellabel[i], value);
|
||||
total_unique += value;
|
||||
}
|
||||
println!("Unique events detected: {}", total_unique);
|
||||
println!("Unique alerts detected: {}", total_unique);
|
||||
}
|
||||
|
||||
// 複数のイベントレコードに対して、ルールを1個実行します。
|
||||
|
||||
@@ -112,12 +112,7 @@ impl Message {
|
||||
.take(target_length)
|
||||
.collect::<String>();
|
||||
|
||||
if let Some(array_str) = configs::CONFIG
|
||||
.read()
|
||||
.unwrap()
|
||||
.event_key_alias_config
|
||||
.get_event_key(target_str.to_string())
|
||||
{
|
||||
if let Some(array_str) = configs::EVENTKEY_ALIAS.get_event_key(&target_str) {
|
||||
let split: Vec<&str> = array_str.split(".").collect();
|
||||
let mut is_exist_event_key = false;
|
||||
let mut tmp_event_record: &Value = event_record.into();
|
||||
@@ -166,7 +161,7 @@ impl Message {
|
||||
detect_count += detect_infos.len();
|
||||
}
|
||||
println!("");
|
||||
println!("Total Events Detected:{:?}", detect_count);
|
||||
println!("Total events:{:?}", detect_count);
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> &BTreeMap<DateTime<Utc>, Vec<DetectInfo>> {
|
||||
|
||||
@@ -114,8 +114,8 @@ impl ConditionCompiler {
|
||||
pub fn compile_condition(
|
||||
&self,
|
||||
condition_str: String,
|
||||
name_2_node: &HashMap<String, Arc<Box<dyn SelectionNode + Send + Sync>>>,
|
||||
) -> Result<Box<dyn SelectionNode + Send + Sync>, String> {
|
||||
name_2_node: &HashMap<String, Arc<Box<dyn SelectionNode>>>,
|
||||
) -> Result<Box<dyn SelectionNode>, String> {
|
||||
// パイプはここでは処理しない
|
||||
let captured = self::RE_PIPE.captures(&condition_str);
|
||||
let condition_str = if captured.is_some() {
|
||||
@@ -137,8 +137,8 @@ impl ConditionCompiler {
|
||||
fn compile_condition_body(
|
||||
&self,
|
||||
condition_str: String,
|
||||
name_2_node: &HashMap<String, Arc<Box<dyn SelectionNode + Send + Sync>>>,
|
||||
) -> Result<Box<dyn SelectionNode + Send + Sync>, String> {
|
||||
name_2_node: &HashMap<String, Arc<Box<dyn SelectionNode>>>,
|
||||
) -> Result<Box<dyn SelectionNode>, String> {
|
||||
let tokens = self.tokenize(&condition_str)?;
|
||||
|
||||
let parsed = self.parse(tokens)?;
|
||||
@@ -410,8 +410,8 @@ impl ConditionCompiler {
|
||||
fn to_selectnode(
|
||||
&self,
|
||||
token: ConditionToken,
|
||||
name_2_node: &HashMap<String, Arc<Box<dyn SelectionNode + Send + Sync>>>,
|
||||
) -> Result<Box<dyn SelectionNode + Send + Sync>, String> {
|
||||
name_2_node: &HashMap<String, Arc<Box<dyn SelectionNode>>>,
|
||||
) -> Result<Box<dyn SelectionNode>, String> {
|
||||
// RefSelectionNodeに変換
|
||||
if let ConditionToken::SelectionReference(selection_name) = token {
|
||||
let selection_node = name_2_node.get(&selection_name);
|
||||
@@ -540,7 +540,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_str.to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
rule_node.select(&"testpath".to_owned(), &recinfo),
|
||||
@@ -589,7 +589,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -636,7 +636,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
|
||||
@@ -43,23 +43,10 @@ pub fn countup(
|
||||
|
||||
/// countでgroupbyなどの情報を区分するためのハッシュマップのキーを作成する関数
|
||||
pub fn create_count_key(rule: &RuleNode, record: &Value) -> String {
|
||||
if rule
|
||||
.detection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.aggregation_condition
|
||||
.as_ref()
|
||||
.is_none()
|
||||
{
|
||||
if rule.detection.aggregation_condition.as_ref().is_none() {
|
||||
return "_".to_string();
|
||||
}
|
||||
let aggcondition = rule
|
||||
.detection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.aggregation_condition
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let aggcondition = rule.detection.aggregation_condition.as_ref().unwrap();
|
||||
// recordでaliasが登録されている前提とする
|
||||
let mut key = "".to_string();
|
||||
if aggcondition._field_name.is_some() {
|
||||
@@ -115,13 +102,7 @@ pub fn aggregation_condition_select(rule: &RuleNode, filepath: &String) -> Vec<A
|
||||
/// aggregation condition内での条件式を文字として返す関数
|
||||
pub fn get_str_agg_eq(rule: &RuleNode) -> String {
|
||||
//この関数はaggregation ruleのパースが正常終了した後に呼ばれる想定のためOptionの判定は行わない
|
||||
let agg_condition = rule
|
||||
.detection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.aggregation_condition
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let agg_condition = rule.detection.aggregation_condition.as_ref().unwrap();
|
||||
let mut ret: String = "".to_owned();
|
||||
match agg_condition._cmp_op {
|
||||
AggregationConditionToken::EQ => {
|
||||
@@ -268,18 +249,12 @@ pub fn judge_timeframe(
|
||||
let mut ret: Vec<AggResult> = Vec::new();
|
||||
let mut time_data = time_datas.clone();
|
||||
time_data.sort();
|
||||
let aggcondition = rule
|
||||
.detection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.aggregation_condition
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let aggcondition = rule.detection.aggregation_condition.as_ref().unwrap();
|
||||
let mut start_point = 0;
|
||||
// 最初はcountの条件として記載されている分のレコードを取得するためのindex指定
|
||||
let mut check_point = start_point + aggcondition._cmp_num - 1;
|
||||
// timeframeで指定された基準の値を秒数として保持
|
||||
let judge_sec_frame = get_sec_timeframe(&rule.detection.as_ref().unwrap().timeframe);
|
||||
let judge_sec_frame = get_sec_timeframe(&rule.detection.timeframe);
|
||||
loop {
|
||||
// 基準となるレコードもしくはcountを最低限満たす対象のレコードのindexが配列の領域を超えていた場合
|
||||
if start_point as usize >= time_data.len() || check_point as usize >= time_data.len() {
|
||||
@@ -670,7 +645,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: rec,
|
||||
data_string: String::default(),
|
||||
data_string: record.to_string(),
|
||||
};
|
||||
let _result = rule_node.select(&"testpath".to_string(), &recinfo);
|
||||
}
|
||||
@@ -763,7 +738,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_str.to_string(),
|
||||
};
|
||||
let result = &rule_node.select(&"testpath".to_owned(), &recinfo);
|
||||
assert_eq!(result, &true);
|
||||
|
||||
@@ -263,7 +263,7 @@ impl LeafMatcher for DefaultMatcher {
|
||||
// Pipeが指定されていればパースする
|
||||
let emp = String::default();
|
||||
let mut keys: VecDeque<&str> = key_list.get(0).unwrap_or(&emp).split("|").collect(); // key_listが空はあり得ない
|
||||
keys.pop_front();
|
||||
keys.pop_front(); // 一つ目はただのキーで、2つめ以降がpipe
|
||||
while !keys.is_empty() {
|
||||
let key = keys.pop_front().unwrap();
|
||||
let pipe_element = match key {
|
||||
@@ -333,14 +333,27 @@ impl LeafMatcher for DefaultMatcher {
|
||||
return is_event_value_null;
|
||||
}
|
||||
|
||||
// JSON形式のEventLogデータをstringに変換するための前処理
|
||||
// 以前のコードはstringに変換に変換する必ずto_string()がするような処理になっていた。
|
||||
// そうすると、凄く遅くなるので、そうならないように回避
|
||||
let mut b_str = String::default();
|
||||
let mut n_str = String::default();
|
||||
match event_value.unwrap_or(&Value::Null) {
|
||||
Value::Bool(b) => b_str = b.to_string(),
|
||||
Value::Number(n) => {
|
||||
n_str = n.to_string();
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// JSON形式のEventLogデータをstringに変換
|
||||
let event_value_str: Option<String> = if self.key_list.is_empty() {
|
||||
Option::Some(recinfo.data_string.to_string())
|
||||
let event_value_str: Option<&String> = if self.key_list.is_empty() {
|
||||
Option::Some(&recinfo.data_string)
|
||||
} else {
|
||||
let value = match event_value.unwrap_or(&Value::Null) {
|
||||
Value::Bool(b) => Option::Some(b.to_string()),
|
||||
Value::String(s) => Option::Some(s.to_string()),
|
||||
Value::Number(n) => Option::Some(n.to_string()),
|
||||
Value::Bool(_) => Option::Some(&b_str),
|
||||
Value::String(s) => Option::Some(s),
|
||||
Value::Number(_) => Option::Some(&n_str),
|
||||
_ => Option::None,
|
||||
};
|
||||
value
|
||||
@@ -350,12 +363,7 @@ impl LeafMatcher for DefaultMatcher {
|
||||
}
|
||||
|
||||
// 変換したデータに対してパイプ処理を実行する。
|
||||
let event_value_str = (&self.pipes)
|
||||
.iter()
|
||||
.fold(event_value_str.unwrap(), |acc, pipe| {
|
||||
return pipe.pipe_eventlog_data(acc);
|
||||
});
|
||||
|
||||
let event_value_str = event_value_str.unwrap();
|
||||
if self.key_list.is_empty() {
|
||||
// この場合ただのgrep検索なので、ただ正規表現に一致するかどうか調べればよいだけ
|
||||
return self.re.as_ref().unwrap().is_match(&event_value_str);
|
||||
@@ -376,15 +384,6 @@ enum PipeElement {
|
||||
}
|
||||
|
||||
impl PipeElement {
|
||||
/// WindowsEventLogのJSONデータに対してパイプ処理します。
|
||||
fn pipe_eventlog_data(&self, pattern: String) -> String {
|
||||
return match self {
|
||||
// wildcardはcase insensetiveなので、全て小文字にして比較する。
|
||||
PipeElement::Wildcard => pattern.to_lowercase(),
|
||||
_ => pattern,
|
||||
};
|
||||
}
|
||||
|
||||
/// patternをパイプ処理します
|
||||
fn pipe_pattern(&self, pattern: String) -> String {
|
||||
// enumでポリモーフィズムを実装すると、一つのメソッドに全部の型の実装をする感じになる。Java使い的にはキモイ感じがする。
|
||||
@@ -429,8 +428,6 @@ impl PipeElement {
|
||||
/// PipeElement::Wildcardのパイプ処理です。
|
||||
/// pipe_pattern()に含めて良い処理ですが、複雑な処理になってしまったので別関数にしました。
|
||||
fn pipe_pattern_wildcard(pattern: String) -> String {
|
||||
// wildcardはcase sensetiveなので、全て小文字にして比較する。
|
||||
let pattern = pattern.to_lowercase();
|
||||
let wildcards = vec!["*".to_string(), "?".to_string()];
|
||||
|
||||
// patternをwildcardでsplitした結果をpattern_splitsに入れる
|
||||
@@ -484,7 +481,7 @@ impl PipeElement {
|
||||
}
|
||||
|
||||
// SIGMAルールのwildcard表記から正規表現の表記に変換します。
|
||||
return pattern_splits.iter().enumerate().fold(
|
||||
let ret = pattern_splits.iter().enumerate().fold(
|
||||
String::default(),
|
||||
|acc: String, (idx, pattern)| {
|
||||
let regex_value = if idx % 2 == 0 {
|
||||
@@ -503,6 +500,10 @@ impl PipeElement {
|
||||
return format!("{}{}", acc, regex_value);
|
||||
},
|
||||
);
|
||||
|
||||
// sigmaのwildcardはcase insensitive
|
||||
// なので、正規表現の先頭にcase insensitiveであることを表す記号を付与
|
||||
return "(?i)".to_string() + &ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +548,7 @@ mod tests {
|
||||
updated_date: 2020/11/8
|
||||
"#;
|
||||
let rule_node = parse_rule_from_str(rule_str);
|
||||
let selection_node = &rule_node.detection.unwrap().name_to_selection["selection"];
|
||||
let selection_node = &rule_node.detection.name_to_selection["selection"];
|
||||
|
||||
// Root
|
||||
let detection_childs = selection_node.get_childs();
|
||||
@@ -573,7 +574,7 @@ mod tests {
|
||||
let re = matcher.re.as_ref();
|
||||
assert_eq!(
|
||||
re.unwrap().as_str(),
|
||||
r"Microsoft\-Windows\-PowerShell/Operational".to_lowercase()
|
||||
r"(?i)Microsoft\-Windows\-PowerShell/Operational"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -595,7 +596,7 @@ mod tests {
|
||||
|
||||
assert_eq!(matcher.re.is_some(), true);
|
||||
let re = matcher.re.as_ref();
|
||||
assert_eq!(re.unwrap().as_str(), "4103");
|
||||
assert_eq!(re.unwrap().as_str(), "(?i)4103");
|
||||
}
|
||||
|
||||
// ContextInfo
|
||||
@@ -620,7 +621,7 @@ mod tests {
|
||||
let hostapp_en_matcher = hostapp_en_matcher.downcast_ref::<DefaultMatcher>().unwrap();
|
||||
assert_eq!(hostapp_en_matcher.re.is_some(), true);
|
||||
let re = hostapp_en_matcher.re.as_ref();
|
||||
assert_eq!(re.unwrap().as_str(), "Host Application".to_lowercase());
|
||||
assert_eq!(re.unwrap().as_str(), "(?i)Host Application");
|
||||
|
||||
// LeafSelectionNodeである、ホスト アプリケーションノードが正しいことを確認
|
||||
let hostapp_jp_node = ancestors[1].as_ref() as &dyn SelectionNode;
|
||||
@@ -634,7 +635,7 @@ mod tests {
|
||||
let hostapp_jp_matcher = hostapp_jp_matcher.downcast_ref::<DefaultMatcher>().unwrap();
|
||||
assert_eq!(hostapp_jp_matcher.re.is_some(), true);
|
||||
let re = hostapp_jp_matcher.re.as_ref();
|
||||
assert_eq!(re.unwrap().as_str(), "ホスト アプリケーション");
|
||||
assert_eq!(re.unwrap().as_str(), "(?i)ホスト アプリケーション");
|
||||
}
|
||||
|
||||
// ImagePath
|
||||
@@ -741,7 +742,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -774,7 +775,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -807,7 +808,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -841,7 +842,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -875,7 +876,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -908,7 +909,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -941,7 +942,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -975,7 +976,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1009,7 +1010,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1043,7 +1044,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1077,7 +1078,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1111,7 +1112,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1144,7 +1145,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1181,7 +1182,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1218,7 +1219,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1254,7 +1255,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1299,7 +1300,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1344,7 +1345,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1389,7 +1390,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1434,7 +1435,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1479,7 +1480,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1524,7 +1525,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1557,7 +1558,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1590,7 +1591,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1623,7 +1624,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1636,7 +1637,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_asterisk() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"*ho*ge*".to_string());
|
||||
assert_eq!(".*ho.*ge.*", value);
|
||||
assert_eq!("(?i).*ho.*ge.*", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1644,7 +1645,7 @@ mod tests {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\*ho\*\*ge\*".to_string());
|
||||
// wildcardの「\*」は文字列としての「*」を表す。
|
||||
// 正規表現で「*」はエスケープする必要があるので、\*が正解
|
||||
assert_eq!(r"\*ho\*\*ge\*", value);
|
||||
assert_eq!(r"(?i)\*ho\*\*ge\*", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1652,43 +1653,43 @@ mod tests {
|
||||
// wildcardの「\\*」は文字列としての「\」と正規表現の「.*」を表す。
|
||||
// 文字列としての「\」はエスケープされるので、「\\.*」が正解
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\\*ho\\*ge\\*".to_string());
|
||||
assert_eq!(r"\\.*ho\\.*ge\\.*", value);
|
||||
assert_eq!(r"(?i)\\.*ho\\.*ge\\.*", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_question() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"?ho?ge?".to_string());
|
||||
assert_eq!(r".ho.ge.", value);
|
||||
assert_eq!(r"(?i).ho.ge.", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_question2() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\?ho\?ge\?".to_string());
|
||||
assert_eq!(r"\?ho\?ge\?", value);
|
||||
assert_eq!(r"(?i)\?ho\?ge\?", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_question3() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\\?ho\\?ge\\?".to_string());
|
||||
assert_eq!(r"\\.ho\\.ge\\.", value);
|
||||
assert_eq!(r"(?i)\\.ho\\.ge\\.", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_backshash() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\\ho\\ge\\".to_string());
|
||||
assert_eq!(r"\\\\ho\\\\ge\\\\", value);
|
||||
assert_eq!(r"(?i)\\\\ho\\\\ge\\\\", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_mixed() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\\*\****\*\\*".to_string());
|
||||
assert_eq!(r"\\.*\*.*.*.*\*\\.*", value);
|
||||
assert_eq!(r"(?i)\\.*\*.*.*.*\*\\.*", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_pattern_wildcard_many_backshashs() {
|
||||
let value = PipeElement::pipe_pattern_wildcard(r"\\\*ho\\\*ge\\\".to_string());
|
||||
assert_eq!(r"\\\\.*ho\\\\.*ge\\\\\\", value);
|
||||
assert_eq!(r"(?i)\\\\.*ho\\\\.*ge\\\\\\", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1712,11 +1713,10 @@ mod tests {
|
||||
match serde_json::from_str(record_json_str) {
|
||||
Ok(rec) => {
|
||||
let rec: Value = rec;
|
||||
let recstr = rec.to_string();
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: rec,
|
||||
data_string: recstr,
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1747,11 +1747,10 @@ mod tests {
|
||||
match serde_json::from_str(record_json_str) {
|
||||
Ok(record) => {
|
||||
let rec: Value = record;
|
||||
let recstr = rec.to_string();
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: rec,
|
||||
data_string: recstr,
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -1786,7 +1785,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -1821,7 +1820,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn create_rule(rulepath: String, yaml: Yaml) -> RuleNode {
|
||||
pub struct RuleNode {
|
||||
pub rulepath: String,
|
||||
pub yaml: Yaml,
|
||||
detection: Option<DetectionNode>,
|
||||
detection: DetectionNode,
|
||||
countdata: HashMap<String, HashMap<String, Vec<DateTime<Utc>>>>,
|
||||
}
|
||||
|
||||
@@ -38,13 +38,14 @@ impl Debug for RuleNode {
|
||||
}
|
||||
|
||||
unsafe impl Sync for RuleNode {}
|
||||
unsafe impl Send for RuleNode {}
|
||||
|
||||
impl RuleNode {
|
||||
pub fn new(rulepath: String, yaml: Yaml) -> RuleNode {
|
||||
return RuleNode {
|
||||
rulepath: rulepath,
|
||||
yaml: yaml,
|
||||
detection: Option::None,
|
||||
detection: DetectionNode::new(),
|
||||
countdata: HashMap::new(),
|
||||
};
|
||||
}
|
||||
@@ -52,18 +53,11 @@ impl RuleNode {
|
||||
pub fn init(&mut self) -> Result<(), Vec<String>> {
|
||||
let mut errmsgs: Vec<String> = vec![];
|
||||
|
||||
// SIGMAルールを受け入れるため、outputがなくてもOKにする。
|
||||
// if self.yaml["output"].as_str().unwrap_or("").is_empty() {
|
||||
// errmsgs.push("Cannot find required key. key:output".to_string());
|
||||
// }
|
||||
|
||||
// detection node initialization
|
||||
let mut detection = DetectionNode::new();
|
||||
let detection_result = detection.init(&self.yaml["detection"]);
|
||||
let detection_result = self.detection.init(&self.yaml["detection"]);
|
||||
if detection_result.is_err() {
|
||||
errmsgs.extend(detection_result.unwrap_err());
|
||||
}
|
||||
self.detection = Option::Some(detection);
|
||||
|
||||
if errmsgs.is_empty() {
|
||||
return Result::Ok(());
|
||||
@@ -73,10 +67,7 @@ impl RuleNode {
|
||||
}
|
||||
|
||||
pub fn select(&mut self, filepath: &String, event_record: &EvtxRecordInfo) -> bool {
|
||||
if self.detection.is_none() {
|
||||
return false;
|
||||
}
|
||||
let result = self.detection.as_ref().unwrap().select(event_record);
|
||||
let result = self.detection.select(event_record);
|
||||
if result {
|
||||
count::count(self, filepath, &event_record.record);
|
||||
}
|
||||
@@ -84,12 +75,7 @@ impl RuleNode {
|
||||
}
|
||||
/// aggregation conditionが存在するかを返す関数
|
||||
pub fn has_agg_condition(&self) -> bool {
|
||||
return self
|
||||
.detection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.aggregation_condition
|
||||
.is_some();
|
||||
return self.detection.aggregation_condition.is_some();
|
||||
}
|
||||
/// Aggregation Conditionの結果を配列で返却する関数
|
||||
pub fn judge_satisfy_aggcondition(&self) -> Vec<AggResult> {
|
||||
@@ -109,8 +95,8 @@ impl RuleNode {
|
||||
|
||||
/// Ruleファイルのdetectionを表すノード
|
||||
struct DetectionNode {
|
||||
pub name_to_selection: HashMap<String, Arc<Box<dyn SelectionNode + Send + Sync>>>,
|
||||
pub condition: Option<Box<dyn SelectionNode + Send + Sync>>,
|
||||
pub name_to_selection: HashMap<String, Arc<Box<dyn SelectionNode>>>,
|
||||
pub condition: Option<Box<dyn SelectionNode>>,
|
||||
pub aggregation_condition: Option<AggregationParseInfo>,
|
||||
pub timeframe: Option<TimeFrameInfo>,
|
||||
}
|
||||
@@ -237,10 +223,7 @@ impl DetectionNode {
|
||||
}
|
||||
|
||||
/// selectionをパースします。
|
||||
fn parse_selection(
|
||||
&self,
|
||||
selection_yaml: &Yaml,
|
||||
) -> Option<Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn parse_selection(&self, selection_yaml: &Yaml) -> Option<Box<dyn SelectionNode>> {
|
||||
return Option::Some(self.parse_selection_recursively(vec![], selection_yaml));
|
||||
}
|
||||
|
||||
@@ -249,7 +232,7 @@ impl DetectionNode {
|
||||
&self,
|
||||
key_list: Vec<String>,
|
||||
yaml: &Yaml,
|
||||
) -> Box<dyn SelectionNode + Send + Sync> {
|
||||
) -> Box<dyn SelectionNode> {
|
||||
if yaml.as_hash().is_some() {
|
||||
// 連想配列はAND条件と解釈する
|
||||
let yaml_hash = yaml.as_hash().unwrap();
|
||||
@@ -355,7 +338,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -388,7 +371,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -421,7 +404,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -507,7 +490,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -569,7 +552,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -638,7 +621,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -685,7 +668,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -733,7 +716,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -800,7 +783,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -867,7 +850,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -916,7 +899,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -977,13 +960,10 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_str.to_string(),
|
||||
};
|
||||
let result = rule_node.select(&"testpath".to_string(), &recinfo);
|
||||
assert_eq!(
|
||||
rule_node.detection.unwrap().aggregation_condition.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rule_node.detection.aggregation_condition.is_some(), true);
|
||||
assert_eq!(result, true);
|
||||
assert_eq!(
|
||||
*&rule_node
|
||||
|
||||
@@ -19,21 +19,18 @@ pub trait SelectionNode: mopa::Any {
|
||||
fn init(&mut self) -> Result<(), Vec<String>>;
|
||||
|
||||
// 子ノードを取得する(グラフ理論のchildと同じ意味)
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>>;
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode>>;
|
||||
|
||||
// 子孫ノードを取得する(グラフ理論のdescendantと同じ意味)
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>>;
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode>>;
|
||||
}
|
||||
mopafy!(SelectionNode);
|
||||
|
||||
/// detection - selection配下でAND条件を表すノード
|
||||
pub struct AndSelectionNode {
|
||||
pub child_nodes: Vec<Box<dyn SelectionNode + Send + Sync>>,
|
||||
pub child_nodes: Vec<Box<dyn SelectionNode>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for AndSelectionNode {}
|
||||
unsafe impl Sync for AndSelectionNode {}
|
||||
|
||||
impl AndSelectionNode {
|
||||
pub fn new() -> AndSelectionNode {
|
||||
return AndSelectionNode {
|
||||
@@ -76,7 +73,7 @@ impl SelectionNode for AndSelectionNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
let mut ret = vec![];
|
||||
self.child_nodes.iter().for_each(|child_node| {
|
||||
ret.push(child_node);
|
||||
@@ -85,7 +82,7 @@ impl SelectionNode for AndSelectionNode {
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
let mut ret = self.get_childs();
|
||||
|
||||
self.child_nodes
|
||||
@@ -104,12 +101,9 @@ impl SelectionNode for AndSelectionNode {
|
||||
|
||||
/// detection - selection配下でOr条件を表すノード
|
||||
pub struct OrSelectionNode {
|
||||
pub child_nodes: Vec<Box<dyn SelectionNode + Send + Sync>>,
|
||||
pub child_nodes: Vec<Box<dyn SelectionNode>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for OrSelectionNode {}
|
||||
unsafe impl Sync for OrSelectionNode {}
|
||||
|
||||
impl OrSelectionNode {
|
||||
pub fn new() -> OrSelectionNode {
|
||||
return OrSelectionNode {
|
||||
@@ -152,7 +146,7 @@ impl SelectionNode for OrSelectionNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
let mut ret = vec![];
|
||||
self.child_nodes.iter().for_each(|child_node| {
|
||||
ret.push(child_node);
|
||||
@@ -161,7 +155,7 @@ impl SelectionNode for OrSelectionNode {
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
let mut ret = self.get_childs();
|
||||
|
||||
self.child_nodes
|
||||
@@ -180,14 +174,11 @@ impl SelectionNode for OrSelectionNode {
|
||||
|
||||
/// conditionでNotを表すノード
|
||||
pub struct NotSelectionNode {
|
||||
node: Box<dyn SelectionNode + Send + Sync>,
|
||||
node: Box<dyn SelectionNode>,
|
||||
}
|
||||
|
||||
unsafe impl Send for NotSelectionNode {}
|
||||
unsafe impl Sync for NotSelectionNode {}
|
||||
|
||||
impl NotSelectionNode {
|
||||
pub fn new(node: Box<dyn SelectionNode + Send + Sync>) -> NotSelectionNode {
|
||||
pub fn new(node: Box<dyn SelectionNode>) -> NotSelectionNode {
|
||||
return NotSelectionNode { node: node };
|
||||
}
|
||||
}
|
||||
@@ -201,11 +192,11 @@ impl SelectionNode for NotSelectionNode {
|
||||
return Result::Ok(());
|
||||
}
|
||||
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
return self.get_childs();
|
||||
}
|
||||
}
|
||||
@@ -215,14 +206,11 @@ pub struct RefSelectionNode {
|
||||
// selection_nodeはDetectionNodeのname_2_nodeが所有権を持っていて、RefSelectionNodeのselection_nodeに所有権を持たせることができない。
|
||||
// そこでArcを使って、DetectionNodeのname_2_nodeとRefSelectionNodeのselection_nodeで所有権を共有する。
|
||||
// RcじゃなくてArcなのはマルチスレッド対応のため
|
||||
selection_node: Arc<Box<dyn SelectionNode + Send + Sync>>,
|
||||
selection_node: Arc<Box<dyn SelectionNode>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for RefSelectionNode {}
|
||||
unsafe impl Sync for RefSelectionNode {}
|
||||
|
||||
impl RefSelectionNode {
|
||||
pub fn new(selection_node: Arc<Box<dyn SelectionNode + Send + Sync>>) -> RefSelectionNode {
|
||||
pub fn new(selection_node: Arc<Box<dyn SelectionNode>>) -> RefSelectionNode {
|
||||
return RefSelectionNode {
|
||||
selection_node: selection_node,
|
||||
};
|
||||
@@ -238,35 +226,38 @@ impl SelectionNode for RefSelectionNode {
|
||||
return Result::Ok(());
|
||||
}
|
||||
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
return vec![&self.selection_node];
|
||||
}
|
||||
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
return self.get_childs();
|
||||
}
|
||||
}
|
||||
|
||||
/// detection - selection配下の末端ノード
|
||||
pub struct LeafSelectionNode {
|
||||
key: String,
|
||||
key_list: Vec<String>,
|
||||
select_value: Yaml,
|
||||
pub matcher: Option<Box<dyn matchers::LeafMatcher>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for LeafSelectionNode {}
|
||||
unsafe impl Sync for LeafSelectionNode {}
|
||||
|
||||
impl LeafSelectionNode {
|
||||
pub fn new(key_list: Vec<String>, value_yaml: Yaml) -> LeafSelectionNode {
|
||||
return LeafSelectionNode {
|
||||
key: String::default(),
|
||||
key_list: key_list,
|
||||
select_value: value_yaml,
|
||||
matcher: Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> String {
|
||||
pub fn get_key(&self) -> &String {
|
||||
return &self.key;
|
||||
}
|
||||
|
||||
fn _create_key(&self) -> String {
|
||||
if self.key_list.is_empty() {
|
||||
return String::default();
|
||||
}
|
||||
@@ -397,6 +388,7 @@ impl SelectionNode for LeafSelectionNode {
|
||||
)]);
|
||||
}
|
||||
|
||||
self.key = self._create_key();
|
||||
return self
|
||||
.matcher
|
||||
.as_mut()
|
||||
@@ -404,11 +396,11 @@ impl SelectionNode for LeafSelectionNode {
|
||||
.init(&match_key_list, &self.select_value);
|
||||
}
|
||||
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_childs(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode + Send + Sync>> {
|
||||
fn get_descendants(&self) -> Vec<&Box<dyn SelectionNode>> {
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
@@ -441,7 +433,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -477,7 +469,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
@@ -512,7 +504,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -547,7 +539,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
|
||||
}
|
||||
@@ -582,7 +574,7 @@ mod tests {
|
||||
let recinfo = EvtxRecordInfo {
|
||||
evtx_filepath: "testpath".to_owned(),
|
||||
record: record,
|
||||
data_string: String::default(),
|
||||
data_string: record_json_str.to_string(),
|
||||
};
|
||||
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
|
||||
}
|
||||
|
||||
@@ -129,35 +129,32 @@ pub fn get_serde_number_to_string(value: &serde_json::Value) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
// alias.txtについて、指定されたevent_keyに対応するaliasを取得します。
|
||||
pub fn get_alias(event_key: &String) -> Option<String> {
|
||||
let conf = configs::CONFIG.read().unwrap();
|
||||
let keyvalues = &conf.event_key_alias_config.get_event_key_values();
|
||||
let value = keyvalues
|
||||
.iter()
|
||||
.find(|(_, cur_event_key)| &event_key == cur_event_key);
|
||||
|
||||
if value.is_none() {
|
||||
return Option::None;
|
||||
} else {
|
||||
return Option::Some(value.unwrap().0.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_event_value<'a>(key: &String, event_value: &'a Value) -> Option<&'a Value> {
|
||||
if key.len() == 0 {
|
||||
return Option::None;
|
||||
}
|
||||
let singleton = configs::CONFIG.read().unwrap();
|
||||
let event_key = match singleton
|
||||
.event_key_alias_config
|
||||
.get_event_key(key.to_string())
|
||||
{
|
||||
Some(alias_event_key) => alias_event_key,
|
||||
None => key,
|
||||
};
|
||||
|
||||
let event_key = configs::EVENTKEY_ALIAS.get_event_key(key);
|
||||
if let Some(event_key) = event_key {
|
||||
let mut ret: &Value = event_value;
|
||||
// get_event_keyが取得できてget_event_key_splitが取得できないことはない
|
||||
let splits = configs::EVENTKEY_ALIAS.get_event_key_split(key);
|
||||
let mut start_idx = 0;
|
||||
for key in splits.unwrap() {
|
||||
if ret.is_object() == false {
|
||||
return Option::None;
|
||||
}
|
||||
|
||||
let val = &event_key[start_idx..(*key + start_idx)];
|
||||
ret = &ret[val];
|
||||
start_idx = *key + start_idx;
|
||||
start_idx += 1;
|
||||
}
|
||||
|
||||
return Option::Some(ret);
|
||||
} else {
|
||||
let mut ret: &Value = event_value;
|
||||
let event_key = key;
|
||||
for key in event_key.split(".") {
|
||||
if ret.is_object() == false {
|
||||
return Option::None;
|
||||
@@ -167,6 +164,7 @@ pub fn get_event_value<'a>(key: &String, event_value: &'a Value) -> Option<&'a V
|
||||
|
||||
return Option::Some(ret);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_thread_num() -> usize {
|
||||
let def_thread_num_str = num_cpus::get().to_string();
|
||||
|
||||
65
src/main.rs
65
src/main.rs
@@ -15,6 +15,7 @@ use hhmmss::Hhmmss;
|
||||
use pbr::ProgressBar;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::PathBuf,
|
||||
@@ -125,7 +126,7 @@ fn analysis_files(evtx_files: Vec<PathBuf>) {
|
||||
.read()
|
||||
.unwrap()
|
||||
.args
|
||||
.value_of("level")
|
||||
.value_of("min-level")
|
||||
.unwrap_or("informational")
|
||||
.to_uppercase();
|
||||
println!("Analyzing event files: {:?}", evtx_files.len());
|
||||
@@ -187,20 +188,11 @@ fn analysis_file(
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = record_result.unwrap().data;
|
||||
|
||||
// target_eventids.txtでフィルタする。
|
||||
let eventid = utils::get_event_value(&utils::get_event_id_key(), &data);
|
||||
if eventid.is_some() {
|
||||
let is_target = match eventid.unwrap() {
|
||||
Value::String(s) => utils::is_target_event_id(s),
|
||||
Value::Number(n) => utils::is_target_event_id(&n.to_string()),
|
||||
_ => true, // レコードからEventIdが取得できない場合は、特にフィルタしない
|
||||
};
|
||||
if !is_target {
|
||||
let data = record_result.unwrap().data;
|
||||
if _is_target_event_id(&data) == false {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let eventtime = utils::get_event_value(&utils::get_event_time(), &data);
|
||||
if eventtime.is_some() {
|
||||
@@ -211,9 +203,7 @@ fn analysis_file(
|
||||
}
|
||||
|
||||
// EvtxRecordInfo構造体に変更
|
||||
let data_string = data.to_string();
|
||||
let record_info = EvtxRecordInfo::new((&filepath_disp).to_string(), data, data_string);
|
||||
records_per_detect.push(record_info);
|
||||
records_per_detect.push(_create_rec_info(data, &filepath_disp));
|
||||
}
|
||||
if records_per_detect.len() == 0 {
|
||||
break;
|
||||
@@ -233,6 +223,51 @@ fn analysis_file(
|
||||
return detection;
|
||||
}
|
||||
|
||||
// target_eventids.txtの設定を元にフィルタする。
|
||||
fn _is_target_event_id(data: &Value) -> bool {
|
||||
let eventid = utils::get_event_value(&utils::get_event_id_key(), data);
|
||||
if eventid.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
return match eventid.unwrap() {
|
||||
Value::String(s) => utils::is_target_event_id(s),
|
||||
Value::Number(n) => utils::is_target_event_id(&n.to_string()),
|
||||
_ => true, // レコードからEventIdが取得できない場合は、特にフィルタしない
|
||||
};
|
||||
}
|
||||
|
||||
// EvtxRecordInfoを作成します。
|
||||
fn _create_rec_info(mut data: Value, path: &dyn Display) -> EvtxRecordInfo {
|
||||
// 高速化のための処理
|
||||
// RuleNodeでワイルドカードや正規表現のマッチング処理をする際には、
|
||||
// Value(JSON)がstring型以外の場合はstringに変換して比較している。
|
||||
// RuleNodeでマッチングする毎にstring変換していると、
|
||||
// 1回の処理はそこまででもないが相当回数呼び出されれるとボトルネックになりうる。
|
||||
|
||||
// なので、よく使われるstring型ではない値を事前に変換しておくことで、
|
||||
// string変換する回数を減らせる。
|
||||
// 本当はやりたくないが...
|
||||
match &data["Event"]["System"]["EventID"] {
|
||||
Value::Number(n) => data["Event"]["System"]["EventID"] = Value::String(n.to_string()),
|
||||
_ => (),
|
||||
};
|
||||
match &data["Event"]["EventData"]["LogonType"] {
|
||||
Value::Number(n) => data["Event"]["EventData"]["LogonType"] = Value::String(n.to_string()),
|
||||
_ => (),
|
||||
}
|
||||
match &data["Event"]["EventData"]["DestinationPort"] {
|
||||
Value::Number(n) => {
|
||||
data["Event"]["EventData"]["DestinationPort"] = Value::String(n.to_string())
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// EvtxRecordInfoを作る
|
||||
let data_str = data.to_string();
|
||||
return EvtxRecordInfo::new(path.to_string(), data, data_str);
|
||||
}
|
||||
|
||||
fn evtx_to_jsons(evtx_filepath: PathBuf) -> Option<EvtxParser<File>> {
|
||||
match EvtxParser::from_path(evtx_filepath) {
|
||||
Ok(evtx_parser) => {
|
||||
|
||||
24
src/yaml.rs
24
src/yaml.rs
@@ -152,6 +152,19 @@ impl ParseYaml {
|
||||
}
|
||||
}
|
||||
|
||||
if !configs::CONFIG
|
||||
.read()
|
||||
.unwrap()
|
||||
.args
|
||||
.is_present("show-deprecated")
|
||||
{
|
||||
let rule_status = &yaml_doc["status"].as_str();
|
||||
if rule_status.is_some() && rule_status.unwrap() == "deprecated" {
|
||||
self.ignorerule_count += 1;
|
||||
return Option::None;
|
||||
}
|
||||
}
|
||||
|
||||
return Option::Some((filepath, yaml_doc));
|
||||
})
|
||||
.collect();
|
||||
@@ -279,4 +292,15 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(yaml.ignorerule_count, 0);
|
||||
}
|
||||
#[test]
|
||||
fn test_exclude_deprecated_rules_file() {
|
||||
let mut yaml = yaml::ParseYaml::new();
|
||||
let path = Path::new("test_files/rules/deprecated");
|
||||
let exclude_ids = RuleExclude {
|
||||
no_use_rule: HashSet::new(),
|
||||
};
|
||||
yaml.read_dir(path.to_path_buf(), &"", &exclude_ids)
|
||||
.unwrap();
|
||||
assert_eq!(yaml.ignorerule_count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
30
test_files/rules/deprecated/1.yml
Normal file
30
test_files/rules/deprecated/1.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
title: CreateMiniDump Hacktool
|
||||
author: Florian Roth
|
||||
date: 2019/12/22
|
||||
description: Detects the use of CreateMiniDump hack tool used to dump the LSASS process
|
||||
memory for credential extraction on the attacker's machine
|
||||
detection:
|
||||
SELECTION_1:
|
||||
EventID: 11
|
||||
SELECTION_2:
|
||||
TargetFilename: '*\lsass.dmp'
|
||||
condition: (SELECTION_1 and SELECTION_2)
|
||||
falsepositives:
|
||||
- Unknown
|
||||
id: db2110f3-479d-42a6-94fb-d35bc1e46492
|
||||
level: high
|
||||
logsource:
|
||||
category: file_event
|
||||
product: windows
|
||||
modified: 2021/09/19
|
||||
references:
|
||||
- https://ired.team/offensive-security/credential-access-and-credential-dumping/dumping-lsass-passwords-without-mimikatz-minidumpwritedump-av-signature-bypass
|
||||
related:
|
||||
- id: 36d88494-1d43-4dc0-b3fa-35c8fea0ca9d
|
||||
type: derived
|
||||
status: deprecated
|
||||
tags:
|
||||
- attack.credential_access
|
||||
- attack.t1003.001
|
||||
- attack.t1003
|
||||
ruletype: SIGMA
|
||||
Reference in New Issue
Block a user