Merge branch 'main' into feature/start_finish_time

This commit is contained in:
itiB
2021-12-16 20:12:01 +09:00
15 changed files with 345 additions and 288 deletions

30
Cargo.lock generated
View File

@@ -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]]

View File

@@ -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

View File

@@ -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(())
}

View File

@@ -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() {

View File

@@ -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個実行します。

View File

@@ -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>> {

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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) => {

View File

@@ -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);
}
}

View 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