regex rule implemented

This commit is contained in:
ichiichi11
2020-11-21 22:56:21 +09:00
parent 1abdbafb5a
commit d976ddc4d0
31 changed files with 675 additions and 1809 deletions

View File

@@ -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
@@ -10,3 +10,6 @@ param1,Event.EventData.param1
param2,Event.EventData.param2
ServiceName,Event.EventData.ServiceName
ImagePath,Event.EventData.ImagePath
ContextInfo,Event.EventData.ContextInfo
Path,Event.EventData.Path
ScriptBlockText,Event.EventData.ScriptBlockText#Name

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, String>,
) {
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");
}
}
}
}

View File

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

View File

@@ -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<Vec<String>>,
pub whitelist: Vec<Vec<String>>,
pub args: ArgMatches<'static>,
pub event_key_alias_config: EventKeyAliasConfig,
}
@@ -17,6 +19,8 @@ pub fn singleton() -> Box<SingletonReader> {
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,34 +61,41 @@ fn build_app() -> clap::App<'static, 'static> {
#[derive(Clone)]
pub struct EventKeyAliasConfig {
key_to_eventkey: HashMap<String,String>,
key_to_eventkey: HashMap<String, String>,
}
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| {
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];
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;

View File

@@ -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<std::fs::File>) {
// from .etvx to json
// serialize from .etvx to jsons
let event_records: Vec<Value> = 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<RuleNode> = rulefile_loader
let selection_rules: Vec<RuleNode> = 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

View File

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

View File

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

View File

@@ -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<DetectionNode> {
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>) -> String {
return key_list
.iter()
.fold("detection -> selection".to_string(), |mut acc, cur| {
acc = acc + " -> " + cur;
return acc;
});
}
fn parse_selection(yaml: &Yaml) -> Option<Box<dyn SelectionNode>> {
// 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<String>, yaml: &Yaml) -> Box<dyn SelectionNode> {
fn parse_selection_recursively(key_list: Vec<String>, yaml: &Yaml) -> Box<dyn SelectionNode> {
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<String>, yaml: &Yaml) -> Box<dy
});
return Box::new(and_node);
} else if yaml.as_vec().is_some() {
// 配列はOR条件と解釈する。
let mut or_node = OrSelectionNode::new();
yaml.as_vec().unwrap().iter().for_each(|child_yaml| {
let child_node = parse_selection_recursively(key_list.clone(), child_yaml);
@@ -39,37 +89,61 @@ fn parse_selection_recursively(mut key_list: Vec<String>, yaml: &Yaml) -> Box<dy
return Box::new(or_node);
} else {
return Box::new(FieldSelectionNode::new(key_list, yaml.clone()));
// 連想配列と配列以外は末端ノード
return Box::new(LeafSelectionNode::new(key_list, yaml.clone()));
}
}
/////////////// RuleNode
// Ruleファイルを表すード
pub struct RuleNode {
pub yaml: Yaml,
pub detection: DetectionNode,
detection: Option<DetectionNode>,
}
//////////////// Detection Node
pub struct DetectionNode {
selection: Option<Box<dyn SelectionNode>>,
}
impl RuleNode {
pub fn init(&mut self) -> Result<(), Vec<String>> {
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<Box<dyn SelectionNode>>,
}
///////////////// AndSelectionNode
impl DetectionNode {
fn init(&mut self) -> Result<(), Vec<String>> {
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<String>>;
}
// detection - selection配下でAND条件を表すード
struct AndSelectionNode {
pub child_nodes: Vec<Box<dyn SelectionNode>>,
}
@@ -88,9 +162,36 @@ impl SelectionNode for AndSelectionNode {
return child_node.as_ref().select(event_record);
});
}
fn init(&mut self) -> Result<(), Vec<String>> {
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<String>, cur: Vec<String>| -> Vec<String> {
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<Box<dyn SelectionNode>>,
}
@@ -109,96 +210,198 @@ impl SelectionNode for OrSelectionNode {
return child_node.as_ref().select(event_record);
});
}
fn init(&mut self) -> Result<(), Vec<String>> {
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<String>, cur: Vec<String>| -> Vec<String> {
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<String>,
select_value: Yaml,
matcher: Option<Box<dyn LeafMatcher>>,
}
impl FieldSelectionNode {
fn new(key_list: Vec<String>, value_yaml: Yaml) -> FieldSelectionNode {
return FieldSelectionNode {
impl LeafSelectionNode {
fn new(key_list: Vec<String>, 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;
return get_event_value(&self.key_list[0].to_string(), event_value);
}
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];
// LeafMatcherの一覧を取得する。
fn get_matchers(&self) -> Vec<Box<dyn LeafMatcher>> {
return vec![Box::new(RegexMatcher::new())];
}
return Option::Some(ret);
}
// TODO Matcherのインスタンスが都度生成されないようにする。
fn get_matchers(&self) -> Vec<Box<dyn FieldSelectionMatcher>> {
return vec![Box::new(ValueMatcher {})];
// LeafMatcherを取得する。
fn get_matcher(&self) -> Option<Box<dyn LeafMatcher>> {
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<String>> {
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<String>) -> bool;
fn is_match(
&self,
key_list: &Vec<String>,
select_value: &Yaml,
event_value: Option<&Value>,
) -> bool;
fn is_match(&self, event_value: Option<&Value>) -> bool;
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>>;
}
struct ValueMatcher {}
// 正規表現で比較するロジックを表すクラス
struct RegexMatcher {
re: Option<Regex>,
}
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<String>) -> bool {
return key_list.is_empty();
}
fn is_match(
&self,
key_list: &Vec<String>,
select_value: &Yaml,
event_value: Option<&Value>,
) -> bool {
return true;
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
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(());
}
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,
};
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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<String, String>,
) {
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<String, String>) {
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<String, String>,
) {
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<String, String>) {
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<String, String>) {
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."
);
}
}
}
}
}
}