Feature/event stats#105 (#137)

Event集計機能実装

Co-authored-by: HajimeTakai <takai.wa.hajime@gmail.com>
This commit is contained in:
garigariganzy
2021-09-20 23:53:45 +09:00
committed by GitHub
parent 403844ae45
commit 76103d31f3
8 changed files with 319 additions and 36 deletions
+71
View File
@@ -11,6 +11,7 @@ lazy_static! {
pub struct ConfigReader {
pub args: ArgMatches<'static>,
pub event_key_alias_config: EventKeyAliasConfig,
pub event_timeline_config: EventInfoConfig,
}
impl ConfigReader {
@@ -18,6 +19,7 @@ impl ConfigReader {
ConfigReader {
args: build_app(),
event_key_alias_config: load_eventkey_alias("config/eventkey_alias.txt"),
event_timeline_config: load_eventcode_info("config/timeline_event_info.txt"),
}
}
}
@@ -107,7 +109,76 @@ fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig {
.key_to_eventkey
.insert(alias.to_owned(), event_key.to_owned());
});
return config;
}
#[derive(Debug, Clone)]
pub struct EventInfo {
pub evttitle: String,
pub detectflg: String,
pub comment: String,
}
impl EventInfo {
pub fn new() -> EventInfo {
let evttitle = "Unknown".to_string();
let detectflg = "".to_string();
let comment = "".to_string();
return EventInfo {
evttitle,
detectflg,
comment,
};
}
}
#[derive(Debug, Clone)]
pub struct EventInfoConfig {
eventinfo: HashMap<String, EventInfo>,
}
impl EventInfoConfig {
pub fn new() -> EventInfoConfig {
return EventInfoConfig {
eventinfo: HashMap::new(),
};
}
pub fn get_event_id(&self, eventid: &String) -> Option<&EventInfo> {
return self.eventinfo.get(eventid);
}
pub fn get_event_info(&self) -> Vec<(&String, &EventInfo)> {
return self.eventinfo.iter().map(|e| e).collect();
}
// pub fn get_event_key_values(&self) -> Vec<(&String, &String)> {
// return self.timeline_eventcode_info.iter().map(|e| e).collect();
// }
}
fn load_eventcode_info(path: &str) -> EventInfoConfig {
let mut infodata = EventInfo::new();
let mut config = EventInfoConfig::new();
let read_result = utils::read_csv(path);
// timeline_event_infoが読み込めなかったらエラーで終了とする。
read_result.unwrap().into_iter().for_each(|line| {
if line.len() != 4 {
return;
}
let empty = &"".to_string();
let eventcode = line.get(0).unwrap_or(empty);
let event_title = line.get(1).unwrap_or(empty);
let detect_flg = line.get(2).unwrap_or(empty);
let comment = line.get(3).unwrap_or(empty);
infodata = EventInfo {
evttitle: event_title.to_string(),
detectflg: detect_flg.to_string(),
comment: comment.to_string(),
};
config
.eventinfo
.insert(eventcode.to_owned(), infodata.to_owned());
});
return config;
}
+2 -2
View File
@@ -4,13 +4,13 @@ use crate::detections::rule::AggResult;
use serde_json::Value;
use tokio::{runtime::Runtime, spawn, task::JoinHandle};
use crate::detections::print::AlertMessage;
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 std::{sync::Arc, usize};
use std::sync::Arc;
const DIRPATH_RULES: &str = "rules";
+1 -1
View File
@@ -532,7 +532,7 @@ mod tests {
creation_date: 2020/11/8
updated_date: 2020/11/8
"#;
let mut rule_node = parse_rule_from_str(rule_str);
let rule_node = parse_rule_from_str(rule_str);
let selection_node = &rule_node.detection.unwrap().name_to_selection["selection"];
// Root
+3 -3
View File
@@ -1,7 +1,7 @@
extern crate regex;
use crate::detections::print::Message;
use chrono::{DateTime, TimeZone, Utc};
use chrono::{DateTime, Utc};
use std::{collections::HashMap, fmt::Debug, sync::Arc, vec};
@@ -30,7 +30,7 @@ pub struct RuleNode {
}
impl Debug for RuleNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return Result::Ok(());
}
}
@@ -906,7 +906,7 @@ mod tests {
}
/// countで対象の数値確認を行うためのテスト用関数
fn check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) {
fn _check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) {
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test = rule_yaml.next().unwrap();
let mut rule_node = create_rule(test);
+8 -9
View File
@@ -5,7 +5,6 @@ use evtx::{EvtxParser, ParserSettings};
use std::{
fs::{self, File},
path::PathBuf,
time::Instant,
vec,
};
use yamato_event_analyzer::detections::detection;
@@ -74,13 +73,11 @@ fn print_credits() {
}
fn analysis_files(evtx_files: Vec<PathBuf>) {
let mut tl = Timeline::new();
let mut detection = detection::Detection::new(detection::Detection::parse_rule_files());
for evtx_file in evtx_files {
let ret = analysis_file(evtx_file, tl, detection);
tl = ret.0;
detection = ret.1;
let ret = analysis_file(evtx_file, detection);
detection = ret;
}
after_fact();
@@ -89,18 +86,19 @@ fn analysis_files(evtx_files: Vec<PathBuf>) {
// Windowsイベントログファイルを1ファイル分解析する。
fn analysis_file(
evtx_filepath: PathBuf,
mut tl: Timeline,
mut detection: detection::Detection,
) -> (Timeline, detection::Detection) {
) -> detection::Detection {
let filepath_disp = evtx_filepath.display();
let parser = evtx_to_jsons(evtx_filepath.clone());
if parser.is_none() {
return (tl, detection);
return detection;
}
let mut tl = Timeline::new();
let mut parser = parser.unwrap();
let mut records = parser.records_json_value();
let tokio_rt = utils::create_tokio_runtime();
loop {
let mut records_per_detect = vec![];
while records_per_detect.len() < MAX_DETECT_RECORDS {
@@ -139,8 +137,9 @@ fn analysis_file(
tokio_rt.shutdown_background();
detection.add_aggcondtion_msg();
tl.tm_stats_dsp_msg();
return (tl, detection);
return detection;
}
fn evtx_to_jsons(evtx_filepath: PathBuf) -> Option<EvtxParser<File>> {
+75 -16
View File
@@ -1,24 +1,32 @@
use crate::detections::{configs, detection::EvtxRecordInfo};
use crate::detections::{configs, detection::EvtxRecordInfo, utils};
use std::collections::HashMap;
#[derive(Debug)]
pub struct EventStatistics {}
pub struct EventStatistics {
pub total: usize,
pub start_time: String,
pub end_time: String,
pub stats_list: HashMap<String, usize>,
}
/**
* Windows Event Logの統計情報を出力する
*/
impl EventStatistics {
pub fn new() -> EventStatistics {
return EventStatistics {};
pub fn new(
total: usize,
start_time: String,
end_time: String,
stats_list: HashMap<String, usize>,
) -> EventStatistics {
return EventStatistics {
total,
start_time,
end_time,
stats_list,
};
}
// この関数の戻り値として、コンソールに出力する内容を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, _records: &Vec<EvtxRecordInfo>) -> Vec<String> {
pub fn start(&mut self, records: &Vec<EvtxRecordInfo>) {
// 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。
if !configs::CONFIG
.read()
@@ -26,11 +34,62 @@ impl EventStatistics {
.args
.is_present("statistics")
{
return vec![];
return;
}
// TODO ここから下を書いて欲しいです。
//let mut filesize = 0;
// _recordsから、EventIDを取り出す。
self.stats_time_cnt(records);
return vec![];
// EventIDで集計
//let evtstat_map = HashMap::new();
self.stats_eventid(records);
}
fn stats_time_cnt(&mut self, records: &Vec<EvtxRecordInfo>) {
if records.len() == 0 {
return;
}
// sortしなくてもイベントログのTimeframeを取得できるように修正しました。
// sortしないことにより計算量が改善されています。
// もうちょっと感じに書けるといえば書けます。
for record in records.iter() {
let evttime = utils::get_event_value(
&"Event.System.TimeCreated_attributes.SystemTime".to_string(),
&record.record,
)
.and_then(|evt_value| {
return Option::Some(evt_value.to_string());
});
if evttime.is_none() {
continue;
}
let evttime = evttime.unwrap();
if self.start_time.len() == 0 || evttime < self.start_time {
self.start_time = evttime.to_string();
}
if self.end_time.len() == 0 || evttime > self.end_time {
self.end_time = evttime;
}
}
self.total += records.len();
}
// EventIDで集計
fn stats_eventid(&mut self, records: &Vec<EvtxRecordInfo>) {
// let mut evtstat_map = HashMap::new();
for record in records.iter() {
let evtid = utils::get_event_value(&"EventID".to_string(), &record.record);
if evtid.is_none() {
continue;
}
let idnum = evtid.unwrap();
let count: &mut usize = self.stats_list.entry(idnum.to_string()).or_insert(0);
*count += 1;
}
// return evtstat_map;
}
}
+86 -5
View File
@@ -1,17 +1,98 @@
use crate::detections::detection::EvtxRecordInfo;
use crate::detections::{configs, detection::EvtxRecordInfo};
use super::statistics::EventStatistics;
use std::collections::HashMap;
#[derive(Debug)]
pub struct Timeline {}
pub struct Timeline {
pub stats: EventStatistics,
}
impl Timeline {
pub fn new() -> Timeline {
return Timeline {};
let totalcnt = 0;
let starttm = "".to_string();
let endtm = "".to_string();
let statslst = HashMap::new();
let statistic = EventStatistics::new(totalcnt, starttm, endtm, statslst);
return Timeline { stats: statistic };
}
pub fn start(&mut self, records: &Vec<EvtxRecordInfo>) {
let mut statistic = EventStatistics::new();
statistic.start(records);
self.stats.start(records);
}
pub fn tm_stats_dsp_msg(&mut self) {
if !configs::CONFIG
.read()
.unwrap()
.args
.is_present("statistics")
{
return;
}
// 出力メッセージ作成
//println!("map -> {:#?}", evtstat_map);
let mut sammsges: Vec<String> = Vec::new();
sammsges.push("---------------------------------------".to_string());
sammsges.push(format!("Total_counts : {}\n", self.stats.total));
sammsges.push(format!("firstevent_time: {}", self.stats.start_time));
sammsges.push(format!("lastevent_time: {}\n", self.stats.end_time));
sammsges.push("count(rate)\tID\tevent\t\ttimeline".to_string());
sammsges.push("--------------- ------- --------------- -------".to_string());
// 集計件数でソート
let mut mapsorted: Vec<_> = self.stats.stats_list.iter().collect();
mapsorted.sort_by(|x, y| y.1.cmp(&x.1));
// イベントID毎の出力メッセージ生成
let stats_msges: Vec<String> = self.tm_stats_set_msg(mapsorted);
for msgprint in sammsges.iter() {
println!("{}", msgprint);
}
for msgprint in stats_msges.iter() {
println!("{}", msgprint);
}
}
// イベントID毎の出力メッセージ生成
fn tm_stats_set_msg(&self, mapsorted: Vec<(&std::string::String, &usize)>) -> Vec<String> {
let mut msges: Vec<String> = Vec::new();
for (event_id, event_cnt) in mapsorted.iter() {
// 件数の割合を算出
let rate: f32 = **event_cnt as f32 / self.stats.total as f32;
// イベント情報取得(eventtitleなど)
let conf = configs::CONFIG.read().unwrap();
// timeline_event_info.txtに登録あるものは情報設定
match conf.event_timeline_config.get_event_id(*event_id) {
Some(e) => {
// 出力メッセージ1行作成
msges.push(format!(
"{0} ({1:.1}%)\t{2}\t{3}\t{4}",
event_cnt,
(rate * 1000.0).round() / 10.0,
event_id,
e.evttitle,
e.detectflg
));
}
None => {
// 出力メッセージ1行作成
msges.push(format!(
"{0} ({1:.1}%)\t{2}\t{3}\t{4}",
event_cnt,
(rate * 1000.0).round() / 10.0,
event_id,
"Unknown".to_string(),
"".to_string()
));
}
}
}
msges.push("---------------------------------------".to_string());
return msges;
}
}