#102実装しました。 (#133)
This commit is contained in:
@@ -2,7 +2,7 @@ extern crate csv;
|
||||
|
||||
use crate::detections::rule::AggResult;
|
||||
use serde_json::Value;
|
||||
use tokio::spawn;
|
||||
use tokio::{runtime::Runtime, spawn, task::JoinHandle};
|
||||
|
||||
use crate::detections::print::MESSAGES;
|
||||
use crate::detections::rule;
|
||||
@@ -10,7 +10,7 @@ use crate::detections::rule::RuleNode;
|
||||
use crate::detections::{print::AlertMessage, utils};
|
||||
use crate::yaml::ParseYaml;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, usize};
|
||||
|
||||
const DIRPATH_RULES: &str = "rules";
|
||||
|
||||
@@ -32,26 +32,21 @@ impl EvtxRecordInfo {
|
||||
|
||||
// TODO テストケースかかなきゃ...
|
||||
#[derive(Debug)]
|
||||
pub struct Detection {}
|
||||
pub struct Detection {
|
||||
rules: Vec<RuleNode>,
|
||||
}
|
||||
|
||||
impl Detection {
|
||||
pub fn new() -> Detection {
|
||||
return Detection {};
|
||||
pub fn new(rules: Vec<RuleNode>) -> Detection {
|
||||
return Detection { rules: rules };
|
||||
}
|
||||
|
||||
pub fn start(&mut self, records: Vec<EvtxRecordInfo>) {
|
||||
let rules = self.parse_rule_files();
|
||||
if rules.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let tokio_rt = utils::create_tokio_runtime();
|
||||
tokio_rt.block_on(Detection::execute_rules(rules, records));
|
||||
tokio_rt.shutdown_background();
|
||||
pub fn start(self, rt: &Runtime, records: Vec<EvtxRecordInfo>) -> Self {
|
||||
return rt.block_on(self.execute_rules(records));
|
||||
}
|
||||
|
||||
// ルールファイルをパースします。
|
||||
fn parse_rule_files(&self) -> Vec<RuleNode> {
|
||||
pub fn parse_rule_files() -> Vec<RuleNode> {
|
||||
// ルールファイルのパースを実行
|
||||
let mut rulefile_loader = ParseYaml::new();
|
||||
let resutl_readdir = rulefile_loader.read_dir(DIRPATH_RULES);
|
||||
@@ -81,7 +76,7 @@ impl Detection {
|
||||
err_msgs.iter().for_each(|err_msg| {
|
||||
AlertMessage::alert(&mut stdout, err_msg.to_string()).ok();
|
||||
});
|
||||
println!("");
|
||||
println!(""); // 一行開けるためのprintln
|
||||
});
|
||||
return Option::None;
|
||||
};
|
||||
@@ -95,25 +90,52 @@ impl Detection {
|
||||
.collect();
|
||||
}
|
||||
|
||||
async fn execute_rules(rules: Vec<RuleNode>, records: Vec<EvtxRecordInfo>) {
|
||||
// 複数のイベントレコードに対して、複数のルールを1個実行します。
|
||||
async fn execute_rules(mut self, records: Vec<EvtxRecordInfo>) -> Self {
|
||||
let records_arc = Arc::new(records);
|
||||
let traiter = rules.into_iter();
|
||||
// 各rule毎にスレッドを作成して、スレッドを起動する。
|
||||
let handles = traiter.map(|rule| {
|
||||
// // 各rule毎にスレッドを作成して、スレッドを起動する。
|
||||
let rules = self.rules;
|
||||
let handles: Vec<JoinHandle<RuleNode>> = rules
|
||||
.into_iter()
|
||||
.map(|rule| {
|
||||
let records_cloned = Arc::clone(&records_arc);
|
||||
return spawn(async move {
|
||||
Detection::execute_rule(rule, records_cloned);
|
||||
});
|
||||
let moved_rule = Detection::execute_rule(rule, records_cloned);
|
||||
return moved_rule;
|
||||
});
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 全スレッドの実行完了を待機
|
||||
let mut rules = vec![];
|
||||
for handle in handles {
|
||||
handle.await.unwrap();
|
||||
let ret_rule = handle.await.unwrap();
|
||||
rules.push(ret_rule);
|
||||
}
|
||||
|
||||
// この関数の先頭でrules.into_iter()を呼び出している。それにより所有権がmapのruleを経由し、execute_ruleの引数に渡しているruleに移っているので、self.rulesには所有権が無くなっている。
|
||||
// 所有権を失ったメンバー変数を持つオブジェクトをreturnするコードを書くと、コンパイラが怒になるので(E0382という番号のコンパイルエラー)、ここでself.rulesに所有権を戻している。
|
||||
// self.rulesが再度所有権を取り戻せるように、Detection::execute_ruleで引数に渡したruleを戻り値として返すようにしている。
|
||||
self.rules = rules;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn add_aggcondtion_msg(&self) {
|
||||
for rule in &self.rules {
|
||||
if !rule.has_agg_condition() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let agg_results = rule.judge_satisfy_aggcondition();
|
||||
for value in agg_results {
|
||||
Detection::insert_agg_message(rule, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 検知ロジックを実行します。
|
||||
fn execute_rule(mut rule: RuleNode, records: Arc<Vec<EvtxRecordInfo>>) {
|
||||
// 複数のイベントレコードに対して、ルールを1個実行します。
|
||||
fn execute_rule(mut rule: RuleNode, records: Arc<Vec<EvtxRecordInfo>>) -> RuleNode {
|
||||
let records = &*records;
|
||||
let agg_condition = rule.has_agg_condition();
|
||||
for record_info in records {
|
||||
@@ -124,16 +146,10 @@ impl Detection {
|
||||
// aggregation conditionが存在しない場合はそのまま出力対応を行う
|
||||
if !agg_condition {
|
||||
Detection::insert_message(&rule, &record_info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let agg_results = rule.judge_satisfy_aggcondition();
|
||||
for value in agg_results {
|
||||
if agg_condition {
|
||||
Detection::insert_agg_message(&rule, value);
|
||||
}
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
/// 条件に合致したレコードを表示するための関数
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::detections::print::Message;
|
||||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
|
||||
use std::{collections::HashMap, sync::Arc, vec};
|
||||
use std::{collections::HashMap, fmt::Debug, sync::Arc, vec};
|
||||
|
||||
use serde_json::Value;
|
||||
use yaml_rust::Yaml;
|
||||
@@ -29,6 +29,12 @@ pub struct RuleNode {
|
||||
countdata: HashMap<String, HashMap<String, Vec<DateTime<Utc>>>>,
|
||||
}
|
||||
|
||||
impl Debug for RuleNode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return Result::Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for RuleNode {}
|
||||
|
||||
impl RuleNode {
|
||||
|
||||
158
src/main.rs
158
src/main.rs
@@ -1,12 +1,13 @@
|
||||
extern crate serde;
|
||||
extern crate serde_derive;
|
||||
|
||||
use evtx::{err, EvtxParser, ParserSettings, SerializedEvtxRecord};
|
||||
use evtx::{EvtxParser, ParserSettings};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::PathBuf,
|
||||
time::Instant,
|
||||
vec,
|
||||
};
|
||||
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;
|
||||
@@ -14,12 +15,15 @@ use yamato_event_analyzer::omikuji::Omikuji;
|
||||
use yamato_event_analyzer::{afterfact::after_fact, detections::utils};
|
||||
use yamato_event_analyzer::{detections::configs, timeline::timeline::Timeline};
|
||||
|
||||
// 一度にtimelineやdetectionを実行する行数
|
||||
const MAX_DETECT_RECORDS: usize = 40000;
|
||||
|
||||
fn main() {
|
||||
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
|
||||
detect_files(vec![PathBuf::from(filepath)]);
|
||||
analysis_files(vec![PathBuf::from(filepath)]);
|
||||
} else if let Some(directory) = configs::CONFIG.read().unwrap().args.value_of("directory") {
|
||||
let evtx_files = collect_evtxfiles(&directory);
|
||||
detect_files(evtx_files);
|
||||
analysis_files(evtx_files);
|
||||
} else if configs::CONFIG.read().unwrap().args.is_present("credits") {
|
||||
print_credits();
|
||||
}
|
||||
@@ -69,110 +73,92 @@ fn print_credits() {
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_files(evtx_files: Vec<PathBuf>) {
|
||||
let evnt_records = evtx_to_jsons(&evtx_files);
|
||||
|
||||
fn analysis_files(evtx_files: Vec<PathBuf>) {
|
||||
let mut tl = Timeline::new();
|
||||
tl.start(&evtx_files, &evnt_records);
|
||||
let mut detection = detection::Detection::new(detection::Detection::parse_rule_files());
|
||||
|
||||
let mut detection = detection::Detection::new();
|
||||
&detection.start(evnt_records);
|
||||
for evtx_file in evtx_files {
|
||||
let ret = analysis_file(evtx_file, tl, detection);
|
||||
tl = ret.0;
|
||||
detection = ret.1;
|
||||
}
|
||||
|
||||
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;
|
||||
// Windowsイベントログファイルを1ファイル分解析する。
|
||||
fn analysis_file(
|
||||
evtx_filepath: PathBuf,
|
||||
mut tl: Timeline,
|
||||
mut detection: detection::Detection,
|
||||
) -> (Timeline, detection::Detection) {
|
||||
let filepath_disp = evtx_filepath.display();
|
||||
let parser = evtx_to_jsons(evtx_filepath.clone());
|
||||
if parser.is_none() {
|
||||
return (tl, detection);
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut parser = parser.unwrap();
|
||||
let mut records = parser.records_json_value();
|
||||
let tokio_rt = utils::create_tokio_runtime();
|
||||
let ret = tokio_rt.block_on(evtx_to_json(evtx_parsers, &evtx_files));
|
||||
tokio_rt.shutdown_background();
|
||||
loop {
|
||||
let mut records_per_detect = vec![];
|
||||
while records_per_detect.len() < MAX_DETECT_RECORDS {
|
||||
// パースに失敗している場合、エラーメッセージを出力
|
||||
let next_rec = records.next();
|
||||
if next_rec.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
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 record_result = next_rec.unwrap();
|
||||
if record_result.is_err() {
|
||||
let evtx_filepath = &filepath_disp;
|
||||
let errmsg = format!(
|
||||
"Failed to parse event file. EventFile:{} Error:{}",
|
||||
evtx_filepath,
|
||||
future_result.unwrap_err()
|
||||
record_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 record_info =
|
||||
EvtxRecordInfo::new((&filepath_disp).to_string(), record_result.unwrap().data);
|
||||
records_per_detect.push(record_info);
|
||||
}
|
||||
if records_per_detect.len() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
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();
|
||||
// timeline機能の実行
|
||||
tl.start(&records_per_detect);
|
||||
|
||||
// ruleファイルの検知
|
||||
detection = detection.start(&tokio_rt, records_per_detect);
|
||||
}
|
||||
|
||||
tokio_rt.shutdown_background();
|
||||
detection.add_aggcondtion_msg();
|
||||
|
||||
return (tl, detection);
|
||||
}
|
||||
|
||||
fn evtx_to_jsons(evtx_filepath: PathBuf) -> Option<EvtxParser<File>> {
|
||||
match EvtxParser::from_path(evtx_filepath) {
|
||||
Ok(evtx_parser) => {
|
||||
// parserのデフォルト設定を変更
|
||||
let mut parse_config = ParserSettings::default();
|
||||
parse_config = parse_config.separate_json_attributes(true); // XMLのattributeをJSONに変換する時のルールを設定
|
||||
parse_config = parse_config.num_threads(utils::get_thread_num()); // 設定しないと遅かったので、設定しておく。
|
||||
|
||||
let evtx_parser = evtx_parser.with_configuration(parse_config);
|
||||
return Option::Some(evtx_parser);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
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) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::detections::{configs, detection::EvtxRecordInfo};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -20,11 +18,7 @@ impl EventStatistics {
|
||||
// recordからEventIDを取得するには、detection::utils::get_event_value()という関数があるので、それを使うと便利かもしれません。
|
||||
|
||||
// 現状では、この関数の戻り値として返すVec<String>を表示するコードは実装していません。
|
||||
pub fn start(
|
||||
&mut self,
|
||||
evtx_files: &Vec<PathBuf>,
|
||||
records: &Vec<EvtxRecordInfo>,
|
||||
) -> Vec<String> {
|
||||
pub fn start(&mut self, _records: &Vec<EvtxRecordInfo>) -> Vec<String> {
|
||||
// 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。
|
||||
if !configs::CONFIG
|
||||
.read()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::detections::detection::EvtxRecordInfo;
|
||||
|
||||
use super::statistics::EventStatistics;
|
||||
@@ -12,8 +10,8 @@ impl Timeline {
|
||||
return Timeline {};
|
||||
}
|
||||
|
||||
pub fn start(&mut self, evtx_files: &Vec<PathBuf>, records: &Vec<EvtxRecordInfo>) {
|
||||
pub fn start(&mut self, records: &Vec<EvtxRecordInfo>) {
|
||||
let mut statistic = EventStatistics::new();
|
||||
statistic.start(evtx_files, records);
|
||||
statistic.start(records);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user