* under constructing * add statistics template * fix * add comment * change for statistics
This commit is contained in:
@@ -1,54 +1,49 @@
|
||||
extern crate csv;
|
||||
|
||||
use serde_json::Value;
|
||||
use tokio::{spawn, task::JoinHandle};
|
||||
|
||||
use crate::detections::print::MESSAGES;
|
||||
use crate::detections::rule;
|
||||
use crate::detections::rule::RuleNode;
|
||||
use crate::detections::{print::AlertMessage, utils};
|
||||
use crate::yaml::ParseYaml;
|
||||
|
||||
use evtx::err;
|
||||
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};
|
||||
use std::sync::Arc;
|
||||
|
||||
const DIRPATH_RULES: &str = "rules";
|
||||
|
||||
// イベントファイルの1レコード分の情報を保持する構造体
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EvtxRecordInfo {
|
||||
evtx_filepath: String, // イベントファイルのファイルパス ログで出力するときに使う
|
||||
record: Value, // 1レコード分のデータをJSON形式にシリアライズしたもの
|
||||
pub evtx_filepath: String, // イベントファイルのファイルパス ログで出力するときに使う
|
||||
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 テストケースかかなきゃ...
|
||||
#[derive(Debug)]
|
||||
pub struct Detection {
|
||||
parseinfos: Vec<EvtxRecordInfo>,
|
||||
}
|
||||
pub struct Detection {}
|
||||
|
||||
impl Detection {
|
||||
pub fn new() -> Detection {
|
||||
let initializer: Vec<EvtxRecordInfo> = Vec::new();
|
||||
Detection {
|
||||
parseinfos: initializer,
|
||||
}
|
||||
return Detection {};
|
||||
}
|
||||
|
||||
pub fn start(&mut self, evtx_files: Vec<PathBuf>) {
|
||||
if evtx_files.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn start(&mut self, records: Vec<EvtxRecordInfo>) {
|
||||
let rules = self.parse_rule_files();
|
||||
if rules.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let records = self.evtx_to_jsons(&evtx_files, &rules);
|
||||
|
||||
let tokio_rt = utils::create_tokio_runtime();
|
||||
tokio_rt.block_on(self.execute_rule(rules, records));
|
||||
tokio_rt.shutdown_background();
|
||||
@@ -99,124 +94,6 @@ impl Detection {
|
||||
.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>) {
|
||||
// 複数スレッドで所有権を共有するため、recordsをArcでwwap
|
||||
@@ -270,13 +147,13 @@ impl Detection {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_event_ids(rules: &Vec<RuleNode>) -> HashSet<i64> {
|
||||
return rules
|
||||
.iter()
|
||||
.map(|rule| rule.get_event_ids())
|
||||
.flatten()
|
||||
.collect();
|
||||
}
|
||||
// fn get_event_ids(rules: &Vec<RuleNode>) -> HashSet<i64> {
|
||||
// return rules
|
||||
// .iter()
|
||||
// .map(|rule| rule.get_event_ids())
|
||||
// .flatten()
|
||||
// .collect();
|
||||
// }
|
||||
|
||||
// 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作
|
||||
fn chunks<T>(ary: Vec<T>, size: usize) -> Vec<Vec<T>> {
|
||||
|
||||
@@ -2,4 +2,4 @@ pub mod configs;
|
||||
pub mod detection;
|
||||
pub mod print;
|
||||
mod rule;
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
|
||||
@@ -126,40 +126,40 @@ impl RuleNode {
|
||||
return selection.unwrap().select(event_record);
|
||||
}
|
||||
|
||||
pub fn get_event_ids(&self) -> Vec<i64> {
|
||||
let selection = self
|
||||
.detection
|
||||
.as_ref()
|
||||
.and_then(|detection| detection.selection.as_ref());
|
||||
if selection.is_none() {
|
||||
return vec![];
|
||||
}
|
||||
// pub fn get_event_ids(&self) -> Vec<i64> {
|
||||
// let selection = self
|
||||
// .detection
|
||||
// .as_ref()
|
||||
// .and_then(|detection| detection.selection.as_ref());
|
||||
// if selection.is_none() {
|
||||
// return vec![];
|
||||
// }
|
||||
|
||||
return selection
|
||||
.unwrap()
|
||||
.get_descendants()
|
||||
.iter()
|
||||
.filter_map(|node| return node.downcast_ref::<LeafSelectionNode>()) // mopaというライブラリを使うと簡単にダウンキャストできるらしいです。https://crates.io/crates/mopa
|
||||
.filter(|node| {
|
||||
// キーがEventIDのノードである
|
||||
let key = utils::get_event_id_key();
|
||||
if node.get_key() == key {
|
||||
return true;
|
||||
}
|
||||
// return selection
|
||||
// .unwrap()
|
||||
// .get_descendants()
|
||||
// .iter()
|
||||
// .filter_map(|node| return node.downcast_ref::<LeafSelectionNode>()) // mopaというライブラリを使うと簡単にダウンキャストできるらしいです。https://crates.io/crates/mopa
|
||||
// .filter(|node| {
|
||||
// // キーがEventIDのノードである
|
||||
// let key = utils::get_event_id_key();
|
||||
// if node.get_key() == key {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// EventIDのAliasに一致しているかどうか
|
||||
let alias = utils::get_alias(&key);
|
||||
if alias.is_none() {
|
||||
return false;
|
||||
} else {
|
||||
return node.get_key() == alias.unwrap();
|
||||
}
|
||||
})
|
||||
.filter_map(|node| {
|
||||
return node.select_value.as_i64();
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
// // EventIDのAliasに一致しているかどうか
|
||||
// let alias = utils::get_alias(&key);
|
||||
// if alias.is_none() {
|
||||
// return false;
|
||||
// } else {
|
||||
// return node.get_key() == alias.unwrap();
|
||||
// }
|
||||
// })
|
||||
// .filter_map(|node| {
|
||||
// return node.select_value.as_i64();
|
||||
// })
|
||||
// .collect();
|
||||
// }
|
||||
}
|
||||
|
||||
// Ruleファイルのdetectionを表すノード
|
||||
@@ -980,20 +980,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_event_ids() {
|
||||
let rule_str = r#"
|
||||
enabled: true
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1234
|
||||
output: 'command=%CommandLine%'
|
||||
"#;
|
||||
let rule_node = parse_rule_from_str(rule_str);
|
||||
let event_ids = rule_node.get_event_ids();
|
||||
assert_eq!(event_ids.len(), 1);
|
||||
assert_eq!(event_ids[0], 1234);
|
||||
}
|
||||
// #[test]
|
||||
// fn test_get_event_ids() {
|
||||
// let rule_str = r#"
|
||||
// enabled: true
|
||||
// detection:
|
||||
// selection:
|
||||
// EventID: 1234
|
||||
// output: 'command=%CommandLine%'
|
||||
// "#;
|
||||
// let rule_node = parse_rule_from_str(rule_str);
|
||||
// let event_ids = rule_node.get_event_ids();
|
||||
// assert_eq!(event_ids.len(), 1);
|
||||
// assert_eq!(event_ids[0], 1234);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_notdetect_regex_eventid() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod afterfact;
|
||||
pub mod detections;
|
||||
pub mod omikuji;
|
||||
pub mod timeline;
|
||||
pub mod yaml;
|
||||
|
||||
113
src/main.rs
113
src/main.rs
@@ -1,12 +1,18 @@
|
||||
extern crate serde;
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::{fs, path::PathBuf};
|
||||
use yamato_event_analyzer::afterfact::after_fact;
|
||||
use yamato_event_analyzer::detections::configs;
|
||||
use evtx::{err, EvtxParser, ParserSettings, SerializedEvtxRecord};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::PathBuf,
|
||||
};
|
||||
use tokio::{spawn, task::JoinHandle};
|
||||
use yamato_event_analyzer::detections::detection;
|
||||
use yamato_event_analyzer::detections::detection::EvtxRecordInfo;
|
||||
use yamato_event_analyzer::detections::print::AlertMessage;
|
||||
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() {
|
||||
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>) {
|
||||
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();
|
||||
&detection.start(evtx_files);
|
||||
&detection.start(evnt_records);
|
||||
|
||||
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) {
|
||||
let fp = &format!("art/omikuji/{}", omikuji);
|
||||
let content = fs::read_to_string(fp).unwrap();
|
||||
|
||||
2
src/timeline/mod.rs
Normal file
2
src/timeline/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod statistics;
|
||||
pub mod timeline;
|
||||
42
src/timeline/statistics.rs
Normal file
42
src/timeline/statistics.rs
Normal 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
19
src/timeline/timeline.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user