From 1adcb8c44bd07dc73eccb226e38eb9a9dff6d16e Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Sun, 22 Nov 2020 12:12:05 +0900 Subject: [PATCH] refactoring --- src/detections/configs.rs | 33 +----- src/detections/rule.rs | 27 +---- src/detections/utils.rs | 231 ++++++-------------------------------- 3 files changed, 39 insertions(+), 252 deletions(-) diff --git a/src/detections/configs.rs b/src/detections/configs.rs index d0a8ef83..6f7fc38d 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -1,13 +1,9 @@ +use crate::detections::utils; use clap::{App, AppSettings, Arg, ArgMatches}; use std::collections::HashMap; -use std::fs::File; -use std::io::prelude::*; use std::sync::Once; - #[derive(Clone)] pub struct SingletonReader { - pub regex: Vec>, - pub whitelist: Vec>, pub args: ArgMatches<'static>, pub event_key_alias_config: EventKeyAliasConfig, } @@ -19,8 +15,6 @@ pub fn singleton() -> Box { unsafe { ONCE.call_once(|| { let singleton = SingletonReader { - regex: read_csv("regexes.txt"), - whitelist: read_csv("whitelist.txt"), args: build_app().get_matches(), event_key_alias_config: load_eventkey_alias(), }; @@ -79,7 +73,7 @@ impl EventKeyAliasConfig { fn load_eventkey_alias() -> EventKeyAliasConfig { let mut config = EventKeyAliasConfig::new(); - read_csv("config/eventkey_alias.txt") + utils::read_csv("config/eventkey_alias.txt") .into_iter() .for_each(|line| { if line.len() != 2 { @@ -100,26 +94,3 @@ fn load_eventkey_alias() -> EventKeyAliasConfig { return config; } - -fn read_csv(filename: &str) -> Vec> { - let mut f = File::open(filename).expect("file not found!!!"); - let mut contents: String = String::new(); - let mut ret = vec![]; - if f.read_to_string(&mut contents).is_err() { - return ret; - } - - let mut rdr = csv::Reader::from_reader(contents.as_bytes()); - rdr.records().for_each(|r| { - if r.is_err() { - return; - } - - let line = r.unwrap(); - let mut v = vec![]; - line.iter().for_each(|s| v.push(s.to_string())); - ret.push(v); - }); - - return ret; -} diff --git a/src/detections/rule.rs b/src/detections/rule.rs index dfe48b8b..89d0bb63 100644 --- a/src/detections/rule.rs +++ b/src/detections/rule.rs @@ -1,6 +1,7 @@ extern crate regex; -use crate::detections::configs; +use crate::detections::utils; + use regex::Regex; use serde_json::Value; use yaml_rust::Yaml; @@ -25,28 +26,6 @@ fn parse_detection(yaml: &Yaml) -> Option { } } -pub fn get_event_value<'a>(key: &String, event_value: &'a Value) -> Option<&'a Value> { - if key.len() == 0 { - return Option::None; - } - - let alias_config = configs::singleton().event_key_alias_config; - let event_key = match alias_config.get_event_key(key.to_string()) { - Some(alias_event_key) => alias_event_key, - None => key, - }; - - let mut ret: &Value = event_value; - for key in event_key.split(".") { - if ret.is_object() == false { - return Option::None; - } - ret = &ret[key]; - } - - return Option::Some(ret); -} - fn concat_selection_key(key_list: &Vec) -> String { return key_list .iter() @@ -261,7 +240,7 @@ impl LeafSelectionNode { return Option::None; } - return get_event_value(&self.key_list[0].to_string(), event_value); + return utils::get_event_value(&self.key_list[0].to_string(), event_value); } // LeafMatcherの一覧を取得する。 diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 728d3199..db3ddc97 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -3,160 +3,21 @@ extern crate csv; extern crate regex; use crate::detections::configs; -use flate2::read::GzDecoder; + use regex::Regex; +use serde_json::Value; use std::fs::File; use std::io::prelude::*; use std::str; use std::string::String; -pub fn check_command( - event_id: usize, - commandline: &str, - minlength: usize, - servicecmd: usize, - servicename: &str, - creator: &str, -) { - let mut text = "".to_string(); - let mut base64 = "".to_string(); - - let ret = check_whitelist(commandline, "whitelist.txt"); - if (ret) { - return; - } - - if commandline.len() > minlength { - text.push_str("Long Command Line: greater than "); - text.push_str(&minlength.to_string()); - text.push_str("bytes\n"); - } - text.push_str(&check_obfu(commandline)); - text.push_str(&check_regex_old(commandline, 0)); - text.push_str(&check_creator(commandline, creator)); - if Regex::new(r"\-enc.*[A-Za-z0-9/+=]{100}") - .unwrap() - .is_match(commandline) - { - let re = Regex::new(r"^.* \-Enc(odedCommand)? ").unwrap(); - base64.push_str(&re.replace_all(commandline, "")); - } else if Regex::new(r":FromBase64String\(") - .unwrap() - .is_match(commandline) - { - let re = Regex::new(r"^.*:FromBase64String\('*").unwrap(); - base64.push_str(&re.replace_all(commandline, "")); - let re = Regex::new(r"'.*$").unwrap(); - base64.push_str(&re.replace_all(&base64.to_string(), "")); - } - if let Ok(decoded) = base64::decode(&base64) { - if !base64.is_empty() { - if Regex::new(r"Compression.GzipStream.*Decompress") - .unwrap() - .is_match(commandline) - { - let mut d = GzDecoder::new(decoded.as_slice()); - let mut uncompressed = String::new(); - d.read_to_string(&mut uncompressed).unwrap(); - println!("Decoded : {}", uncompressed); - text.push_str("Base64-encoded and compressed function\n"); - } else { - println!("Decoded : {}", str::from_utf8(decoded.as_slice()).unwrap()); - text.push_str("Base64-encoded function\n"); - text.push_str(&check_obfu(str::from_utf8(decoded.as_slice()).unwrap())); - text.push_str(&check_regex_old( - str::from_utf8(decoded.as_slice()).unwrap(), - 0, - )); - } - } - } - if !text.is_empty() { - println!("EventID : {}", event_id); - if servicecmd != 0 { - println!("Message : Suspicious Service Command"); - println!("Results : Service name: {}\n", servicename); - } else { - println!("Message : Suspicious Command Line"); - } - println!("command : {}", commandline); - println!("result : {}", text); - } -} - -fn check_obfu(string: &str) -> std::string::String { - let mut obfutext = "".to_string(); - let lowercasestring = string.to_lowercase(); - let length = lowercasestring.len() as f64; - let mut minpercent = 0.65; - let maxbinary = 0.50; - - let mut re = Regex::new(r"[a-z0-9/¥;:|.]").unwrap(); - let noalphastring = re.replace_all(&lowercasestring, ""); - - re = Regex::new(r"[01]").unwrap(); - let nobinarystring = re.replace_all(&lowercasestring, ""); - - if length > 0.0 { - let mut percent = (length - noalphastring.len() as f64) / length; - if ((length / 100.0) as f64) < minpercent { - minpercent = length / 100.0; - } - - if percent < minpercent { - obfutext.push_str("Possible command obfuscation: only "); - let percent = (percent * 100.0) as usize; - obfutext.push_str(&percent.to_string()); - obfutext.push_str("% alphanumeric and common symbols\n"); - } - - percent = ((nobinarystring.len().wrapping_sub(length as usize) as f64) / length) / length; - let binarypercent = 1.0 - percent; - if binarypercent > maxbinary { - obfutext.push_str("Possible command obfuscation: "); - let binarypercent = (binarypercent * 100.0) as usize; - obfutext.push_str(&binarypercent.to_string()); - obfutext.push_str("% zeroes and ones (possible numeric or binary encoding)\n"); - } - } - return obfutext; -} - -pub fn check_regex_old(string: &str, r#type: usize) -> std::string::String { +pub fn check_regex( + string: &str, + r#type: usize, + regex_list: &Vec>, +) -> std::string::String { let empty = "".to_string(); let mut regextext = "".to_string(); - for line in configs::singleton().regex { - let type_str = line.get(0).unwrap_or(&empty); - if type_str != &r#type.to_string() { - continue; - } - - let regex_str = line.get(1).unwrap_or(&empty); - if regex_str.is_empty() { - continue; - } - - let re = Regex::new(regex_str); - if re.is_err() || re.unwrap().is_match(string) == false { - continue; - } - - let text = line.get(2).unwrap_or(&empty); - if text.is_empty() { - continue; - } - - regextext.push_str(text); - regextext.push_str("\n"); - } - - return regextext; -} - -pub fn check_regex(string: &str, r#type: usize, regex_path: &str) -> std::string::String { - let empty = "".to_string(); - let mut regextext = "".to_string(); - let regex_list = read_csv(regex_path); for line in regex_list { let type_str = line.get(0).unwrap_or(&empty); if type_str != &r#type.to_string() { @@ -185,9 +46,8 @@ pub fn check_regex(string: &str, r#type: usize, regex_path: &str) -> std::string return regextext; } -pub fn check_whitelist(target: &str, whitelist_path: &str) -> bool { +pub fn check_whitelist(target: &str, whitelist: &Vec>) -> bool { let empty = "".to_string(); - let whitelist = read_csv(whitelist_path); for line in whitelist { let r_str = line.get(0).unwrap_or(&empty); if r_str.is_empty() { @@ -203,7 +63,7 @@ pub fn check_whitelist(target: &str, whitelist_path: &str) -> bool { return false; } -fn read_csv(filename: &str) -> Vec> { +pub fn read_csv(filename: &str) -> Vec> { let mut f = File::open(filename).expect("file not found!!!"); let mut contents: String = String::new(); let mut ret = vec![]; @@ -226,22 +86,26 @@ fn read_csv(filename: &str) -> Vec> { return ret; } -fn check_creator(command: &str, creator: &str) -> std::string::String { - let mut creatortext = "".to_string(); - if !creator.is_empty() { - if command == "powershell" { - if creator == "PSEXESVC" { - creatortext.push_str("PowerShell launched via PsExec: "); - creatortext.push_str(creator); - creatortext.push_str("\n"); - } else if creator == "WmiPrvSE" { - creatortext.push_str("PowerShell launched via WMI: "); - creatortext.push_str(creator); - creatortext.push_str("\n"); - } - } +pub fn get_event_value<'a>(key: &String, event_value: &'a Value) -> Option<&'a Value> { + if key.len() == 0 { + return Option::None; } - return creatortext; + + let alias_config = configs::singleton().event_key_alias_config; + let event_key = match alias_config.get_event_key(key.to_string()) { + Some(alias_event_key) => alias_event_key, + None => key, + }; + + let mut ret: &Value = event_value; + for key in event_key.split(".") { + if ret.is_object() == false { + return Option::None; + } + ret = &ret[key]; + } + + return Option::Some(ret); } #[cfg(test)] @@ -249,48 +113,21 @@ mod tests { use crate::detections::utils; #[test] fn test_check_regex() { - let regextext = utils::check_regex("\\cvtres.exe", 0, "regexes.txt"); + let regexes = utils::read_csv("regexes.txt"); + let regextext = utils::check_regex("\\cvtres.exe", 0, ®exes); assert!(regextext == "Resource File To COFF Object Conversion Utility cvtres.exe\n"); - let regextext = utils::check_regex("\\hogehoge.exe", 0, "regexes.txt"); + let regextext = utils::check_regex("\\hogehoge.exe", 0, ®exes); assert!(regextext == ""); } - #[test] - fn test_check_creator() { - let mut creatortext = utils::check_creator("powershell", "PSEXESVC"); - assert!(creatortext == "PowerShell launched via PsExec: PSEXESVC\n"); - creatortext = utils::check_creator("powershell", "WmiPrvSE"); - assert!(creatortext == "PowerShell launched via WMI: WmiPrvSE\n"); - } - - #[test] - fn test_check_obfu() { - let obfutext = utils::check_obfu("string"); - assert!(obfutext == "Possible command obfuscation: 100% zeroes and ones (possible numeric or binary encoding)\n"); - } - - #[test] - fn test_check_command() { - utils::check_command(1, "dir", 100, 100, "dir", "dir"); - - //test return with whitelist. - utils::check_command( - 1, - "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\"", - 100, - 100, - "dir", - "dir", - ); - } - #[test] fn test_check_whitelist() { let commandline = "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\""; - assert!(true == utils::check_whitelist(commandline, "whitelist.txt")); + let whitelist = utils::read_csv("whitelist.txt"); + assert!(true == utils::check_whitelist(commandline, &whitelist)); let commandline = "\"C:\\Program Files\\Google\\Update\\GoogleUpdate2.exe\""; - assert!(false == utils::check_whitelist(commandline, "whitelist.txt")); + assert!(false == utils::check_whitelist(commandline, &whitelist)); } }