diff --git a/config/eventkey_alias.txt b/config/eventkey_alias.txt index dda9ee24..be9beeac 100644 --- a/config/eventkey_alias.txt +++ b/config/eventkey_alias.txt @@ -1,5 +1,5 @@ alias,event_key -EventID,Event.System.EventId +EventID,Event.System.EventID Channel,Event.System.Channel CommandLine,Event.EventData.CommandLine Signed,Event.EventData.Signed @@ -9,4 +9,7 @@ TargetUserName,Event.EventData.TargetUserName param1,Event.EventData.param1 param2,Event.EventData.param2 ServiceName,Event.EventData.ServiceName -ImagePath,Event.EventData.ImagePath \ No newline at end of file +ImagePath,Event.EventData.ImagePath +ContextInfo,Event.EventData.ContextInfo +Path,Event.EventData.Path +ScriptBlockText,Event.EventData.ScriptBlockText#Name \ No newline at end of file diff --git a/rules/deep_blue_cli/powershell/4103.yml b/rules/deep_blue_cli/powershell/4103.yml index 709e360a..8bcefcff 100644 --- a/rules/deep_blue_cli/powershell/4103.yml +++ b/rules/deep_blue_cli/powershell/4103.yml @@ -6,12 +6,12 @@ logsource: product: windows detection: selection: - EventLog: PowerShell + Channel: PowerShell EventID: 4103 ContextInfo: - Host Application - ホスト アプリケーション - condition: selection + # condition: selection falsepositives: - unknown level: medium diff --git a/rules/deep_blue_cli/powershell/4104.yml b/rules/deep_blue_cli/powershell/4104.yml index 0b34b70f..d9d3a8b4 100644 --- a/rules/deep_blue_cli/powershell/4104.yml +++ b/rules/deep_blue_cli/powershell/4104.yml @@ -6,11 +6,11 @@ logsource: product: windows detection: selection: - EventLog: PowerShell + Channel: PowerShell EventID: 4104 - Path: '' - ScriptBlockText: '.' - condition: selection + Path: null + ScriptBlockText: null + # condition: selection falsepositives: - unknown level: medium diff --git a/rules/deep_blue_cli/security/1102.yml b/rules/deep_blue_cli/security/1102.yml new file mode 100644 index 00000000..b7281755 --- /dev/null +++ b/rules/deep_blue_cli/security/1102.yml @@ -0,0 +1,17 @@ +title: The Audit log file was cleared +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 1102 + # condition: selection +falsepositives: + - unknown +level: medium +output: 'Audit Log Clear¥n The Audit log was cleared.¥m%user_data.log_file_cleared%%user_data.subject_user_name%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4673.yml b/rules/deep_blue_cli/security/4673.yml new file mode 100644 index 00000000..35d813bc --- /dev/null +++ b/rules/deep_blue_cli/security/4673.yml @@ -0,0 +1,17 @@ +title: Sensitive Privilede Use (Mimikatz) +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4673 + # condition: selection | count(EventID) > 4 +falsepositives: + - unknown +level: medium +output: 'Sensitive Privilege Use Exceeds Threshold¥n Potentially indicative of Mimikatz, multiple sensitive priviledge calls have been made.¥nUserName:SubjectUserName% Domain Name:%DomainName%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4674.yml b/rules/deep_blue_cli/security/4674.yml new file mode 100644 index 00000000..eb169a0d --- /dev/null +++ b/rules/deep_blue_cli/security/4674.yml @@ -0,0 +1,19 @@ +title: An Operation was attempted on a privileged object +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4674 + ProcessName: '(?i)C:\WINDOWS\SYSTEM32\SERVICE.EXE' # (?i) means case insesitive for Rust Regex + AccessMask: '%%1539' + # condition: selection +falsepositives: + - unknown +level: medium +output: 'Possible Hidden Service Attempt¥nUser requested to modify the Dynamic Access Control (DAC) permissions of a service, possibly to hide it from view.¥nUser: %SubjectUserName%¥nTarget service:%ObjectName¥nDesired Access:WRITE_DAC' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4688.yml b/rules/deep_blue_cli/security/4688.yml new file mode 100644 index 00000000..7a4d6f3a --- /dev/null +++ b/rules/deep_blue_cli/security/4688.yml @@ -0,0 +1,18 @@ +title: Command Line Logging +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4688 + CommandLine: '.+' + # condition: selection +falsepositives: + - unknown +level: medium +output: 'CommandLine:%CommandLine% ParentProcessName:%ParentProcessName%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4720.yml b/rules/deep_blue_cli/security/4720.yml new file mode 100644 index 00000000..e35d5c05 --- /dev/null +++ b/rules/deep_blue_cli/security/4720.yml @@ -0,0 +1,17 @@ +title: A user account was created. +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4720 + # condition: selection +falsepositives: + - unknown +level: low +output: 'New User Created UserName:%TargetUserName% SID:%TargetSid%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4728.yml b/rules/deep_blue_cli/security/4728.yml new file mode 100644 index 00000000..47ed1866 --- /dev/null +++ b/rules/deep_blue_cli/security/4728.yml @@ -0,0 +1,18 @@ +title: A member was added to a security-enabled global group. +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4728 + TargetUserName: Administrators + # condition: selection +falsepositives: + - unknown +level: low +output: 'user added to global Administrators UserName: %MemberName% SID: %MemberSid%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4732.yml b/rules/deep_blue_cli/security/4732.yml new file mode 100644 index 00000000..05daa583 --- /dev/null +++ b/rules/deep_blue_cli/security/4732.yml @@ -0,0 +1,18 @@ +title: A member was added to a security-enabled local group. +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4732 + TargetUserName: Administrators + # condition: selection +falsepositives: + - unknown +level: low +output: 'user added to local Administrators UserName: %MemberName% SID: %MemberSid%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/4756.yml b/rules/deep_blue_cli/security/4756.yml new file mode 100644 index 00000000..c7af8718 --- /dev/null +++ b/rules/deep_blue_cli/security/4756.yml @@ -0,0 +1,18 @@ +title: A member was added to a security-enabled universal group. +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4756 + TargetUserName: Administrators + # condition: selection +falsepositives: + - unknown +level: low +output: 'user added to universal Administrators UserName: %MemberName% SID: %MemberSid%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/_4625.yml b/rules/deep_blue_cli/security/_4625.yml new file mode 100644 index 00000000..0fbabcd4 --- /dev/null +++ b/rules/deep_blue_cli/security/_4625.yml @@ -0,0 +1,17 @@ +title: An account failed to log on +description: hogehoge +enabled: false +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4625 + # condition: selection | count(TargetUserName) > 3 +falsepositives: + - unknown +level: medium +output: 'High number of logon failures for one account UserName:%event_data.SubjectUserName% Total logon faiures:%count%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/_4648.yml b/rules/deep_blue_cli/security/_4648.yml new file mode 100644 index 00000000..4b9735c6 --- /dev/null +++ b/rules/deep_blue_cli/security/_4648.yml @@ -0,0 +1,17 @@ +title: An account failed to log on +description: hogehoge +enabled: false +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4648 + # condition: selection | count(TargetUserName) > 3 +falsepositives: + - unknown +level: High +output: 'Distributed Account Explicit Credential Use (Password Spray Attack)¥n The use of multiple user account access attempts with explicit credentials is ¥nan indicator of a password spray attack.¥nTarget Usernames:%TargetUserName$¥nAccessing Username: %SubjectUserName%¥nAccessing Host Name: %SubjectDomainName%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/security/_4672.yml b/rules/deep_blue_cli/security/_4672.yml new file mode 100644 index 00000000..b1763b97 --- /dev/null +++ b/rules/deep_blue_cli/security/_4672.yml @@ -0,0 +1,19 @@ +title: Command Line Logging +description: hogehoge +enabled: false +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Security + EventID: 4672 + PrivilegeList: + contain: SeDebugPrivilege + # condition: selection +falsepositives: + - unknown +level: medium +output: 'CommandLine:%CommandLine% ParentProcessName:%ParentProcessName%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/sysmon/1.yml b/rules/deep_blue_cli/sysmon/1.yml new file mode 100644 index 00000000..3f2b36c6 --- /dev/null +++ b/rules/deep_blue_cli/sysmon/1.yml @@ -0,0 +1,19 @@ +title: Sysmon Check command lines +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Sysmon + EventID: 1 + CommandLine: '.+' + # condition: selection +falsepositives: + - unknown +level: medium +output: 'CommandLine=%CommandLine%¥nParentImage=%ParentImage%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 + diff --git a/rules/deep_blue_cli/sysmon/7.yml b/rules/deep_blue_cli/sysmon/7.yml new file mode 100644 index 00000000..4f321af4 --- /dev/null +++ b/rules/deep_blue_cli/sysmon/7.yml @@ -0,0 +1,18 @@ +title: Check for unsigned EXEs/DLLs +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: Sysmon + EventID: 7 + Signed: "false" # Compare by string + # condition: selection +falsepositives: + - unknown +level: low +output: 'Message: Unsigned Image(DLL)¥n Result : Loaded by: %event_data.Image%¥nCommand : %event_data.ImageLoaded%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/system/104.yml b/rules/deep_blue_cli/system/104.yml new file mode 100644 index 00000000..19301965 --- /dev/null +++ b/rules/deep_blue_cli/system/104.yml @@ -0,0 +1,17 @@ +title: The System log file was cleared +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: System + EventID: 104 + # condition: selection +falsepositives: + - unknown +level: medium +output: 'System Log Clear¥nThe System log was cleared.' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/system/7030.yml b/rules/deep_blue_cli/system/7030.yml new file mode 100644 index 00000000..5e168427 --- /dev/null +++ b/rules/deep_blue_cli/system/7030.yml @@ -0,0 +1,19 @@ +title: This service may not function properly +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: System + EventID: 7030 + param1: + regexes: ./regexes.txt + # condition: selection +falsepositives: + - unknown +level: low +output: 'Interactive service warning¥nService name: %ServiceName%¥nMalware (and some third party software) trigger this warning' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/system/7036.yml b/rules/deep_blue_cli/system/7036.yml new file mode 100644 index 00000000..f17ee1ad --- /dev/null +++ b/rules/deep_blue_cli/system/7036.yml @@ -0,0 +1,19 @@ +title: The ... service entered the stopped|running state +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: System + EventID: 7036 + param1: + regexes: ./regexes.txt + condition: selection +falsepositives: + - unknown +level: low +output: 'Suspicious Service Name¥nService name: %ServiceName%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/system/7040.yml b/rules/deep_blue_cli/system/7040.yml new file mode 100644 index 00000000..7ff699f2 --- /dev/null +++ b/rules/deep_blue_cli/system/7040.yml @@ -0,0 +1,21 @@ +title: The start type of the Windows Event Log service was changed from auto start to disabled +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: System + EventID: 7040 + param1: 'Windows Event Log' + param2: + - "disabled" + - "auto start" + condition: selection +falsepositives: + - unknown +level: low +output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/rules/deep_blue_cli/system/7045.yml b/rules/deep_blue_cli/system/7045.yml new file mode 100644 index 00000000..a733cf54 --- /dev/null +++ b/rules/deep_blue_cli/system/7045.yml @@ -0,0 +1,22 @@ +title: A service was installed in the system +description: hogehoge +enabled: true +author: Yea +logsource: + product: windows +detection: + selection: + Channel: System + EventID: 7045 + ServiceName: + regexes: ./regexes.txt + ImagePath: + min_length: 1000 + whitelist: ./whitelist.txt + condition: selection +falsepositives: + - unknown +level: low +output: 'New Service Created¥n%ImagePath¥nService name: %ServiceName%' +creation_date: 2020/11/8 +uodated_date: 2020/11/8 diff --git a/src/detections/application.rs b/src/detections/application.rs deleted file mode 100644 index 624c5997..00000000 --- a/src/detections/application.rs +++ /dev/null @@ -1,57 +0,0 @@ -extern crate regex; - -use crate::models::event; -use regex::Regex; -use std::collections::HashMap; - -pub struct Application {} - -impl Application { - pub fn new() -> Application { - Application {} - } - - pub fn detection( - &mut self, - event_id: String, - system: &event::System, - event_data: HashMap, - ) { - self.emet(&event_id, system); - } - - fn emet(&mut self, event_id: &String, system: &event::System) { - if event_id != "2" { - return; - } - - match &system.provider.name { - Some(name) => { - if name != "EMET" { - return; - } - } - None => return, - } - match &system.message { - Some(message) => { - let message_split: Vec<&str> = message.split("\n").collect(); - if !message_split.is_empty() && message_split.len() >= 5 { - let text = message_split[0]; - let application = message_split[3]; - let re = Regex::new(r"^Application: ").unwrap(); - let command = re.replace_all(application, ""); - let username = message_split[4]; - - println!("Message EMET Block"); - println!("Command : {}", command); - println!("Results : {}", text); - println!("Results : {}", username); - } - } - None => { - println!("Warning: EMET Message field is blank. Install EMET locally to see full details of this alert"); - } - } - } -} diff --git a/src/detections/applocker.rs b/src/detections/applocker.rs deleted file mode 100644 index 2862bc3c..00000000 --- a/src/detections/applocker.rs +++ /dev/null @@ -1,53 +0,0 @@ -extern crate regex; - -use crate::models::event; -use regex::Regex; -use std::collections::HashMap; - -pub struct AppLocker {} - -impl AppLocker { - pub fn new() -> AppLocker { - AppLocker {} - } - - pub fn detection( - &mut self, - event_id: String, - _system: &event::System, - _event_data: HashMap, - ) { - self.applocker_log_warning(&event_id, &_system); - self.applocker_log_block(&event_id, &_system); - } - - fn applocker_log_warning(&mut self, event_id: &String, system: &event::System) { - if event_id != "8003" { - return; - } - - let re = Regex::new(r" was .*$").unwrap(); - let default = "".to_string(); - let message = &system.message.as_ref().unwrap_or(&default); - let command = re.replace_all(&message, ""); - - println!("Message Applocker Warning"); - println!("Command : {}", command); - println!("Results : {}", message); - } - - fn applocker_log_block(&mut self, event_id: &String, system: &event::System) { - if event_id != "8004" { - return; - } - - let re = Regex::new(r" was .*$").unwrap(); - let default = "".to_string(); - let message = &system.message.as_ref().unwrap_or(&default); - let command = re.replace_all(&message, ""); - - println!("Message Applocker Block"); - println!("Command : {}", command); - println!("Results : {}", message); - } -} diff --git a/src/detections/configs.rs b/src/detections/configs.rs index a3d97eb1..d0a8ef83 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -1,11 +1,13 @@ use clap::{App, AppSettings, Arg, ArgMatches}; +use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; use std::sync::Once; -use std::collections::HashMap; #[derive(Clone)] pub struct SingletonReader { + pub regex: Vec>, + pub whitelist: Vec>, pub args: ArgMatches<'static>, pub event_key_alias_config: EventKeyAliasConfig, } @@ -17,6 +19,8 @@ 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(), }; @@ -57,35 +61,42 @@ fn build_app() -> clap::App<'static, 'static> { #[derive(Clone)] pub struct EventKeyAliasConfig { - key_to_eventkey: HashMap, + key_to_eventkey: HashMap, } impl EventKeyAliasConfig { pub fn new() -> EventKeyAliasConfig { - return EventKeyAliasConfig{ key_to_eventkey: HashMap::new() }; + return EventKeyAliasConfig { + key_to_eventkey: HashMap::new(), + }; } - pub fn get_event_key(&self, alias: String ) -> Option<&String> { + pub fn get_event_key(&self, alias: String) -> Option<&String> { return self.key_to_eventkey.get(&alias); } } fn load_eventkey_alias() -> EventKeyAliasConfig { - let config = EventKeyAliasConfig::new(); + let mut config = EventKeyAliasConfig::new(); - read_csv("config/eventkey_alias.txt").into_iter().for_each( | line| { - if line.len() != 2 { - return; - } + read_csv("config/eventkey_alias.txt") + .into_iter() + .for_each(|line| { + if line.len() != 2 { + return; + } - let alias = line[0]; - let event_key = line[1]; - if alias.len() == 0 || event_key.len() == 0 { - return; - } + let empty = &"".to_string(); + let alias = line.get(0).unwrap_or(empty); + let event_key = line.get(1).unwrap_or(empty); + if alias.len() == 0 || event_key.len() == 0 { + return; + } - config.key_to_eventkey.insert(alias, event_key); - }); + config + .key_to_eventkey + .insert(alias.to_owned(), event_key.to_owned()); + }); return config; } diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 45d7612a..b3c6c3bd 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1,9 +1,9 @@ -extern crate csv; extern crate chrono; +extern crate csv; +use crate::detections::print::Message; use crate::detections::rule; use crate::detections::rule::RuleNode; -use crate::detections::print::{Message}; use crate::yaml::ParseYaml; use chrono::{TimeZone, Utc}; @@ -13,18 +13,15 @@ use serde_json::{Error, Value}; const DIRPATH_RULES: &str = "rules"; #[derive(Debug)] -pub struct Detection { - -} +pub struct Detection {} impl Detection { pub fn new() -> Detection { - Detection { - } + Detection {} } pub fn start(&mut self, mut parser: EvtxParser) { - // from .etvx to json + // serialize from .etvx to jsons let event_records: Vec = parser .records_json() .filter_map(|result_record| { @@ -44,10 +41,6 @@ impl Detection { }) .collect(); - event_records.iter().for_each(|event_rec| { - println!("{}", event_rec["Event"]); - }); - // load rule files let mut rulefile_loader = ParseYaml::new(); let resutl_readdir = rulefile_loader.read_dir(DIRPATH_RULES); @@ -57,19 +50,39 @@ impl Detection { } // parse rule files - let rules: Vec = rulefile_loader + let selection_rules: Vec = rulefile_loader .files .into_iter() .map(|rule_file| rule::parse_rule(rule_file)) + .filter_map(|mut rule| { + return rule + .init() + .or_else(|err_msgs| { + print!( + "Failed to parse Rule file. See following detail. [rule file title:{}]", + rule.yaml["title"].as_str().unwrap_or("") + ); + err_msgs.iter().for_each(|err_msg| println!("{}", err_msg)); + println!("\n"); + return Result::Err(err_msgs); + }) + .and_then(|_empty| Result::Ok(rule)) + .ok(); + }) .collect(); - // selection rule files and collect log + // selection rule files and collect message let mut message = Message::new(); - rules.iter().for_each(|rule| { + selection_rules.iter().for_each(|rule| { &event_records .iter() - .filter(|event_record| rule.detection.select(event_record)) - .for_each(|event_record| message.insert(Utc.ymd(1996, 2, 27).and_hms(1, 5, 1), event_record.to_string())); + .filter(|event_record| rule.select(event_record)) + .for_each(|event_record| { + message.insert( + Utc.ymd(1996, 2, 27).and_hms(1, 5, 1), + event_record.to_string(), + ) + }); }); // output message diff --git a/src/detections/mod.rs b/src/detections/mod.rs index c11cdb13..2c864e71 100644 --- a/src/detections/mod.rs +++ b/src/detections/mod.rs @@ -1,12 +1,6 @@ -mod application; -mod applocker; mod common; pub mod configs; pub mod detection; -mod powershell; pub mod print; mod rule; -mod security; -mod sysmon; -mod system; mod utils; diff --git a/src/detections/powershell.rs b/src/detections/powershell.rs deleted file mode 100644 index 4fa956b0..00000000 --- a/src/detections/powershell.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::detections::utils; -use crate::models::event; -use regex::Regex; -use std::collections::HashMap; - -pub struct PowerShell {} - -impl PowerShell { - pub fn new() -> PowerShell { - PowerShell {} - } - - pub fn detection( - &mut self, - event_id: String, - _system: &event::System, - event_data: HashMap, - ) { - self.execute_pipeline(&event_id, &event_data); - self.execute_remote_command(&event_id, &event_data); - } - - fn execute_pipeline(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "4103" { - return; - } - - let default = String::from(""); - let commandline = event_data.get("ContextInfo").unwrap_or(&default); - - if commandline.contains("Host Application") - || commandline.contains("ホスト アプリケーション") - { - let rm_before = - Regex::new("(?ms)^.*(ホスト アプリケーション|Host Application) = ").unwrap(); - let rm_after = Regex::new("(?ms)\n.*$").unwrap(); - - let temp_command_with_extra = rm_before.replace_all(commandline, ""); - let command = rm_after.replace_all(&temp_command_with_extra, ""); - - if command != "" { - utils::check_command(4103, &command, 1000, 0, &default, &default); - } - } - } - - fn execute_remote_command(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "4104" { - return; - } - - let default = String::from(""); - let path = event_data.get("Path").unwrap().to_string(); - if path == "".to_string() { - let commandline = event_data.get("ScriptBlockText").unwrap_or(&default); - if commandline.to_string() != default { - utils::check_command(4104, &commandline, 1000, 0, &default, &default); - } - } - } -} diff --git a/src/detections/rule.rs b/src/detections/rule.rs index 8d263ff3..2623dff3 100644 --- a/src/detections/rule.rs +++ b/src/detections/rule.rs @@ -1,24 +1,73 @@ +extern crate regex; + +use crate::detections::configs; +use regex::Regex; use serde_json::Value; use yaml_rust::Yaml; -use crate::detections::configs; pub fn parse_rule(yaml: Yaml) -> RuleNode { - let selection = parse_selection(&yaml); + let detection = parse_detection(&yaml); + return RuleNode { yaml: yaml, - detection: DetectionNode { - selection: selection, - }, + detection: detection, }; } +fn parse_detection(yaml: &Yaml) -> Option { + if yaml["detection"].is_badvalue() { + return Option::None; + } else { + let node = DetectionNode { + selection: parse_selection(&yaml), + }; + return Option::Some(node); + } +} + +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() + .fold("detection -> selection".to_string(), |mut acc, cur| { + acc = acc + " -> " + cur; + return acc; + }); +} + fn parse_selection(yaml: &Yaml) -> Option> { + // TODO detection-selectionが存在しない場合のチェック let selection_yaml = &yaml["detection"]["selection"]; + if selection_yaml.is_badvalue() { + return Option::None; + } return Option::Some(parse_selection_recursively(vec![], &selection_yaml)); } -fn parse_selection_recursively(mut key_list: Vec, yaml: &Yaml) -> Box { +fn parse_selection_recursively(key_list: Vec, yaml: &Yaml) -> Box { if yaml.as_hash().is_some() { + // 連想配列はAND条件と解釈する let yaml_hash = yaml.as_hash().unwrap(); let mut and_node = AndSelectionNode::new(); @@ -31,6 +80,7 @@ fn parse_selection_recursively(mut key_list: Vec, yaml: &Yaml) -> Box, yaml: &Yaml) -> Box, } -//////////////// Detection Node -pub struct DetectionNode { - selection: Option>, -} +impl RuleNode { + pub fn init(&mut self) -> Result<(), Vec> { + if self.detection.is_none() { + return Result::Ok(()); + } + + return self.detection.as_mut().unwrap().init(); + } -impl DetectionNode { pub fn select(&self, event_record: &Value) -> bool { - if self.selection.is_none() { + let selection = self + .detection + .as_ref() + .and_then(|detect_node| detect_node.selection.as_ref()); + if selection.is_none() { return false; } - return self.selection.as_ref().unwrap().select(event_record); + return selection.unwrap().select(event_record); } } -//////////// Selection Node -trait SelectionNode { - fn select(&self, event_record: &Value) -> bool; +// Ruleファイルのdetectionを表すノード +struct DetectionNode { + pub selection: Option>, } -///////////////// AndSelectionNode +impl DetectionNode { + fn init(&mut self) -> Result<(), Vec> { + if self.selection.is_none() { + return Result::Ok(()); + } + + return self.selection.as_mut().unwrap().init(); + } +} + +// Ruleファイルの detection- selection配下のノードはこのtraitを実装する。 +trait SelectionNode { + fn select(&self, event_record: &Value) -> bool; + fn init(&mut self) -> Result<(), Vec>; +} + +// detection - selection配下でAND条件を表すノード struct AndSelectionNode { pub child_nodes: Vec>, } @@ -88,9 +162,36 @@ impl SelectionNode for AndSelectionNode { return child_node.as_ref().select(event_record); }); } + + fn init(&mut self) -> Result<(), Vec> { + let err_msgs = self + .child_nodes + .iter_mut() + .map(|child_node| { + let res = child_node.init(); + if res.is_err() { + return res.unwrap_err(); + } else { + return vec![]; + } + }) + .fold( + vec![], + |mut acc: Vec, cur: Vec| -> Vec { + acc.extend(cur.into_iter()); + return acc; + }, + ); + + if err_msgs.is_empty() { + return Result::Ok(()); + } else { + return Result::Err(err_msgs); + } + } } -////////// OrSelectionNode +// detection - selection配下でOr条件を表すノード struct OrSelectionNode { pub child_nodes: Vec>, } @@ -109,96 +210,198 @@ impl SelectionNode for OrSelectionNode { return child_node.as_ref().select(event_record); }); } + + fn init(&mut self) -> Result<(), Vec> { + let err_msgs = self + .child_nodes + .iter_mut() + .map(|child_node| { + let res = child_node.init(); + if res.is_err() { + return res.unwrap_err(); + } else { + return vec![]; + } + }) + .fold( + vec![], + |mut acc: Vec, cur: Vec| -> Vec { + acc.extend(cur.into_iter()); + return acc; + }, + ); + + if err_msgs.is_empty() { + return Result::Ok(()); + } else { + return Result::Err(err_msgs); + } + } } -////////////// Field Selection Node -struct FieldSelectionNode { +// detection - selection配下の末端ノード +struct LeafSelectionNode { key_list: Vec, select_value: Yaml, + matcher: Option>, } -impl FieldSelectionNode { - fn new(key_list: Vec, value_yaml: Yaml) -> FieldSelectionNode { - return FieldSelectionNode { +impl LeafSelectionNode { + fn new(key_list: Vec, value_yaml: Yaml) -> LeafSelectionNode { + return LeafSelectionNode { key_list: key_list, select_value: value_yaml, + matcher: Option::None, }; } // JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。 - // TODO Messageを出力する際も利用するので、共通して使えるようにrefactoringする。 fn get_event_value<'a>(&self, event_value: &'a Value) -> Option<&'a Value> { if self.key_list.is_empty() { return Option::None; } - let key: &str = &self.key_list[0]; - if key.len() == 0 { - return Option::None; - } - - let event_key = match configs::singleton().event_key_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); + return get_event_value(&self.key_list[0].to_string(), event_value); } - // TODO Matcherのインスタンスが都度生成されないようにする。 - fn get_matchers(&self) -> Vec> { - return vec![Box::new(ValueMatcher {})]; + // LeafMatcherの一覧を取得する。 + fn get_matchers(&self) -> Vec> { + return vec![Box::new(RegexMatcher::new())]; + } + + // LeafMatcherを取得する。 + fn get_matcher(&self) -> Option> { + let matchers = self.get_matchers(); + let mut match_key_list = self.key_list.clone(); + match_key_list.remove(0); + return matchers + .into_iter() + .find(|matcher| matcher.is_target_key(&match_key_list)); } } -impl SelectionNode for FieldSelectionNode { +impl SelectionNode for LeafSelectionNode { fn select(&self, event_record: &Value) -> bool { - let matchers = self.get_matchers(); - let matcher = matchers - .into_iter() - .find(|matcher| matcher.is_target_key(&self.key_list)); - if matcher.is_none() { + if self.matcher.is_none() { return false; } let event_value = self.get_event_value(event_record); - return matcher + return self.matcher.as_ref().unwrap().is_match(event_value); + } + + fn init(&mut self) -> Result<(), Vec> { + let matchers = self.get_matchers(); + let mut match_key_list = self.key_list.clone(); + match_key_list.remove(0); + self.matcher = matchers + .into_iter() + .find(|matcher| matcher.is_target_key(&match_key_list)); + // 一致するmatcherが見つからないエラー + if self.matcher.is_none() { + return Result::Err(vec![format!( + "Found unknown key. key:{}", + concat_selection_key(&match_key_list) + )]); + } + + return self + .matcher + .as_mut() .unwrap() - .is_match(&self.key_list, &self.select_value, event_value); + .init(&match_key_list, &self.select_value); } } -trait FieldSelectionMatcher { +// 末端ノードがEventLogの値を比較するロジックを表す。 +// 正規条件のマッチや文字数制限など、比較ロジック毎にこのtraitを実装したクラスが存在する。 +// +// 新規にLeafMatcherを実装するクラスを作成した場合、 +// LeafSelectionNodeのget_matchersクラスの戻り値の配列に新規作成したクラスのインスタンスを追加する。 +trait LeafMatcher { fn is_target_key(&self, key_list: &Vec) -> bool; - fn is_match( - &self, - key_list: &Vec, - select_value: &Yaml, - event_value: Option<&Value>, - ) -> bool; + + fn is_match(&self, event_value: Option<&Value>) -> bool; + + fn init(&mut self, key_list: &Vec, select_value: &Yaml) -> Result<(), Vec>; } -struct ValueMatcher {} +// 正規表現で比較するロジックを表すクラス +struct RegexMatcher { + re: Option, +} -impl FieldSelectionMatcher for ValueMatcher { +impl RegexMatcher { + fn new() -> RegexMatcher { + return RegexMatcher { + re: Option::None, // empty + }; + } + fn is_regex_fullmatch(&self, re: &Regex, value: String) -> bool { + return re.find_iter(&value).any(|match_obj| { + return match_obj.as_str().to_string() == value; + }); + } +} + +impl LeafMatcher for RegexMatcher { fn is_target_key(&self, key_list: &Vec) -> bool { return key_list.is_empty(); } - fn is_match( - &self, - key_list: &Vec, - select_value: &Yaml, - event_value: Option<&Value>, - ) -> bool { - return true; + fn init(&mut self, key_list: &Vec, select_value: &Yaml) -> Result<(), Vec> { + if select_value.is_null() { + self.re = Option::None; + return Result::Ok(()); + } + + // stringで比較する。 + let yaml_value = match select_value { + Yaml::Boolean(b) => Option::Some(b.to_string()), + Yaml::Integer(i) => Option::Some(i.to_string()), + Yaml::Real(r) => Option::Some(r.to_string()), + Yaml::String(s) => Option::Some(s.to_owned()), + _ => Option::None, + }; + // ここには来ないはず + if yaml_value.is_none() { + let errmsg = format!( + "unknown error occured. [key:{}]", + concat_selection_key(key_list) + ); + return Result::Err(vec![errmsg]); + } + + // 指定された正規表現が間違っていて、パースに失敗した場合 + let yaml_str = yaml_value.unwrap(); + let re_result = Regex::new(&yaml_str); + if re_result.is_err() { + let errmsg = format!( + "cannot parse regex. [regex:{}, key:{}]", + yaml_str, + concat_selection_key(key_list) + ); + return Result::Err(vec![errmsg]); + } + + return Result::Ok(()); } -} \ No newline at end of file + + fn is_match(&self, event_value: Option<&Value>) -> bool { + let is_event_value_null = event_value.is_none() + || event_value.unwrap().is_null() + || event_value.unwrap().as_str().unwrap_or(" ").len() == 0; + + // yamlにnullが設定されていた場合 + if self.re.is_none() { + return is_event_value_null; + } + + return match event_value.unwrap_or(&Value::Null) { + Value::Bool(b) => self.is_regex_fullmatch(self.re.as_ref().unwrap(), b.to_string()), + Value::String(s) => self.is_regex_fullmatch(self.re.as_ref().unwrap(), s.to_owned()), + Value::Number(n) => self.is_regex_fullmatch(self.re.as_ref().unwrap(), n.to_string()), + _ => false, + }; + } +} diff --git a/src/detections/security.rs b/src/detections/security.rs deleted file mode 100644 index 44ce23ae..00000000 --- a/src/detections/security.rs +++ /dev/null @@ -1,1348 +0,0 @@ -use crate::detections::utils; -use crate::models::event; -use std::collections::HashMap; - -#[derive(Debug)] -pub struct Security { - max_total_sensitive_privuse: i32, - max_passspray_login: i32, - max_passspray_uniquser: i32, - max_failed_logons: i32, - alert_all_admin: i32, - total_admin_logons: i32, - total_failed_logons: i32, - total_failed_account: i32, - total_sensitive_privuse: i32, - admin_logons: HashMap>, - multiple_admin_logons: HashMap, - account_2_failedcnt: HashMap, - passspray_2_user: HashMap, - empty_str: String, -} - -impl Security { - pub fn new() -> Security { - Security { - max_total_sensitive_privuse: 4, - max_passspray_login: 6, - max_passspray_uniquser: 6, - max_failed_logons: 5, - alert_all_admin: 0, - total_admin_logons: 0, - total_failed_logons: 0, - total_failed_account: 0, - total_sensitive_privuse: 0, - admin_logons: HashMap::new(), - multiple_admin_logons: HashMap::new(), - account_2_failedcnt: HashMap::new(), - passspray_2_user: HashMap::new(), - empty_str: String::default(), - } - } - - pub fn disp(&self) { - self.disp_admin_logons().and_then(Security::print_console); - self.disp_login_failed().and_then(Security::print_console); - } - - fn disp_admin_logons(&self) -> Option> { - if self.total_admin_logons < 1 { - return Option::None; - } - - let mut msges: Vec = Vec::new(); - msges.push(format!("total_admin_logons:{}", self.total_admin_logons)); - msges.push(format!("admin_logons:{:?}", self.admin_logons)); - msges.push(format!( - "multiple_admin_logons:{:?}", - self.multiple_admin_logons - )); - - return Option::Some(msges); - } - - fn disp_login_failed(&self) -> Option> { - let exceed_failed_logons = self.total_failed_logons <= self.max_failed_logons; - - let exist_failed_account = self.account_2_failedcnt.keys().count() as i32 <= 1; - if exceed_failed_logons || exist_failed_account { - return Option::None; - } - - let mut msges: Vec = Vec::new(); - msges.push(format!( - "High number of total logon failures for multiple accounts" - )); - msges.push(format!( - "Total accounts: {}", - self.account_2_failedcnt.keys().count() - )); - msges.push(format!( - "Total logon failures: {}", - self.total_failed_logons - )); - - return Option::Some(msges); - } - - pub fn detection( - &mut self, - event_id: String, - _system: &event::System, - user_data: &Option, - event_data: HashMap, - ) { - self.process_created(&event_id, &event_data); - self.se_debug_privilege(&event_id, &event_data); - self.account_created(&event_id, &event_data) - .and_then(Security::print_console); - self.add_member_security_group(&event_id, &event_data) - .and_then(Security::print_console); - self.failed_logon(&event_id, &event_data); - self.sensitive_priviledge(&event_id, &event_data) - .and_then(Security::print_console); - self.attempt_priviledge(&event_id, &event_data) - .and_then(Security::print_console); - self.pass_spray(&event_id, &event_data) - .and_then(Security::print_console); - self.audit_log_cleared(&event_id, &user_data) - .and_then(Security::print_console); - } - - fn print_console(v: Vec) -> Option> { - v.iter().for_each(|s| println!("{}", s)); - println!("\n"); - return Option::Some(v); - } - - fn process_created(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "4688" { - return; - } - - let commandline = event_data.get("CommandLine").unwrap_or(&self.empty_str); - let creator = event_data - .get("ParentProcessName") - .unwrap_or(&self.empty_str); - utils::check_command(4688, &commandline, 1000, 0, &self.empty_str, &creator); - } - - // - // Special privileges assigned to new logon (possible admin access) - // - fn se_debug_privilege(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "4672" { - return; - } - - if let Some(privileage_list) = event_data.get("PrivilegeList") { - if let Some(_data) = privileage_list.find("SeDebugPrivilege") { - // alert_all_adminが有効であれば、標準出力して知らせる - // DeepBlueCLIでは必ず0になっていて、基本的には表示されない。 - if self.alert_all_admin == 1 { - println!("Logon with SeDebugPrivilege (admin access)"); - println!("Username:{}", event_data["SubjectUserName"]); - println!("Domain:{}", event_data["SubjectDomainName"]); - println!("User SID:{}", event_data["SubjectUserSid"]); - println!("Domain:{}", event_data["PrivilegeList"]); - } - - self.total_admin_logons += 1; - - // admin_logons配列にusernameが含まれているか確認 - match self.admin_logons.get(&event_data["SubjectUserName"]) { - Some(sid) => { - // 含まれていれば、マルチユーザが管理者としてログインしているか確認 - // マルチログオンのデータをセット - if event_data["SubjectUserName"] != event_data["SubjectUserSid"] { - // One username with multiple admin logon SIDs - self.multiple_admin_logons - .insert(event_data["SubjectUserName"].to_string(), 1); - - let mut count_hash: HashMap = HashMap::new(); - count_hash.insert( - event_data["SubjectUserSid"].to_string(), - sid[&event_data["SubjectUserSid"]] + 1, - ); - self.admin_logons - .insert(event_data["SubjectUserName"].to_string(), count_hash); - } - } - None => { - // admin_logons配列にセットUserNameとSIDとカウンタをセット - let mut count_hash: HashMap = HashMap::new(); - count_hash.insert(event_data["SubjectUserSid"].to_string(), 1); - self.admin_logons - .insert(event_data["SubjectUserName"].to_string(), count_hash); - } - } - } - } - } - - // account craeted:OK - fn account_created( - &mut self, - event_id: &String, - event_data: &HashMap, - ) -> Option> { - if event_id != "4720" { - return Option::None; - } - - let mut msges: Vec = Vec::new(); - msges.push("New User Created".to_string()); - - let username = event_data.get("TargetUserName").unwrap_or(&self.empty_str); - msges.push(format!("Username: {}", username)); - let sid = event_data.get("TargetSid").unwrap_or(&self.empty_str); - msges.push(format!("User SID: {}", sid)); - - return Option::Some(msges); - } - - // add member to security group - fn add_member_security_group( - &mut self, - event_id: &String, - event_data: &HashMap, - ) -> Option> { - // check if group is Administrator, may later expand to all groups - if event_data.get("TargetUserName").unwrap_or(&self.empty_str) != "Administrators" { - return Option::None; - } - - // A member was added to a security-enabled (global|local|universal) group. - let mut msges: Vec = Vec::new(); - if event_id == "4728" { - msges.push("User added to global Administrators group".to_string()); - } else if event_id == "4732" { - msges.push("User added to local Administrators group".to_string()); - } else if event_id == "4756" { - msges.push("User added to universal Administrators group".to_string()); - } else { - return Option::None; - } - - let username = event_data.get("MemberName").unwrap_or(&self.empty_str); - msges.push(format!("Username: {}", username)); - let sid = event_data.get("MemberSid").unwrap_or(&self.empty_str); - msges.push(format!("User SID: {}", sid)); - - return Option::Some(msges); - } - - // An account failed to log on.:OK - // Requires auditing logon failures - // https://technet.microsoft.com/en-us/library/cc976395.aspx - fn failed_logon(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "4625" { - return; - } - - // see fn disp() - self.total_failed_logons += 1; - let username = event_data.get("TargetUserName").unwrap_or(&self.empty_str); - let failed_cnt = self.account_2_failedcnt.get(username).unwrap_or(&0) + 1; - self.account_2_failedcnt - .insert(username.to_string(), failed_cnt); - } - - // Sensitive Privilege Use (Mimikatz) - fn sensitive_priviledge( - &mut self, - event_id: &String, - event_data: &HashMap, - ) -> Option> { - if event_id != "4673" { - return Option::None; - } - - self.total_sensitive_privuse += 1; - let mut msges: Vec = Vec::new(); - // use == operator here to avoid multiple log notices - if self.max_total_sensitive_privuse != self.total_sensitive_privuse { - return Option::None; - } - - msges.push("Sensititive Privilege Use Exceeds Threshold".to_string()); - msges.push( - "Potentially indicative of Mimikatz, multiple sensitive privilege calls have been made" - .to_string(), - ); - - let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); - msges.push(format!("Username: {}", username)); - - let domainname = event_data - .get("SubjectDomainName") - .unwrap_or(&self.empty_str); - msges.push(format!("Domain Name: {}", domainname)); - - return Option::Some(msges); - } - - fn attempt_priviledge( - &mut self, - event_id: &String, - event_data: &HashMap, - ) -> Option> { - if event_id != "4674" { - return Option::None; - } - - // "%%1539" means WRITE_DAC(see detail: https://docs.microsoft.com/ja-jp/windows/security/threat-protection/auditing/event-4663) - let servicename = event_data - .get("ProcessName") - .unwrap_or(&self.empty_str) - .to_uppercase(); - let accessname = event_data.get("AccessMask").unwrap_or(&self.empty_str); - if servicename != r"C:\WINDOWS\SYSTEM32\SERVICES.EXE" || accessname != "%%1539" { - return Option::None; - } - - let mut msges: Vec = Vec::new(); - msges.push("Possible Hidden Service Attempt".to_string()); - msges.push("User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view".to_string()); - - let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); - msges.push(format!("User: {}", username)); - - let servicename = event_data.get("ObjectName").unwrap_or(&self.empty_str); - msges.push(format!("Target service: {}", servicename)); - - msges.push("WRITE_DAC".to_string()); - - return Option::Some(msges); - } - - // A logon was attempted using explicit credentials. - fn pass_spray( - &mut self, - event_id: &String, - event_data: &HashMap, - ) -> Option> { - if event_id != "4648" { - return Option::None; - } - - let targetusername = event_data.get("TargetUserName").unwrap_or(&self.empty_str); - let spray_cnt = self.passspray_2_user.get(targetusername).unwrap_or(&0) + 1; - self.passspray_2_user - .insert(targetusername.to_string(), spray_cnt); - - // check exceeded targetuser count. - let spray_uniq_user = self - .passspray_2_user - .values() - .filter(|value| value > &&self.max_passspray_login) - .count() as i32; - if spray_uniq_user <= self.max_passspray_uniquser { - return Option::None; - } - - // let v_username = Vec::new(); - let mut v_username = Vec::new(); - self.passspray_2_user - .keys() - .for_each(|u| v_username.push(u)); - v_username.sort(); - let usernames: String = v_username.iter().fold( - self.empty_str.to_string(), - |mut acc: String, cur| -> String { - acc.push_str(cur); - acc.push_str(" "); - return acc; - }, - ); - - let mut msges: Vec = Vec::new(); - msges.push( - "Distributed Account Explicit Credential Use (Password Spray Attack)".to_string(), - ); - msges.push( - "The use of multiple user account access attempts with explicit credentials is " - .to_string(), - ); - msges.push("an indicator of a password spray attack".to_string()); - - msges.push(format!("Target Usernames: {}", usernames.trim())); - let access_username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); - - msges.push(format!("Accessing Username: {}", access_username)); - - let access_hostname = event_data - .get("SubjectDomainName") - .unwrap_or(&self.empty_str); - msges.push(format!("Accessing Host Name: {}", access_hostname)); - - // reset - self.passspray_2_user = HashMap::new(); - - return Option::Some(msges); - } - - fn audit_log_cleared( - &mut self, - event_id: &String, - user_data: &Option, - ) -> Option> { - if event_id != "1102" { - return Option::None; - } - - let mut msges: Vec = Vec::new(); - msges.push("Audit Log Clear".to_string()); - msges.push("The Audit log was cleared".to_string()); - let username = user_data - .as_ref() - .and_then(|u| u.log_file_cleared.as_ref()) - .and_then(|l| l.subject_user_name.as_ref()); - msges.push(format!( - "Security ID: {}", - username.unwrap_or(&self.empty_str) - )); - - return Option::Some(msges); - } -} - -#[cfg(test)] -mod tests { - extern crate quick_xml; - - use crate::detections::security; - use crate::models::event; - - // 正しくヒットするパターン - #[test] - fn test_account_created_hit() { - let xml_str = get_account_created_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.account_created( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - let v = option_v.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"New User Created".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: IEUser".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(Option::None, ite.next()); - } - - // event idが異なるパターン - #[test] - fn test_account_created_noteq_eventid() { - let xml_str = - get_account_created_xml().replace("4720", "4721"); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.account_created( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - assert_eq!(Option::None, option_v); - } - - // 実在するかどうか不明だが、EventDataの必要なフィールドがないパターン - #[test] - fn test_account_created_none_check() { - let xml_str = r#" - - - - - 4720 - 0 - 0 - 13824 - 0 - 0x8020000000000000 - - 112 - - - Security - IE8Win7 - - - - "#; - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.account_created( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - let v = option_v.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"New User Created".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: ".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(&"User SID: ", ite.next().unwrap_or(&"".to_string())); - assert_eq!(Option::None, ite.next()); - } - - fn get_account_created_xml() -> String { - return r#" - - - - - 4720 - 0 - 0 - 13824 - 0 - 0x8020000000000000 - - 112 - - - Security - IE8Win7 - - - - IEUser - IE8Win7 - S-1-5-21-3463664321-2923530833-3546627382-1000 - S-1-5-18 - WIN-QALA5Q3KJ43$ - WORKGROUP - 0x3e7 - - - IEUserSam - %%1793 - - - %%1793 - %%1793 - %%1793 - %%1793 - %%1793 - %%1794 - %%1794 - 513 - - - 0x0 - 0x15 - - %%2080 - %%2082 - %%2084 - %%1793 - - - %%1797 - - "#.to_string(); - } - - // 正しくヒットするパターン(eventid=4732) - #[test] - fn test_add_member_security_group_hit_4732() { - let xml_str = get_add_member_security_group_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.add_member_security_group( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - let v = option_v.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"User added to local Administrators group".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: testnamess".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(Option::None, ite.next()); - } - - // 正しくヒットするパターン(eventid=4728は一行目が変わる) - #[test] - fn test_add_member_security_group_hit_4728() { - let xml_str = get_add_member_security_group_xml() - .replace(r"4732", r"4728"); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.add_member_security_group( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - let v = option_v.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"User added to global Administrators group".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: testnamess".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(Option::None, ite.next()); - } - - // 正しくヒットするパターン(eventid=4756は一行目が変わる) - #[test] - fn test_add_member_security_group_hit_4756() { - let xml_str = get_add_member_security_group_xml() - .replace(r"4732", r"4756"); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.add_member_security_group( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - let v = option_v.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"User added to universal Administrators group".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: testnamess".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(Option::None, ite.next()); - } - - // eventidが異なりヒットしないパターン - #[test] - fn test_add_member_security_group_noteq_eventid() { - let xml_str = get_add_member_security_group_xml() - .replace(r"4732", r"4757"); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.add_member_security_group( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - assert_eq!(Option::None, option_v); - } - - // グループがAdministratorsじゃなくてHitしないパターン - #[test] - fn test_add_member_security_not_administrators() { - let xml_str = get_add_member_security_group_xml().replace( - r"Administrators", - r"local", - ); - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.add_member_security_group( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - assert_eq!(Option::None, option_v); - } - - // hitするけど表示するフィールドがない場合 - #[test] - fn test_add_member_security_group_none() { - let xml_str = r#" - - - - - 4732 - 0 - 0 - 13826 - 0 - 0x8020000000000000 - - 116 - - - Security - IE8Win7 - - - - Administrators - - "#; - let event: event::Evtx = quick_xml::de::from_str(&xml_str) - .map_err(|e| { - println!("{}", e.to_string()); - }) - .unwrap(); - - let mut sec = security::Security::new(); - let option_v = sec.add_member_security_group( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - let v = option_v.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"User added to local Administrators group".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: ".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(&"User SID: ", ite.next().unwrap_or(&"".to_string())); - assert_eq!(Option::None, ite.next()); - } - - fn get_add_member_security_group_xml() -> String { - return r#" - - - - - 4732 - 0 - 0 - 13826 - 0 - 0x8020000000000000 - - 116 - - - Security - IE8Win7 - - - - testnamess - S-1-5-21-3463664321-2923530833-3546627382-1000 - Administrators - Builtin - S-1-5-32-544 - S-1-5-18 - WIN-QALA5Q3KJ43$ - WORKGROUP - 0x3e7 - - - - "#.to_string(); - } - - // ユーザー数が一つなら、ログ数が幾らあっても、メッセージは表示されないはず。 - #[test] - fn test_failed_logon_nothit_onlyoneuser() { - let xml_str = get_failed_logon_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - - let mut sec = security::Security::new(); - - sec.max_failed_logons = 5; - let ite = [1, 2, 3, 4, 5, 6, 7].iter(); - ite.for_each(|i| { - sec.failed_logon( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - assert_eq!(i, &sec.total_failed_logons); - assert_eq!(Option::None, sec.disp_login_failed()); - }); - } - - // 失敗回数を増やしていき、境界値でメッセージが表示されることのテスト。 - #[test] - fn test_failed_logon_hit() { - let xml_str = get_failed_logon_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - let event_another: event::Evtx = quick_xml::de::from_str(&xml_str.replace( - r"Administrator", - r"localuser", - )) - .unwrap(); - - let mut sec = security::Security::new(); - sec.max_failed_logons = 5; - - // メッセージが表示されるには2ユーザー以上失敗している必要がある。まず一人目 - sec.failed_logon( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - assert_eq!(1, sec.total_failed_logons); - - let ite = [1, 2, 3, 4, 5, 6, 7].iter(); - ite.for_each(|i| { - sec.failed_logon( - &event_another.system.event_id.to_string(), - &event_another.parse_event_data(), - ); - let fail_cnt = i + 1; - assert_eq!(fail_cnt, sec.total_failed_logons); - if fail_cnt > 5 { - let v = sec.disp_login_failed().unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"High number of total logon failures for multiple accounts".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Total accounts: 2".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &format!("Total logon failures: {}", fail_cnt), - ite.next().unwrap_or(&"".to_string()) - ); - // assert_eq!(Option::None, ite.next()); - } else { - assert_eq!(Option::None, sec.disp_login_failed()); - } - }); - - // hitするけど表示するフィールドがない場合 - let xml_nofield = r#" - - - 4625001254400x80100000000000006016SecurityDESKTOP-M5SN04R - - "#; - - // エラーにならなければOK - let event_nofield: event::Evtx = quick_xml::de::from_str(xml_nofield).unwrap(); - sec.failed_logon( - &event_nofield.system.event_id.to_string(), - &event_nofield.parse_event_data(), - ); - } - - // 失敗回数を増やしていき、境界値でメッセージが表示されることのテスト。 - #[test] - fn test_failed_logon_noteq_eventid() { - let xml_str = get_failed_logon_xml(); - let event: event::Evtx = quick_xml::de::from_str( - &xml_str.replace(r"4625", r"4626"), - ) - .unwrap(); - let event_another: event::Evtx = quick_xml::de::from_str( - &xml_str - .replace(r"4625", r"4626") - .replace( - r"Administrator", - r"localuser", - ), - ) - .unwrap(); - - let mut sec = security::Security::new(); - sec.max_failed_logons = 5; - - // メッセージが表示されるには2ユーザー以上失敗している必要がある。まず一人目 - sec.failed_logon( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - assert_eq!(0, sec.total_failed_logons); - - let ite = [1, 2, 3, 4, 5, 6, 7].iter(); - ite.for_each(|_i| { - sec.failed_logon( - &event_another.system.event_id.to_string(), - &event_another.parse_event_data(), - ); - assert_eq!(0, sec.total_failed_logons); - assert_eq!(Option::None, sec.disp_login_failed()); - }); - } - - fn get_failed_logon_xml() -> String { - return r#" - - - - 4625 - 0 - 0 - 12544 - 0 - 0x8010000000000000 - - 6016 - - - Security - DESKTOP-M5SN04R - - - - S-1-0-0 - - - - - 0x0 - S-1-0-0 - Administrator - . - 0xc000006d - %%2313 - 0xc000006a - 3 - NtLmSsp - NTLM - fpEbpiox2Q3Qf8av - - - - - 0 - 0x0 - - - 192.168.198.149 - 33083 - - "# - .to_string(); - } - - // Hitするパターンとしないパターンをまとめてテスト - #[test] - fn test_sensitive_priviledge_hit() { - let xml_str = get_sensitive_prividedge_hit(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - - let mut sec = security::Security::new(); - sec.max_total_sensitive_privuse = 6; - - let ite = [1, 2, 3, 4, 5, 6, 7].iter(); - ite.for_each(|i| { - let msg = sec.sensitive_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); - // i == 7ときにHitしない - if i == &6 { - let v = msg.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"Sensititive Privilege Use Exceeds Threshold".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Potentially indicative of Mimikatz, multiple sensitive privilege calls have been made".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Username: Sec504".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Domain Name: SEC504STUDENT", - ite.next().unwrap_or(&"".to_string()) - ); - } else { - assert_eq!(Option::None, msg); - } - }); - } - - // eventidが異なるので、Hitしないテスト - #[test] - fn test_sensitive_priviledge_noteq_eventid() { - let xml_str = get_sensitive_prividedge_hit() - .replace(r"4673", r"4674"); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - - let mut sec = security::Security::new(); - sec.max_total_sensitive_privuse = 6; - - let ite = [1, 2, 3, 4, 5, 6, 7].iter(); - ite.for_each(|_i| { - let msg = sec.sensitive_priviledge( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - assert_eq!(Option::None, msg); - }); - } - - fn get_sensitive_prividedge_hit() -> String { - return r#" - - - - 4673 - 0 - 0 - 13056 - 0 - 0x8010000000000000 - - 8936 - - - Security - Sec504Student - - - - S-1-5-21-2977773840-2930198165-1551093962-1000 - Sec504 - SEC504STUDENT - 0x1e3dd - Security - - - SeTcbPrivilege - 0x15a8 - C:\Tools\mimikatz\mimikatz.exe - - "#.to_string(); - } - - // Hitするテスト - #[test] - fn test_attempt_priviledge_hit() { - let xml_str = get_attempt_priviledge_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - - let mut sec = security::Security::new(); - let msg = sec.attempt_priviledge( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - assert_ne!(Option::None, msg); - let v = msg.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"Possible Hidden Service Attempt".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(&"User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view".to_string(), ite.next().unwrap_or(&"".to_string())); - assert_eq!( - &"User: Sec504".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Target service: nginx".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"WRITE_DAC".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(Option::None, ite.next()); - } - - // accessmaskが異なるので、Hitしないテスト - #[test] - fn test_attempt_priviledge_noteq_accessmask() { - let xml_str = get_attempt_priviledge_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str.replace( - r"%%1539", - r"%%1538", - )) - .unwrap(); - - let mut sec = security::Security::new(); - let msg = sec.attempt_priviledge( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - assert_eq!(Option::None, msg); - } - - // Serviceが違うのでHitしないテスト - #[test] - fn test_attempt_priviledge_noteq_service() { - let xml_str = get_attempt_priviledge_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str.replace( - r"C:\Windows\System32\services.exe", - r"C:\Windows\System32\lsass.exe", - )) - .unwrap(); - - let mut sec = security::Security::new(); - let msg = sec.attempt_priviledge( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - assert_eq!(Option::None, msg); - } - - // EventIDが違うのでHitしないテスト - #[test] - fn test_attempt_priviledge_noteq_eventid() { - let xml_str = get_attempt_priviledge_xml(); - let event: event::Evtx = quick_xml::de::from_str( - &xml_str.replace(r"4674", r"4675"), - ) - .unwrap(); - - let mut sec = security::Security::new(); - let msg = sec.attempt_priviledge( - &event.system.event_id.to_string(), - &event.parse_event_data(), - ); - - assert_eq!(Option::None, msg); - } - - fn get_attempt_priviledge_xml() -> String { - return r#" - - - - 4674 - 0 - 0 - 13056 - 0 - 0x8020000000000000 - - 39406 - - - Security - Sec504Student - - - - S-1-5-21-2977773840-2930198165-1551093962-1000 - Sec504 - SEC504STUDENT - 0x99e3d - SC Manager - SERVICE OBJECT - nginx - 0xffff820cb1d95928 - %%1539 - - SeSecurityPrivilege - 0x21c - C:\Windows\System32\services.exe - - "#.to_string(); - } - - #[test] - fn test_pass_spray_hit() { - let mut sec = security::Security::new(); - // 6ユーザまでは表示されず、7ユーザー以上で表示されるようになる。 - sec.max_passspray_login = 6; - sec.max_passspray_uniquser = 6; - - test_pass_spray_hit_1cycle(&mut sec, "4648".to_string(), true); - // counterがreset確認のため、2回実行 - test_pass_spray_hit_1cycle(&mut sec, "4648".to_string(), true); - } - - // eventid異なるので、Hitしないはず - #[test] - fn test_pass_spray_noteq_eventid() { - let mut sec = security::Security::new(); - // 6ユーザまでは表示されず、7ユーザー以上で表示されるようになる。 - sec.max_passspray_login = 6; - sec.max_passspray_uniquser = 6; - - test_pass_spray_hit_1cycle(&mut sec, "4649".to_string(), false); - // counterがreset確認のため、2回実行 - test_pass_spray_hit_1cycle(&mut sec, "4649".to_string(), false); - } - - fn test_pass_spray_hit_1cycle(sec: &mut security::Security, event_id: String, is_eq: bool) { - [1,2,3,4,5,6,7].iter().for_each(|i| { - let rep_str = format!(r#"smisenar{}"#,i); - let event_id_tag = format!("{}", event_id); - let xml_str = get_passs_pray_hit().replace(r#"smisenar"#, &rep_str).replace(r"4648", &event_id_tag); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - [1,2,3,4,5,6,7].iter().for_each(|k|{ - let ret = sec.pass_spray(&event.system.event_id.to_string(), &event.parse_event_data()); - if i == &7 && k == &7 && is_eq { - let v = ret.unwrap(); - let mut ret_ite = v.iter(); - assert_eq!(&"Distributed Account Explicit Credential Use (Password Spray Attack)".to_string(),ret_ite.next().unwrap()); - assert_eq!(&"The use of multiple user account access attempts with explicit credentials is ".to_string(),ret_ite.next().unwrap()); - assert_eq!(&"an indicator of a password spray attack".to_string(),ret_ite.next().unwrap()); - assert_eq!("Target Usernames: smisenar1 smisenar2 smisenar3 smisenar4 smisenar5 smisenar6 smisenar7",ret_ite.next().unwrap()); - assert_eq!(&"Accessing Username: jwrig".to_string(),ret_ite.next().unwrap()); - assert_eq!(&"Accessing Host Name: DESKTOP-JR78RLP".to_string(),ret_ite.next().unwrap()); - assert_eq!(Option::None,ret_ite.next()); - } else { - assert_eq!(Option::None,ret); - } - }); - }); - } - - fn get_passs_pray_hit() -> String { - return r#" - - - - 4648 - 0 - 0 - 12544 - 0 - 0x8020000000000000 - - 43097 - - - Security - DESKTOP-JR78RLP - - - - S-1-5-21-979008924-657238111-836329461-1002 - jwrig - DESKTOP-JR78RLP - 0x3069d - {00000000-0000-0000-0000-000000000000} - smisenar - DOMAIN - {00000000-0000-0000-0000-000000000000} - DESKTOP-JR78RLP - DESKTOP-JR78RLP - 0x4 - - 172.16.144.128 - 445 - - "#.to_string(); - } - - // 普通にHitするテスト - #[test] - fn test_audit_log_cleared_hit() { - let xml_str = get_audit_log_cleared_xml(); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - - let mut sec = security::Security::new(); - let msg = sec.audit_log_cleared(&event.system.event_id.to_string(), &event.user_data); - - assert_ne!(Option::None, msg); - let v = msg.unwrap(); - let mut ite = v.iter(); - assert_eq!( - &"Audit Log Clear".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"The Audit log was cleared".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!( - &"Security ID: jwrig".to_string(), - ite.next().unwrap_or(&"".to_string()) - ); - assert_eq!(Option::None, ite.next()); - } - - // eventid違うのでHitしないはず - #[test] - fn test_audit_log_cleared_noteq_eventid() { - let xml_str = get_audit_log_cleared_xml() - .replace(r"1102", r"1103"); - let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); - - let mut sec = security::Security::new(); - let msg = sec.audit_log_cleared(&event.system.event_id.to_string(), &event.user_data); - assert_eq!(Option::None, msg); - } - - fn get_audit_log_cleared_xml() -> String { - return r#" - - - - 1102 - 0 - 4 - 104 - 0 - 0x4020000000000000 - - 42803 - - - Security - DESKTOP-JR78RLP - - - - - S-1-5-21-979008924-657238111-836329461-1002 - jwrig - DESKTOP-JR78RLP - 0x30550 - - - "#.to_string(); - } -} diff --git a/src/detections/sysmon.rs b/src/detections/sysmon.rs deleted file mode 100644 index 37f01874..00000000 --- a/src/detections/sysmon.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::detections::utils::check_command; -use crate::models::event; -use std::collections::HashMap; - -pub struct Sysmon { - checkunsigned: u16, -} - -impl Sysmon { - pub fn new() -> Sysmon { - Sysmon { checkunsigned: 0 } - } - - pub fn detection( - &mut self, - event_id: String, - _system: &event::System, - event_data: HashMap, - ) { - self.check_command_lines(&event_id, &event_data); - self.check_for_unsigned_files(&event_id, &event_data); - } - - fn check_command_lines(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "1" { - return; - } - - if let Some(_command_line) = event_data.get("CommandLine") { - let default = "".to_string(); - let _creater = event_data.get("ParentImage").unwrap_or(&default); - - check_command(1, _command_line, 1000, 0, "", _creater); - } - } - - fn check_for_unsigned_files( - &mut self, - event_id: &String, - event_data: &HashMap, - ) { - if event_id != "7" { - return; - } - - if self.checkunsigned == 1 { - let default = "".to_string(); - let _signed = event_data.get("Signed").unwrap_or(&default); - if _signed == "false" { - let _image = event_data.get("Image").unwrap_or(&default); - let _command_line = event_data.get("ImageLoaded").unwrap_or(&default); - - println!("Message : Unsigned Image (DLL)"); - println!("Result : Loaded by: {}", _image); - println!("Command : {}", _command_line); - } - }; - } -} diff --git a/src/detections/system.rs b/src/detections/system.rs deleted file mode 100644 index 14619d60..00000000 --- a/src/detections/system.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::detections::utils; -use crate::models::event; -use std::collections::HashMap; - -pub struct System {} - -impl System { - pub fn new() -> System { - System {} - } - - pub fn detection( - &mut self, - event_id: String, - system: &event::System, - event_data: HashMap, - ) { - self.system_log_clear(&event_id); - self.windows_event_log(&event_id, &event_data); - self.new_service_created(&event_id, &event_data); - self.interactive_service_warning(&event_id, &event_data); - self.suspicious_service_name(&event_id, &event_data); - } - - fn new_service_created(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "7045" { - return; - } - - let default = String::from(""); - let servicename = &event_data.get("ServiceName").unwrap_or(&default); - let commandline = &event_data.get("ImagePath").unwrap_or(&default); - let text = utils::check_regex(&servicename, 1); - if !text.is_empty() { - println!("Message : New Service Created"); - println!("Command : {}", commandline); - println!("Results : Service name: {}", servicename); - println!("Results : {}", text); - } - if !commandline.is_empty() { - utils::check_command(7045, &commandline, 1000, 0, &servicename, &""); - } - } - - fn interactive_service_warning( - &mut self, - event_id: &String, - event_data: &HashMap, - ) { - if event_id != "7030" { - return; - } - - let default = String::from(""); - let servicename = &event_data.get("param1").unwrap_or(&default); - println!("Message : Interactive service warning"); - println!("Results : Service name: {}", servicename); - println!("Results : Malware (and some third party software) trigger this warning"); - println!("{}", utils::check_regex(&servicename, 1)); - } - - fn suspicious_service_name(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "7036" { - return; - } - - let default = String::from(""); - let servicename = &event_data.get("param1").unwrap_or(&default); - let text = utils::check_regex(&servicename, 1); - if !text.is_empty() { - println!("Message : Suspicious Service Name"); - println!("Results : Service name: {}", servicename); - println!("Results : {}", text); - } - } - - fn system_log_clear(&mut self, event_id: &String) { - if event_id != "104" { - return; - } - - println!("Message : System Log Clear"); - println!("Results : The System log was cleared."); - } - - fn windows_event_log(&mut self, event_id: &String, event_data: &HashMap) { - if event_id != "7040" { - return; - } - - if let Some(_param1) = event_data.get("param1") { - if _param1 == "Windows Event Log" { - println!("Service name : {}", _param1); - if let Some(_param2) = event_data.get("param2") { - if _param2 == "disabled" { - println!("Message : Event Log Service Stopped"); - println!( - "Results : Selective event log manipulation may follow this event." - ); - } else if _param2 == "auto start" { - println!("Message : Event Log Service Started"); - println!( - "Results : Selective event log manipulation may precede this event." - ); - } - } - } - } - } -}