regex rule implemented
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
alias,event_key
|
alias,event_key
|
||||||
EventID,Event.System.EventId
|
EventID,Event.System.EventID
|
||||||
Channel,Event.System.Channel
|
Channel,Event.System.Channel
|
||||||
CommandLine,Event.EventData.CommandLine
|
CommandLine,Event.EventData.CommandLine
|
||||||
Signed,Event.EventData.Signed
|
Signed,Event.EventData.Signed
|
||||||
@@ -10,3 +10,6 @@ param1,Event.EventData.param1
|
|||||||
param2,Event.EventData.param2
|
param2,Event.EventData.param2
|
||||||
ServiceName,Event.EventData.ServiceName
|
ServiceName,Event.EventData.ServiceName
|
||||||
ImagePath,Event.EventData.ImagePath
|
ImagePath,Event.EventData.ImagePath
|
||||||
|
ContextInfo,Event.EventData.ContextInfo
|
||||||
|
Path,Event.EventData.Path
|
||||||
|
ScriptBlockText,Event.EventData.ScriptBlockText#Name
|
||||||
@@ -6,12 +6,12 @@ logsource:
|
|||||||
product: windows
|
product: windows
|
||||||
detection:
|
detection:
|
||||||
selection:
|
selection:
|
||||||
EventLog: PowerShell
|
Channel: PowerShell
|
||||||
EventID: 4103
|
EventID: 4103
|
||||||
ContextInfo:
|
ContextInfo:
|
||||||
- Host Application
|
- Host Application
|
||||||
- ホスト アプリケーション
|
- ホスト アプリケーション
|
||||||
condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
level: medium
|
level: medium
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ logsource:
|
|||||||
product: windows
|
product: windows
|
||||||
detection:
|
detection:
|
||||||
selection:
|
selection:
|
||||||
EventLog: PowerShell
|
Channel: PowerShell
|
||||||
EventID: 4104
|
EventID: 4104
|
||||||
Path: ''
|
Path: null
|
||||||
ScriptBlockText: '.'
|
ScriptBlockText: null
|
||||||
condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
level: medium
|
level: medium
|
||||||
|
|||||||
17
rules/deep_blue_cli/security/1102.yml
Normal file
17
rules/deep_blue_cli/security/1102.yml
Normal 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
|
||||||
17
rules/deep_blue_cli/security/4673.yml
Normal file
17
rules/deep_blue_cli/security/4673.yml
Normal 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
|
||||||
19
rules/deep_blue_cli/security/4674.yml
Normal file
19
rules/deep_blue_cli/security/4674.yml
Normal 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
|
||||||
18
rules/deep_blue_cli/security/4688.yml
Normal file
18
rules/deep_blue_cli/security/4688.yml
Normal 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
|
||||||
17
rules/deep_blue_cli/security/4720.yml
Normal file
17
rules/deep_blue_cli/security/4720.yml
Normal 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
|
||||||
18
rules/deep_blue_cli/security/4728.yml
Normal file
18
rules/deep_blue_cli/security/4728.yml
Normal 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
|
||||||
18
rules/deep_blue_cli/security/4732.yml
Normal file
18
rules/deep_blue_cli/security/4732.yml
Normal 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
|
||||||
18
rules/deep_blue_cli/security/4756.yml
Normal file
18
rules/deep_blue_cli/security/4756.yml
Normal 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
|
||||||
17
rules/deep_blue_cli/security/_4625.yml
Normal file
17
rules/deep_blue_cli/security/_4625.yml
Normal 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
|
||||||
17
rules/deep_blue_cli/security/_4648.yml
Normal file
17
rules/deep_blue_cli/security/_4648.yml
Normal 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
|
||||||
19
rules/deep_blue_cli/security/_4672.yml
Normal file
19
rules/deep_blue_cli/security/_4672.yml
Normal 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
|
||||||
19
rules/deep_blue_cli/sysmon/1.yml
Normal file
19
rules/deep_blue_cli/sysmon/1.yml
Normal 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
|
||||||
|
|
||||||
18
rules/deep_blue_cli/sysmon/7.yml
Normal file
18
rules/deep_blue_cli/sysmon/7.yml
Normal 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
|
||||||
17
rules/deep_blue_cli/system/104.yml
Normal file
17
rules/deep_blue_cli/system/104.yml
Normal 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
|
||||||
19
rules/deep_blue_cli/system/7030.yml
Normal file
19
rules/deep_blue_cli/system/7030.yml
Normal 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
|
||||||
19
rules/deep_blue_cli/system/7036.yml
Normal file
19
rules/deep_blue_cli/system/7036.yml
Normal 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
|
||||||
21
rules/deep_blue_cli/system/7040.yml
Normal file
21
rules/deep_blue_cli/system/7040.yml
Normal 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
|
||||||
22
rules/deep_blue_cli/system/7045.yml
Normal file
22
rules/deep_blue_cli/system/7045.yml
Normal 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
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SingletonReader {
|
pub struct SingletonReader {
|
||||||
|
pub regex: Vec<Vec<String>>,
|
||||||
|
pub whitelist: Vec<Vec<String>>,
|
||||||
pub args: ArgMatches<'static>,
|
pub args: ArgMatches<'static>,
|
||||||
pub event_key_alias_config: EventKeyAliasConfig,
|
pub event_key_alias_config: EventKeyAliasConfig,
|
||||||
}
|
}
|
||||||
@@ -17,6 +19,8 @@ pub fn singleton() -> Box<SingletonReader> {
|
|||||||
unsafe {
|
unsafe {
|
||||||
ONCE.call_once(|| {
|
ONCE.call_once(|| {
|
||||||
let singleton = SingletonReader {
|
let singleton = SingletonReader {
|
||||||
|
regex: read_csv("regexes.txt"),
|
||||||
|
whitelist: read_csv("whitelist.txt"),
|
||||||
args: build_app().get_matches(),
|
args: build_app().get_matches(),
|
||||||
event_key_alias_config: load_eventkey_alias(),
|
event_key_alias_config: load_eventkey_alias(),
|
||||||
};
|
};
|
||||||
@@ -57,34 +61,41 @@ fn build_app() -> clap::App<'static, 'static> {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventKeyAliasConfig {
|
pub struct EventKeyAliasConfig {
|
||||||
key_to_eventkey: HashMap<String,String>,
|
key_to_eventkey: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventKeyAliasConfig {
|
impl EventKeyAliasConfig {
|
||||||
pub fn new() -> 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);
|
return self.key_to_eventkey.get(&alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_eventkey_alias() -> EventKeyAliasConfig {
|
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 {
|
if line.len() != 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let alias = line[0];
|
let empty = &"".to_string();
|
||||||
let event_key = line[1];
|
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 {
|
if alias.len() == 0 || event_key.len() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.key_to_eventkey.insert(alias, event_key);
|
config
|
||||||
|
.key_to_eventkey
|
||||||
|
.insert(alias.to_owned(), event_key.to_owned());
|
||||||
});
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
extern crate csv;
|
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
|
extern crate csv;
|
||||||
|
|
||||||
|
use crate::detections::print::Message;
|
||||||
use crate::detections::rule;
|
use crate::detections::rule;
|
||||||
use crate::detections::rule::RuleNode;
|
use crate::detections::rule::RuleNode;
|
||||||
use crate::detections::print::{Message};
|
|
||||||
use crate::yaml::ParseYaml;
|
use crate::yaml::ParseYaml;
|
||||||
|
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
@@ -13,18 +13,15 @@ use serde_json::{Error, Value};
|
|||||||
const DIRPATH_RULES: &str = "rules";
|
const DIRPATH_RULES: &str = "rules";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Detection {
|
pub struct Detection {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Detection {
|
impl Detection {
|
||||||
pub fn new() -> Detection {
|
pub fn new() -> Detection {
|
||||||
Detection {
|
Detection {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, mut parser: EvtxParser<std::fs::File>) {
|
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
|
let event_records: Vec<Value> = parser
|
||||||
.records_json()
|
.records_json()
|
||||||
.filter_map(|result_record| {
|
.filter_map(|result_record| {
|
||||||
@@ -44,10 +41,6 @@ impl Detection {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
event_records.iter().for_each(|event_rec| {
|
|
||||||
println!("{}", event_rec["Event"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// load rule files
|
// load rule files
|
||||||
let mut rulefile_loader = ParseYaml::new();
|
let mut rulefile_loader = ParseYaml::new();
|
||||||
let resutl_readdir = rulefile_loader.read_dir(DIRPATH_RULES);
|
let resutl_readdir = rulefile_loader.read_dir(DIRPATH_RULES);
|
||||||
@@ -57,19 +50,39 @@ impl Detection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse rule files
|
// parse rule files
|
||||||
let rules: Vec<RuleNode> = rulefile_loader
|
let selection_rules: Vec<RuleNode> = rulefile_loader
|
||||||
.files
|
.files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|rule_file| rule::parse_rule(rule_file))
|
.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();
|
.collect();
|
||||||
|
|
||||||
// selection rule files and collect log
|
// selection rule files and collect message
|
||||||
let mut message = Message::new();
|
let mut message = Message::new();
|
||||||
rules.iter().for_each(|rule| {
|
selection_rules.iter().for_each(|rule| {
|
||||||
&event_records
|
&event_records
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|event_record| rule.detection.select(event_record))
|
.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()));
|
.for_each(|event_record| {
|
||||||
|
message.insert(
|
||||||
|
Utc.ymd(1996, 2, 27).and_hms(1, 5, 1),
|
||||||
|
event_record.to_string(),
|
||||||
|
)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// output message
|
// output message
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
mod application;
|
|
||||||
mod applocker;
|
|
||||||
mod common;
|
mod common;
|
||||||
pub mod configs;
|
pub mod configs;
|
||||||
pub mod detection;
|
pub mod detection;
|
||||||
mod powershell;
|
|
||||||
pub mod print;
|
pub mod print;
|
||||||
mod rule;
|
mod rule;
|
||||||
mod security;
|
|
||||||
mod sysmon;
|
|
||||||
mod system;
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,73 @@
|
|||||||
|
extern crate regex;
|
||||||
|
|
||||||
|
use crate::detections::configs;
|
||||||
|
use regex::Regex;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use yaml_rust::Yaml;
|
use yaml_rust::Yaml;
|
||||||
use crate::detections::configs;
|
|
||||||
|
|
||||||
pub fn parse_rule(yaml: Yaml) -> RuleNode {
|
pub fn parse_rule(yaml: Yaml) -> RuleNode {
|
||||||
let selection = parse_selection(&yaml);
|
let detection = parse_detection(&yaml);
|
||||||
|
|
||||||
return RuleNode {
|
return RuleNode {
|
||||||
yaml: yaml,
|
yaml: yaml,
|
||||||
detection: DetectionNode {
|
detection: detection,
|
||||||
selection: selection,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>> {
|
fn parse_selection(yaml: &Yaml) -> Option<Box<dyn SelectionNode>> {
|
||||||
|
// TODO detection-selectionが存在しない場合のチェック
|
||||||
let selection_yaml = &yaml["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));
|
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() {
|
if yaml.as_hash().is_some() {
|
||||||
|
// 連想配列はAND条件と解釈する
|
||||||
let yaml_hash = yaml.as_hash().unwrap();
|
let yaml_hash = yaml.as_hash().unwrap();
|
||||||
let mut and_node = AndSelectionNode::new();
|
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);
|
return Box::new(and_node);
|
||||||
} else if yaml.as_vec().is_some() {
|
} else if yaml.as_vec().is_some() {
|
||||||
|
// 配列はOR条件と解釈する。
|
||||||
let mut or_node = OrSelectionNode::new();
|
let mut or_node = OrSelectionNode::new();
|
||||||
yaml.as_vec().unwrap().iter().for_each(|child_yaml| {
|
yaml.as_vec().unwrap().iter().for_each(|child_yaml| {
|
||||||
let child_node = parse_selection_recursively(key_list.clone(), 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);
|
return Box::new(or_node);
|
||||||
} else {
|
} 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 struct RuleNode {
|
||||||
pub yaml: Yaml,
|
pub yaml: Yaml,
|
||||||
pub detection: DetectionNode,
|
detection: Option<DetectionNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////// Detection Node
|
impl RuleNode {
|
||||||
pub struct DetectionNode {
|
pub fn init(&mut self) -> Result<(), Vec<String>> {
|
||||||
selection: Option<Box<dyn SelectionNode>>,
|
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 {
|
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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.selection.as_ref().unwrap().select(event_record);
|
return selection.unwrap().select(event_record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////// Selection Node
|
// Ruleファイルのdetectionを表すノード
|
||||||
trait SelectionNode {
|
struct DetectionNode {
|
||||||
fn select(&self, event_record: &Value) -> bool;
|
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 {
|
struct AndSelectionNode {
|
||||||
pub child_nodes: Vec<Box<dyn SelectionNode>>,
|
pub child_nodes: Vec<Box<dyn SelectionNode>>,
|
||||||
}
|
}
|
||||||
@@ -88,9 +162,36 @@ impl SelectionNode for AndSelectionNode {
|
|||||||
return child_node.as_ref().select(event_record);
|
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 {
|
struct OrSelectionNode {
|
||||||
pub child_nodes: Vec<Box<dyn SelectionNode>>,
|
pub child_nodes: Vec<Box<dyn SelectionNode>>,
|
||||||
}
|
}
|
||||||
@@ -109,96 +210,198 @@ impl SelectionNode for OrSelectionNode {
|
|||||||
return child_node.as_ref().select(event_record);
|
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
|
// detection - selection配下の末端ノード
|
||||||
struct FieldSelectionNode {
|
struct LeafSelectionNode {
|
||||||
key_list: Vec<String>,
|
key_list: Vec<String>,
|
||||||
select_value: Yaml,
|
select_value: Yaml,
|
||||||
|
matcher: Option<Box<dyn LeafMatcher>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldSelectionNode {
|
impl LeafSelectionNode {
|
||||||
fn new(key_list: Vec<String>, value_yaml: Yaml) -> FieldSelectionNode {
|
fn new(key_list: Vec<String>, value_yaml: Yaml) -> LeafSelectionNode {
|
||||||
return FieldSelectionNode {
|
return LeafSelectionNode {
|
||||||
key_list: key_list,
|
key_list: key_list,
|
||||||
select_value: value_yaml,
|
select_value: value_yaml,
|
||||||
|
matcher: Option::None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。
|
// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。
|
||||||
// TODO Messageを出力する際も利用するので、共通して使えるようにrefactoringする。
|
|
||||||
fn get_event_value<'a>(&self, event_value: &'a Value) -> Option<&'a Value> {
|
fn get_event_value<'a>(&self, event_value: &'a Value) -> Option<&'a Value> {
|
||||||
if self.key_list.is_empty() {
|
if self.key_list.is_empty() {
|
||||||
return Option::None;
|
return Option::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let key: &str = &self.key_list[0];
|
return get_event_value(&self.key_list[0].to_string(), event_value);
|
||||||
if key.len() == 0 {
|
|
||||||
return Option::None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_key = match configs::singleton().event_key_alias_config.get_event_key(key.to_string()) {
|
// LeafMatcherの一覧を取得する。
|
||||||
Some(alias_event_key) => { alias_event_key }
|
fn get_matchers(&self) -> Vec<Box<dyn LeafMatcher>> {
|
||||||
None => { key }
|
return vec![Box::new(RegexMatcher::new())];
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
// LeafMatcherを取得する。
|
||||||
}
|
fn get_matcher(&self) -> Option<Box<dyn LeafMatcher>> {
|
||||||
|
let matchers = self.get_matchers();
|
||||||
// TODO Matcherのインスタンスが都度生成されないようにする。
|
let mut match_key_list = self.key_list.clone();
|
||||||
fn get_matchers(&self) -> Vec<Box<dyn FieldSelectionMatcher>> {
|
match_key_list.remove(0);
|
||||||
return vec![Box::new(ValueMatcher {})];
|
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 {
|
fn select(&self, event_record: &Value) -> bool {
|
||||||
let matchers = self.get_matchers();
|
if self.matcher.is_none() {
|
||||||
let matcher = matchers
|
|
||||||
.into_iter()
|
|
||||||
.find(|matcher| matcher.is_target_key(&self.key_list));
|
|
||||||
if matcher.is_none() {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_value = self.get_event_value(event_record);
|
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()
|
.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_target_key(&self, key_list: &Vec<String>) -> bool;
|
||||||
fn is_match(
|
|
||||||
&self,
|
fn is_match(&self, event_value: Option<&Value>) -> bool;
|
||||||
key_list: &Vec<String>,
|
|
||||||
select_value: &Yaml,
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>>;
|
||||||
event_value: Option<&Value>,
|
|
||||||
) -> bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
||||||
return key_list.is_empty();
|
return key_list.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_match(
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
||||||
&self,
|
if select_value.is_null() {
|
||||||
key_list: &Vec<String>,
|
self.re = Option::None;
|
||||||
select_value: &Yaml,
|
return Result::Ok(());
|
||||||
event_value: Option<&Value>,
|
}
|
||||||
) -> bool {
|
|
||||||
return true;
|
// 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
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user