Feature/timeline template #104 (#106)

* under constructing

* add statistics template

* fix

* add comment

* change for statistics
This commit is contained in:
James
2021-05-16 01:34:48 +09:00
committed by GitHub
parent 99b640adaa
commit 9b8bed70f8
8 changed files with 245 additions and 199 deletions

View File

@@ -1,54 +1,49 @@
extern crate csv; extern crate csv;
use serde_json::Value;
use tokio::{spawn, task::JoinHandle};
use crate::detections::print::MESSAGES; use crate::detections::print::MESSAGES;
use crate::detections::rule; use crate::detections::rule;
use crate::detections::rule::RuleNode; use crate::detections::rule::RuleNode;
use crate::detections::{print::AlertMessage, utils}; use crate::detections::{print::AlertMessage, utils};
use crate::yaml::ParseYaml; use crate::yaml::ParseYaml;
use evtx::err; use std::sync::Arc;
use evtx::{EvtxParser, ParserSettings, SerializedEvtxRecord};
use serde_json::Value;
use tokio::{spawn, task::JoinHandle};
use std::{collections::HashSet, path::PathBuf};
use std::{fs::File, sync::Arc};
const DIRPATH_RULES: &str = "rules"; const DIRPATH_RULES: &str = "rules";
// イベントファイルの1レコード分の情報を保持する構造体 // イベントファイルの1レコード分の情報を保持する構造体
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EvtxRecordInfo { pub struct EvtxRecordInfo {
evtx_filepath: String, // イベントファイルのファイルパス ログで出力するときに使う pub evtx_filepath: String, // イベントファイルのファイルパス ログで出力するときに使う
record: Value, // 1レコード分のデータをJSON形式にシリアライズしたもの pub record: Value, // 1レコード分のデータをJSON形式にシリアライズしたもの
}
impl EvtxRecordInfo {
pub fn new(evtx_filepath: String, record: Value) -> EvtxRecordInfo {
return EvtxRecordInfo {
evtx_filepath: evtx_filepath,
record: record,
};
}
} }
// TODO テストケースかかなきゃ... // TODO テストケースかかなきゃ...
#[derive(Debug)] #[derive(Debug)]
pub struct Detection { pub struct Detection {}
parseinfos: Vec<EvtxRecordInfo>,
}
impl Detection { impl Detection {
pub fn new() -> Detection { pub fn new() -> Detection {
let initializer: Vec<EvtxRecordInfo> = Vec::new(); return Detection {};
Detection {
parseinfos: initializer,
}
} }
pub fn start(&mut self, evtx_files: Vec<PathBuf>) { pub fn start(&mut self, records: Vec<EvtxRecordInfo>) {
if evtx_files.is_empty() {
return;
}
let rules = self.parse_rule_files(); let rules = self.parse_rule_files();
if rules.is_empty() { if rules.is_empty() {
return; return;
} }
let records = self.evtx_to_jsons(&evtx_files, &rules);
let tokio_rt = utils::create_tokio_runtime(); let tokio_rt = utils::create_tokio_runtime();
tokio_rt.block_on(self.execute_rule(rules, records)); tokio_rt.block_on(self.execute_rule(rules, records));
tokio_rt.shutdown_background(); tokio_rt.shutdown_background();
@@ -99,124 +94,6 @@ impl Detection {
.collect(); .collect();
} }
// evtxファイルをjsonに変換します。
fn evtx_to_jsons(
&mut self,
evtx_files: &Vec<PathBuf>,
rules: &Vec<RuleNode>,
) -> Vec<EvtxRecordInfo> {
// EvtxParserを生成する。
let evtx_parsers: Vec<EvtxParser<File>> = evtx_files
.clone()
.into_iter()
.filter_map(|evtx_file| {
// convert to evtx parser
// println!("PathBuf:{}", evtx_file.display());
match EvtxParser::from_path(evtx_file) {
Ok(parser) => Option::Some(parser),
Err(e) => {
eprintln!("{}", e);
return Option::None;
}
}
})
.collect();
let tokio_rt = utils::create_tokio_runtime();
let ret = tokio_rt.block_on(self.evtx_to_json(evtx_parsers, &evtx_files, rules));
tokio_rt.shutdown_background();
return ret;
}
// evtxファイルからEvtxRecordInfoを生成する。
// 戻り値は「どのイベントファイルから生成されたXMLかを示すindex」と「変換されたXML」のタプルです。
// タプルのindexは、引数で指定されるevtx_filesのindexに対応しています。
async fn evtx_to_json(
&mut self,
evtx_parsers: Vec<EvtxParser<File>>,
evtx_files: &Vec<PathBuf>,
rules: &Vec<RuleNode>,
) -> Vec<EvtxRecordInfo> {
// evtx_parser.records_json()でevtxをxmlに変換するJobを作成
let handles: Vec<JoinHandle<Vec<err::Result<SerializedEvtxRecord<serde_json::Value>>>>> =
evtx_parsers
.into_iter()
.map(|mut evtx_parser| {
return spawn(async move {
let mut parse_config = ParserSettings::default();
parse_config = parse_config.separate_json_attributes(true);
parse_config = parse_config.num_threads(utils::get_thread_num());
evtx_parser = evtx_parser.with_configuration(parse_config);
let values = evtx_parser.records_json_value().collect();
return values;
});
})
.collect();
// 作成したjobを実行し(handle.awaitの部分)、スレッドの実行時にエラーが発生した場合、標準エラー出力に出しておく
let mut ret = vec![];
for (parser_idx, handle) in handles.into_iter().enumerate() {
let future_result = handle.await;
if future_result.is_err() {
let evtx_filepath = &evtx_files[parser_idx].display();
let errmsg = format!(
"Failed to parse event file. EventFile:{} Error:{}",
evtx_filepath,
future_result.unwrap_err()
);
AlertMessage::alert(&mut std::io::stdout().lock(), errmsg).ok();
continue;
}
future_result.unwrap().into_iter().for_each(|parse_result| {
ret.push((parser_idx, parse_result));
});
}
let event_id_set = Detection::get_event_ids(rules);
return ret
.into_iter()
.filter_map(|(parser_idx, parse_result)| {
// パースに失敗している場合、エラーメッセージを出力
if parse_result.is_err() {
let evtx_filepath = &evtx_files[parser_idx].display();
let errmsg = format!(
"Failed to parse event file. EventFile:{} Error:{}",
evtx_filepath,
parse_result.unwrap_err()
);
AlertMessage::alert(&mut std::io::stdout().lock(), errmsg).ok();
return Option::None;
}
// ルールファイルに記載されていないEventIDのレコードは絶対に検知しないので無視する。
let record_json = parse_result.unwrap().data;
let event_id_opt = utils::get_event_value(&utils::get_event_id_key(), &record_json);
let is_exit_eventid = event_id_opt
.and_then(|event_id| event_id.as_i64())
.and_then(|event_id| {
if event_id_set.contains(&event_id) {
return Option::Some(&record_json);
} else {
return Option::None;
}
});
if is_exit_eventid.is_none() {
return Option::None;
}
let evtx_filepath = evtx_files[parser_idx].display().to_string();
let record_info = EvtxRecordInfo {
evtx_filepath: evtx_filepath,
record: record_json,
};
return Option::Some(record_info);
})
.collect();
}
// 検知ロジックを実行します。 // 検知ロジックを実行します。
async fn execute_rule(&mut self, rules: Vec<RuleNode>, records: Vec<EvtxRecordInfo>) { async fn execute_rule(&mut self, rules: Vec<RuleNode>, records: Vec<EvtxRecordInfo>) {
// 複数スレッドで所有権を共有するため、recordsをArcでwwap // 複数スレッドで所有権を共有するため、recordsをArcでwwap
@@ -270,13 +147,13 @@ impl Detection {
} }
} }
fn get_event_ids(rules: &Vec<RuleNode>) -> HashSet<i64> { // fn get_event_ids(rules: &Vec<RuleNode>) -> HashSet<i64> {
return rules // return rules
.iter() // .iter()
.map(|rule| rule.get_event_ids()) // .map(|rule| rule.get_event_ids())
.flatten() // .flatten()
.collect(); // .collect();
} // }
// 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作 // 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作
fn chunks<T>(ary: Vec<T>, size: usize) -> Vec<Vec<T>> { fn chunks<T>(ary: Vec<T>, size: usize) -> Vec<Vec<T>> {

View File

@@ -2,4 +2,4 @@ pub mod configs;
pub mod detection; pub mod detection;
pub mod print; pub mod print;
mod rule; mod rule;
mod utils; pub mod utils;

View File

@@ -126,40 +126,40 @@ impl RuleNode {
return selection.unwrap().select(event_record); return selection.unwrap().select(event_record);
} }
pub fn get_event_ids(&self) -> Vec<i64> { // pub fn get_event_ids(&self) -> Vec<i64> {
let selection = self // let selection = self
.detection // .detection
.as_ref() // .as_ref()
.and_then(|detection| detection.selection.as_ref()); // .and_then(|detection| detection.selection.as_ref());
if selection.is_none() { // if selection.is_none() {
return vec![]; // return vec![];
} // }
return selection // return selection
.unwrap() // .unwrap()
.get_descendants() // .get_descendants()
.iter() // .iter()
.filter_map(|node| return node.downcast_ref::<LeafSelectionNode>()) // mopaというライブラリを使うと簡単にダウンキャストできるらしいです。https://crates.io/crates/mopa // .filter_map(|node| return node.downcast_ref::<LeafSelectionNode>()) // mopaというライブラリを使うと簡単にダウンキャストできるらしいです。https://crates.io/crates/mopa
.filter(|node| { // .filter(|node| {
// キーがEventIDのードである // // キーがEventIDのードである
let key = utils::get_event_id_key(); // let key = utils::get_event_id_key();
if node.get_key() == key { // if node.get_key() == key {
return true; // return true;
} // }
// EventIDのAliasに一致しているかどうか // // EventIDのAliasに一致しているかどうか
let alias = utils::get_alias(&key); // let alias = utils::get_alias(&key);
if alias.is_none() { // if alias.is_none() {
return false; // return false;
} else { // } else {
return node.get_key() == alias.unwrap(); // return node.get_key() == alias.unwrap();
} // }
}) // })
.filter_map(|node| { // .filter_map(|node| {
return node.select_value.as_i64(); // return node.select_value.as_i64();
}) // })
.collect(); // .collect();
} // }
} }
// Ruleファイルのdetectionを表すード // Ruleファイルのdetectionを表すード
@@ -980,20 +980,20 @@ mod tests {
} }
} }
#[test] // #[test]
fn test_get_event_ids() { // fn test_get_event_ids() {
let rule_str = r#" // let rule_str = r#"
enabled: true // enabled: true
detection: // detection:
selection: // selection:
EventID: 1234 // EventID: 1234
output: 'command=%CommandLine%' // output: 'command=%CommandLine%'
"#; // "#;
let rule_node = parse_rule_from_str(rule_str); // let rule_node = parse_rule_from_str(rule_str);
let event_ids = rule_node.get_event_ids(); // let event_ids = rule_node.get_event_ids();
assert_eq!(event_ids.len(), 1); // assert_eq!(event_ids.len(), 1);
assert_eq!(event_ids[0], 1234); // assert_eq!(event_ids[0], 1234);
} // }
#[test] #[test]
fn test_notdetect_regex_eventid() { fn test_notdetect_regex_eventid() {

View File

@@ -1,4 +1,5 @@
pub mod afterfact; pub mod afterfact;
pub mod detections; pub mod detections;
pub mod omikuji; pub mod omikuji;
pub mod timeline;
pub mod yaml; pub mod yaml;

View File

@@ -1,12 +1,18 @@
extern crate serde; extern crate serde;
extern crate serde_derive; extern crate serde_derive;
use std::{fs, path::PathBuf}; use evtx::{err, EvtxParser, ParserSettings, SerializedEvtxRecord};
use yamato_event_analyzer::afterfact::after_fact; use std::{
use yamato_event_analyzer::detections::configs; fs::{self, File},
path::PathBuf,
};
use tokio::{spawn, task::JoinHandle};
use yamato_event_analyzer::detections::detection; use yamato_event_analyzer::detections::detection;
use yamato_event_analyzer::detections::detection::EvtxRecordInfo;
use yamato_event_analyzer::detections::print::AlertMessage; use yamato_event_analyzer::detections::print::AlertMessage;
use yamato_event_analyzer::omikuji::Omikuji; use yamato_event_analyzer::omikuji::Omikuji;
use yamato_event_analyzer::{afterfact::after_fact, detections::utils};
use yamato_event_analyzer::{detections::configs, timeline::timeline::Timeline};
fn main() { fn main() {
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") { if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
@@ -64,12 +70,111 @@ fn print_credits() {
} }
fn detect_files(evtx_files: Vec<PathBuf>) { fn detect_files(evtx_files: Vec<PathBuf>) {
let evnt_records = evtx_to_jsons(&evtx_files);
let mut tl = Timeline::new();
tl.start(&evtx_files, &evnt_records);
let mut detection = detection::Detection::new(); let mut detection = detection::Detection::new();
&detection.start(evtx_files); &detection.start(evnt_records);
after_fact(); after_fact();
} }
// evtxファイルをjsonに変換します。
fn evtx_to_jsons(evtx_files: &Vec<PathBuf>) -> Vec<EvtxRecordInfo> {
// EvtxParserを生成する。
let evtx_parsers: Vec<EvtxParser<File>> = evtx_files
.clone()
.into_iter()
.filter_map(|evtx_file| {
// convert to evtx parser
// println!("PathBuf:{}", evtx_file.display());
match EvtxParser::from_path(evtx_file) {
Ok(parser) => Option::Some(parser),
Err(e) => {
eprintln!("{}", e);
return Option::None;
}
}
})
.collect();
let tokio_rt = utils::create_tokio_runtime();
let ret = tokio_rt.block_on(evtx_to_json(evtx_parsers, &evtx_files));
tokio_rt.shutdown_background();
return ret;
}
// evtxファイルからEvtxRecordInfoを生成する。
// 戻り値は「どのイベントファイルから生成されたXMLかを示すindex」と「変換されたXML」のタプルです。
// タプルのindexは、引数で指定されるevtx_filesのindexに対応しています。
async fn evtx_to_json(
evtx_parsers: Vec<EvtxParser<File>>,
evtx_files: &Vec<PathBuf>,
) -> Vec<EvtxRecordInfo> {
// evtx_parser.records_json()でevtxをxmlに変換するJobを作成
let handles: Vec<JoinHandle<Vec<err::Result<SerializedEvtxRecord<serde_json::Value>>>>> =
evtx_parsers
.into_iter()
.map(|mut evtx_parser| {
return spawn(async move {
let mut parse_config = ParserSettings::default();
parse_config = parse_config.separate_json_attributes(true);
parse_config = parse_config.num_threads(utils::get_thread_num());
evtx_parser = evtx_parser.with_configuration(parse_config);
let values = evtx_parser.records_json_value().collect();
return values;
});
})
.collect();
// 作成したjobを実行し(handle.awaitの部分)、スレッドの実行時にエラーが発生した場合、標準エラー出力に出しておく
let mut ret = vec![];
for (parser_idx, handle) in handles.into_iter().enumerate() {
let future_result = handle.await;
if future_result.is_err() {
let evtx_filepath = &evtx_files[parser_idx].display();
let errmsg = format!(
"Failed to parse event file. EventFile:{} Error:{}",
evtx_filepath,
future_result.unwrap_err()
);
AlertMessage::alert(&mut std::io::stdout().lock(), errmsg).ok();
continue;
}
future_result.unwrap().into_iter().for_each(|parse_result| {
ret.push((parser_idx, parse_result));
});
}
return ret
.into_iter()
.filter_map(|(parser_idx, parse_result)| {
// パースに失敗している場合、エラーメッセージを出力
if parse_result.is_err() {
let evtx_filepath = &evtx_files[parser_idx].display();
let errmsg = format!(
"Failed to parse event file. EventFile:{} Error:{}",
evtx_filepath,
parse_result.unwrap_err()
);
AlertMessage::alert(&mut std::io::stdout().lock(), errmsg).ok();
return Option::None;
}
let record_info = EvtxRecordInfo::new(
evtx_files[parser_idx].display().to_string(),
parse_result.unwrap().data,
);
return Option::Some(record_info);
})
.collect();
}
fn _output_with_omikuji(omikuji: Omikuji) { fn _output_with_omikuji(omikuji: Omikuji) {
let fp = &format!("art/omikuji/{}", omikuji); let fp = &format!("art/omikuji/{}", omikuji);
let content = fs::read_to_string(fp).unwrap(); let content = fs::read_to_string(fp).unwrap();

2
src/timeline/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod statistics;
pub mod timeline;

View File

@@ -0,0 +1,42 @@
use std::path::PathBuf;
use crate::detections::{configs, detection::EvtxRecordInfo};
#[derive(Debug)]
pub struct EventStatistics {}
/**
* Windows Event Logの統計情報を出力する
*/
impl EventStatistics {
pub fn new() -> EventStatistics {
return EventStatistics {};
}
// この関数の戻り値として、コンソールに出力する内容をStringの可変配列(Vec)として返却してください。
// 可変配列にしているのは改行を表すためで、可変配列にコンソールに出力する内容を1行ずつ追加してください。
// 引数の_recordsが読み込んだWindowsイベントログのを表す、EvtxRecordInfo構造体の配列になっています。
// EvtxRecordInfo構造体の pub record: Value というメンバーがいて、それがWindowsイベントログの1レコード分を表していますので、
// EvtxRecordInfo構造体のrecordから、EventIDとか統計情報を取得するようにしてください。
// recordからEventIDを取得するには、detection::utils::get_event_value()という関数があるので、それを使うと便利かもしれません。
// 現状では、この関数の戻り値として返すVec<String>を表示するコードは実装していません。
pub fn start(
&mut self,
evtx_files: &Vec<PathBuf>,
records: &Vec<EvtxRecordInfo>,
) -> Vec<String> {
// 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。
if !configs::CONFIG
.read()
.unwrap()
.args
.is_present("statistics")
{
return vec![];
}
// TODO ここから下を書いて欲しいです。
return vec![];
}
}

19
src/timeline/timeline.rs Normal file
View File

@@ -0,0 +1,19 @@
use std::path::PathBuf;
use crate::detections::detection::EvtxRecordInfo;
use super::statistics::EventStatistics;
#[derive(Debug)]
pub struct Timeline {}
impl Timeline {
pub fn new() -> Timeline {
return Timeline {};
}
pub fn start(&mut self, evtx_files: &Vec<PathBuf>, records: &Vec<EvtxRecordInfo>) {
let mut statistic = EventStatistics::new();
statistic.start(evtx_files, records);
}
}