under constructing

This commit is contained in:
ichiichi11
2020-11-21 15:04:28 +09:00
parent a794e011a9
commit 1abdbafb5a
13 changed files with 360 additions and 71 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/target
/samples
*.test
/.vscode/

1
Cargo.lock generated
View File

@@ -1192,6 +1192,7 @@ dependencies = [
"evtx",
"flate2",
"lazy_static",
"linked-hash-map",
"quick-xml 0.17.2",
"regex",
"serde",

View File

@@ -20,6 +20,7 @@ flate2 = "1.0"
lazy_static = "1.4.0"
chrono = "0.4.19"
yaml-rust = "0.4"
linked-hash-map = "0.5.3"
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

12
config/eventkey_alias.txt Normal file
View File

@@ -0,0 +1,12 @@
alias,event_key
EventID,Event.System.EventId
Channel,Event.System.Channel
CommandLine,Event.EventData.CommandLine
Signed,Event.EventData.Signed
ProcessName,Event.EventData.ProcessName
AccessMask,Event.EventData.AccessMask
TargetUserName,Event.EventData.TargetUserName
param1,Event.EventData.param1
param2,Event.EventData.param2
ServiceName,Event.EventData.ServiceName
ImagePath,Event.EventData.ImagePath

View File

@@ -0,0 +1,20 @@
title: PowerShell Execution Pipeline
description: hogehoge
enabled: true
author: Yea
logsource:
product: windows
detection:
selection:
EventLog: PowerShell
EventID: 4103
ContextInfo:
- Host Application
- ホスト アプリケーション
condition: selection
falsepositives:
- unknown
level: medium
output: 'command=%CommandLine%'
creation_date: 2020/11/8
uodated_date: 2020/11/8

View File

@@ -0,0 +1,19 @@
title: PowerShell Execution Remote Command
description: hogehoge
enabled: true
author: Yea
logsource:
product: windows
detection:
selection:
EventLog: PowerShell
EventID: 4104
Path: ''
ScriptBlockText: '.'
condition: selection
falsepositives:
- unknown
level: medium
output: 'command=%CommandLine%'
creation_date: 2020/11/8
uodated_date: 2020/11/8

View File

@@ -1,4 +0,0 @@
[rule]
severity = "high"
name = "4103"
message = "Execute Pipeline"

View File

@@ -1,4 +0,0 @@
[rule]
severity = "high"
name = "4104"
message = "Excute Remote Command"

View File

@@ -2,12 +2,12 @@ use clap::{App, AppSettings, Arg, ArgMatches};
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,
}
pub fn singleton() -> Box<SingletonReader> {
@@ -17,9 +17,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(),
};
SINGLETON = Some(Box::new(singleton));
@@ -56,6 +55,41 @@ fn build_app() -> clap::App<'static, 'static> {
.arg(Arg::from_usage("--credits 'Zachary Mathis, Akira Nishikawa'"))
}
#[derive(Clone)]
pub struct EventKeyAliasConfig {
key_to_eventkey: HashMap<String,String>,
}
impl EventKeyAliasConfig {
pub fn new() -> EventKeyAliasConfig {
return EventKeyAliasConfig{ key_to_eventkey: HashMap::new() };
}
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();
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;
}
config.key_to_eventkey.insert(alias, event_key);
});
return config;
}
fn read_csv(filename: &str) -> Vec<Vec<String>> {
let mut f = File::open(filename).expect("file not found!!!");
let mut contents: String = String::new();

View File

@@ -1,74 +1,78 @@
extern crate csv;
extern crate quick_xml;
extern crate chrono;
use crate::detections::application;
use crate::detections::applocker;
use crate::detections::common;
use crate::detections::powershell;
use crate::detections::security;
use crate::detections::sysmon;
use crate::detections::system;
use crate::models::event;
use crate::detections::rule;
use crate::detections::rule::RuleNode;
use crate::detections::print::{Message};
use crate::yaml::ParseYaml;
use chrono::{TimeZone, Utc};
use evtx::EvtxParser;
use quick_xml::de::DeError;
use std::collections::BTreeMap;
use serde_json::{Error, Value};
const DIRPATH_RULES: &str = "rules";
#[derive(Debug)]
pub struct Detection {
timeline_list: BTreeMap<String, String>,
}
impl Detection {
pub fn new() -> Detection {
Detection {
timeline_list: BTreeMap::new(),
}
}
pub fn start(&mut self, mut parser: EvtxParser<std::fs::File>) -> Result<(), DeError> {
let mut common: common::Common = common::Common::new();
let mut security = security::Security::new();
let mut system = system::System::new();
let mut application = application::Application::new();
let mut applocker = applocker::AppLocker::new();
let mut sysmon = sysmon::Sysmon::new();
let mut powershell = powershell::PowerShell::new();
for record in parser.records() {
match record {
Ok(r) => {
let event: event::Evtx = quick_xml::de::from_str(&r.data)?;
let event_id = event.system.event_id.to_string();
let channel = event.system.channel.to_string();
let event_data = event.parse_event_data();
&common.detection(&event.system, &event_data);
if channel == "Security" {
&security.detection(event_id, &event.system, &event.user_data, event_data);
} else if channel == "System" {
&system.detection(event_id, &event.system, event_data);
} else if channel == "Application" {
&application.detection(event_id, &event.system, event_data);
} else if channel == "Microsoft-Windows-PowerShell/Operational" {
&powershell.detection(event_id, &event.system, event_data);
} else if channel == "Microsoft-Windows-Sysmon/Operational" {
&sysmon.detection(event_id, &event.system, event_data);
} else if channel == "Microsoft-Windows-AppLocker/EXE and DLL" {
&applocker.detection(event_id, &event.system, event_data);
} else {
//&other.detection();
}
pub fn start(&mut self, mut parser: EvtxParser<std::fs::File>) {
// from .etvx to json
let event_records: Vec<Value> = parser
.records_json()
.filter_map(|result_record| {
if result_record.is_err() {
eprintln!("{}", result_record.unwrap_err());
return Option::None;
}
Err(e) => eprintln!("{}", e),
}
//// refer https://rust-lang-nursery.github.io/rust-cookbook/encoding/complex.html
let result_json: Result<Value, Error> =
serde_json::from_str(&result_record.unwrap().data);
if result_json.is_err() {
eprintln!("{}", result_json.unwrap_err());
return Option::None;
}
return result_json.ok();
})
.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);
if resutl_readdir.is_err() {
eprintln!("{}", resutl_readdir.unwrap_err());
return;
}
////////////////////////////
// 表示
////////////////////////////
common.disp();
security.disp();
// parse rule files
let rules: Vec<RuleNode> = rulefile_loader
.files
.into_iter()
.map(|rule_file| rule::parse_rule(rule_file))
.collect();
return Ok(());
// selection rule files and collect log
let mut message = Message::new();
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()));
});
// output message
message.debug();
}
}

View File

@@ -5,6 +5,7 @@ pub mod configs;
pub mod detection;
mod powershell;
pub mod print;
mod rule;
mod security;
mod sysmon;
mod system;

204
src/detections/rule.rs Normal file
View File

@@ -0,0 +1,204 @@
use serde_json::Value;
use yaml_rust::Yaml;
use crate::detections::configs;
pub fn parse_rule(yaml: Yaml) -> RuleNode {
let selection = parse_selection(&yaml);
return RuleNode {
yaml: yaml,
detection: DetectionNode {
selection: selection,
},
};
}
fn parse_selection(yaml: &Yaml) -> Option<Box<dyn SelectionNode>> {
let selection_yaml = &yaml["detection"]["selection"];
return Option::Some(parse_selection_recursively(vec![], &selection_yaml));
}
fn parse_selection_recursively(mut key_list: Vec<String>, yaml: &Yaml) -> Box<dyn SelectionNode> {
if yaml.as_hash().is_some() {
let yaml_hash = yaml.as_hash().unwrap();
let mut and_node = AndSelectionNode::new();
yaml_hash.keys().for_each(|hash_key| {
let child_yaml = yaml_hash.get(hash_key).unwrap();
let mut child_key_list = key_list.clone();
child_key_list.push(hash_key.as_str().unwrap().to_string());
let child_node = parse_selection_recursively(child_key_list, child_yaml);
and_node.child_nodes.push(child_node);
});
return Box::new(and_node);
} else if yaml.as_vec().is_some() {
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);
or_node.child_nodes.push(child_node);
});
return Box::new(or_node);
} else {
return Box::new(FieldSelectionNode::new(key_list, yaml.clone()));
}
}
/////////////// RuleNode
pub struct RuleNode {
pub yaml: Yaml,
pub detection: DetectionNode,
}
//////////////// Detection Node
pub struct DetectionNode {
selection: Option<Box<dyn SelectionNode>>,
}
impl DetectionNode {
pub fn select(&self, event_record: &Value) -> bool {
if self.selection.is_none() {
return false;
}
return self.selection.as_ref().unwrap().select(event_record);
}
}
//////////// Selection Node
trait SelectionNode {
fn select(&self, event_record: &Value) -> bool;
}
///////////////// AndSelectionNode
struct AndSelectionNode {
pub child_nodes: Vec<Box<dyn SelectionNode>>,
}
impl AndSelectionNode {
pub fn new() -> AndSelectionNode {
return AndSelectionNode {
child_nodes: vec![],
};
}
}
impl SelectionNode for AndSelectionNode {
fn select(&self, event_record: &Value) -> bool {
return self.child_nodes.iter().all(|child_node| {
return child_node.as_ref().select(event_record);
});
}
}
////////// OrSelectionNode
struct OrSelectionNode {
pub child_nodes: Vec<Box<dyn SelectionNode>>,
}
impl OrSelectionNode {
pub fn new() -> OrSelectionNode {
return OrSelectionNode {
child_nodes: vec![],
};
}
}
impl SelectionNode for OrSelectionNode {
fn select(&self, event_record: &Value) -> bool {
return self.child_nodes.iter().any(|child_node| {
return child_node.as_ref().select(event_record);
});
}
}
////////////// Field Selection Node
struct FieldSelectionNode {
key_list: Vec<String>,
select_value: Yaml,
}
impl FieldSelectionNode {
fn new(key_list: Vec<String>, value_yaml: Yaml) -> FieldSelectionNode {
return FieldSelectionNode {
key_list: key_list,
select_value: value_yaml,
};
}
// 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);
}
// TODO Matcherのインスタンスが都度生成されないようにする。
fn get_matchers(&self) -> Vec<Box<dyn FieldSelectionMatcher>> {
return vec![Box::new(ValueMatcher {})];
}
}
impl SelectionNode for FieldSelectionNode {
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() {
return false;
}
let event_value = self.get_event_value(event_record);
return matcher
.unwrap()
.is_match(&self.key_list, &self.select_value, event_value);
}
}
trait FieldSelectionMatcher {
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;
}
struct ValueMatcher {}
impl FieldSelectionMatcher for ValueMatcher {
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;
}
}

View File

@@ -8,12 +8,12 @@ use std::path::{Path, PathBuf};
use yaml_rust::YamlLoader;
pub struct ParseYaml {
pub rules: Vec<yaml_rust::Yaml>,
pub files: Vec<yaml_rust::Yaml>,
}
impl ParseYaml {
pub fn new() -> ParseYaml {
ParseYaml { rules: Vec::new() }
ParseYaml { files: Vec::new() }
}
pub fn read_file(&self, path: PathBuf) -> Result<String, String> {
@@ -39,7 +39,7 @@ impl ParseYaml {
let docs = YamlLoader::load_from_str(&s).unwrap();
for i in docs {
if i["enabled"].as_bool().unwrap() {
&self.rules.push(i);
&self.files.push(i);
}
}
}
@@ -64,7 +64,7 @@ mod tests {
fn test_read_yaml() {
let mut yaml = yaml::ParseYaml::new();
&yaml.read_dir("test_files/rules/yaml/".to_string());
for rule in yaml.rules {
for rule in yaml.files {
if rule["title"].as_str().unwrap() == "Sysmon Check command lines" {
assert_eq!(
"*",