* Feature/call error message struct#66 (#69) * change way to use write trait #66 * change call error message struct #66 * erase finished TODO #66 * erase comment in error message format test #66 * resolve conflict #66 * Feature/call error message struct#66 (#71) * change ERROR writeln struct #66 * add evtx file path export to csv #76 * fixed test case #76 * fix for #76 * forget cargo fmt -all * fix testcase Co-authored-by: ichiichi11 <takai.wa.hajime@gmail.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
|||||||
/samples
|
/samples
|
||||||
*.test
|
*.test
|
||||||
/.vscode/
|
/.vscode/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
test_*
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::detections::configs;
|
use crate::detections::configs;
|
||||||
use crate::detections::print;
|
use crate::detections::print;
|
||||||
|
use crate::detections::print::AlertMessage;
|
||||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
use chrono::{DateTime, Local, TimeZone, Utc};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -11,6 +12,7 @@ use std::process;
|
|||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct CsvFormat<'a> {
|
pub struct CsvFormat<'a> {
|
||||||
time: &'a str,
|
time: &'a str,
|
||||||
|
filepath: &'a str,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
message: &'a str,
|
message: &'a str,
|
||||||
}
|
}
|
||||||
@@ -25,7 +27,9 @@ pub fn after_fact() {
|
|||||||
match File::create(csv_path) {
|
match File::create(csv_path) {
|
||||||
Ok(file) => Box::new(file),
|
Ok(file) => Box::new(file),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Failed to open file. {}", err);
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
AlertMessage::alert(&mut stdout, format!("Failed to open file. {}", err)).ok();
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +38,9 @@ pub fn after_fact() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = emit_csv(&mut target) {
|
if let Err(err) = emit_csv(&mut target) {
|
||||||
println!("Failed to write CSV. {}", err);
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
AlertMessage::alert(&mut stdout, format!("Failed to write CSV. {}", err)).ok();
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +53,7 @@ fn emit_csv(writer: &mut Box<dyn io::Write>) -> Result<(), Box<dyn Error>> {
|
|||||||
for detect_info in detect_infos {
|
for detect_info in detect_infos {
|
||||||
wtr.serialize(CsvFormat {
|
wtr.serialize(CsvFormat {
|
||||||
time: &format_time(time),
|
time: &format_time(time),
|
||||||
|
filepath: &detect_info.filepath,
|
||||||
title: &detect_info.title,
|
title: &detect_info.title,
|
||||||
message: &detect_info.detail,
|
message: &detect_info.detail,
|
||||||
})?;
|
})?;
|
||||||
@@ -79,6 +86,9 @@ where
|
|||||||
fn test_emit_csv() {
|
fn test_emit_csv() {
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fs::{read_to_string, remove_file};
|
use std::fs::{read_to_string, remove_file};
|
||||||
|
let testfilepath: &str = "test.evtx";
|
||||||
|
let test_title = "test_title";
|
||||||
|
let output = "pokepoke";
|
||||||
{
|
{
|
||||||
let mut messages = print::MESSAGES.lock().unwrap();
|
let mut messages = print::MESSAGES.lock().unwrap();
|
||||||
|
|
||||||
@@ -86,7 +96,7 @@ fn test_emit_csv() {
|
|||||||
{
|
{
|
||||||
"Event": {
|
"Event": {
|
||||||
"EventData": {
|
"EventData": {
|
||||||
"CommandLine": "hoge"
|
"CommandRLine": "hoge"
|
||||||
},
|
},
|
||||||
"System": {
|
"System": {
|
||||||
"TimeCreated_attributes": {
|
"TimeCreated_attributes": {
|
||||||
@@ -97,11 +107,27 @@ fn test_emit_csv() {
|
|||||||
}
|
}
|
||||||
"##;
|
"##;
|
||||||
let event: Value = serde_json::from_str(val).unwrap();
|
let event: Value = serde_json::from_str(val).unwrap();
|
||||||
messages.insert(&event, "test".to_string(), "pokepoke".to_string());
|
messages.insert(
|
||||||
|
testfilepath.to_string(),
|
||||||
|
&event,
|
||||||
|
test_title.to_string(),
|
||||||
|
output.to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expect = "Time,Title,Message
|
let expect_time = Utc
|
||||||
1996-02-2";
|
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
.unwrap();
|
||||||
|
let expect_tz = expect_time.with_timezone(&Local);
|
||||||
|
let expect = "Time,Filepath,Title,Message\n".to_string()
|
||||||
|
+ &expect_tz.clone().format("%Y-%m-%dT%H:%M:%S%:z").to_string()
|
||||||
|
+ ","
|
||||||
|
+ testfilepath
|
||||||
|
+ ","
|
||||||
|
+ test_title
|
||||||
|
+ ","
|
||||||
|
+ output
|
||||||
|
+ "\n";
|
||||||
|
|
||||||
let mut file: Box<dyn io::Write> =
|
let mut file: Box<dyn io::Write> =
|
||||||
Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap());
|
Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap());
|
||||||
@@ -110,9 +136,8 @@ fn test_emit_csv() {
|
|||||||
match read_to_string("./test_emit_csv.csv") {
|
match read_to_string("./test_emit_csv.csv") {
|
||||||
Err(_) => panic!("Failed to open file"),
|
Err(_) => panic!("Failed to open file"),
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
assert_eq!(&s[0..28], expect);
|
assert_eq!(s, expect);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(remove_file("./test_emit_csv.csv").is_ok());
|
assert!(remove_file("./test_emit_csv.csv").is_ok());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
extern crate csv;
|
extern crate csv;
|
||||||
|
|
||||||
|
use crate::detections::print::AlertMessage;
|
||||||
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;
|
||||||
@@ -11,18 +12,29 @@ use serde_json::{Error, Value};
|
|||||||
use tokio::runtime;
|
use tokio::runtime;
|
||||||
use tokio::{spawn, task::JoinHandle};
|
use tokio::{spawn, task::JoinHandle};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::{fs::File, sync::Arc};
|
use std::{fs::File, sync::Arc};
|
||||||
use std::{path::PathBuf, time::Instant};
|
|
||||||
|
|
||||||
const DIRPATH_RULES: &str = "rules";
|
const DIRPATH_RULES: &str = "rules";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct EvtxRecordInfo {
|
||||||
|
evtx_filepath: String,
|
||||||
|
record: Value,
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
Detection {}
|
let initializer: Vec<EvtxRecordInfo> = Vec::new();
|
||||||
|
Detection {
|
||||||
|
parseinfos: initializer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, evtx_files: Vec<PathBuf>) {
|
pub fn start(&mut self, evtx_files: Vec<PathBuf>) {
|
||||||
@@ -36,7 +48,6 @@ impl Detection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let records = self.evtx_to_jsons(evtx_files);
|
let records = self.evtx_to_jsons(evtx_files);
|
||||||
|
|
||||||
runtime::Runtime::new()
|
runtime::Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(self.execute_rule(rules, records));
|
.block_on(self.execute_rule(rules, records));
|
||||||
@@ -48,7 +59,9 @@ impl Detection {
|
|||||||
let mut rulefile_loader = ParseYaml::new();
|
let mut rulefile_loader = ParseYaml::new();
|
||||||
let resutl_readdir = rulefile_loader.read_dir(DIRPATH_RULES);
|
let resutl_readdir = rulefile_loader.read_dir(DIRPATH_RULES);
|
||||||
if resutl_readdir.is_err() {
|
if resutl_readdir.is_err() {
|
||||||
eprintln!("{}", resutl_readdir.unwrap_err());
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
AlertMessage::alert(&mut stdout, format!("{}", resutl_readdir.unwrap_err())).ok();
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,14 +80,19 @@ impl Detection {
|
|||||||
err_msgs_result.err().iter().for_each(|err_msgs| {
|
err_msgs_result.err().iter().for_each(|err_msgs| {
|
||||||
// TODO 本当はファイルパスを出力したい
|
// TODO 本当はファイルパスを出力したい
|
||||||
// ParseYamlの変更が必要なので、一旦yamlのタイトルを表示。
|
// ParseYamlの変更が必要なので、一旦yamlのタイトルを表示。
|
||||||
|
let stdout = std::io::stdout();
|
||||||
// TODO エラーの出力方法を統一したい。
|
let mut stdout = stdout.lock();
|
||||||
// エラー出力用のクラスを作成してもいいかも
|
AlertMessage::alert(
|
||||||
println!(
|
&mut stdout,
|
||||||
"[ERROR] Failed to parse Rule file. (Error Rule Title : {})",
|
format!(
|
||||||
rule.yaml["title"].as_str().unwrap_or("")
|
"Failed to parse Rule file. (Error Rule Title : {})",
|
||||||
);
|
rule.yaml["title"].as_str().unwrap_or("")
|
||||||
err_msgs.iter().for_each(|err_msg| println!("{}", err_msg));
|
),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
err_msgs.iter().for_each(|err_msg| {
|
||||||
|
AlertMessage::alert(&mut stdout, err_msg.to_string()).ok();
|
||||||
|
});
|
||||||
println!("");
|
println!("");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,7 +102,7 @@ impl Detection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// evtxファイルをjsonに変換します。
|
// evtxファイルをjsonに変換します。
|
||||||
fn evtx_to_jsons(&mut self, evtx_files: Vec<PathBuf>) -> Vec<Value> {
|
fn evtx_to_jsons(&mut self, evtx_files: Vec<PathBuf>) -> Vec<EvtxRecordInfo> {
|
||||||
// EvtxParserを生成する。
|
// EvtxParserを生成する。
|
||||||
let evtx_parsers: Vec<EvtxParser<File>> = evtx_files
|
let evtx_parsers: Vec<EvtxParser<File>> = evtx_files
|
||||||
.iter()
|
.iter()
|
||||||
@@ -102,18 +120,41 @@ impl Detection {
|
|||||||
|
|
||||||
let xml_records = runtime::Runtime::new()
|
let xml_records = runtime::Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(self.evtx_to_xml(evtx_parsers));
|
.block_on(self.evtx_to_xml(evtx_parsers, &evtx_files));
|
||||||
|
let json_records = runtime::Runtime::new()
|
||||||
return runtime::Runtime::new()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(self.xml_to_json(xml_records));
|
.block_on(self.xml_to_json(xml_records, &evtx_files));
|
||||||
|
|
||||||
|
let mut evtx_file_index = 0;
|
||||||
|
return json_records
|
||||||
|
.into_iter()
|
||||||
|
.map(|json_records_per_evtxfile| {
|
||||||
|
let evtx_filepath = evtx_files[evtx_file_index].display().to_string();
|
||||||
|
let ret: Vec<EvtxRecordInfo> = json_records_per_evtxfile
|
||||||
|
.into_iter()
|
||||||
|
.map(|json_record| {
|
||||||
|
return EvtxRecordInfo {
|
||||||
|
evtx_filepath: String::from(&evtx_filepath),
|
||||||
|
record: json_record,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
evtx_file_index = evtx_file_index + 1;
|
||||||
|
return ret;
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// evtxファイルからxmlを生成する。
|
// evtxファイルからxmlを生成する。
|
||||||
|
// ちょっと分かりにくいですが、戻り値の型はVec<SerializedEvtxRecord<String>>ではなくて、Vec<Vec<SerializedEvtxRecord<String>>>になっています。
|
||||||
|
// 2次元配列にしている理由は、この後Value型(EvtxのXMLをJSONに変換したやつ)とイベントファイルのパスをEvtxRecordInfo構造体で保持するためです。
|
||||||
|
// EvtxParser毎にSerializedEvtxRecord<String>をグルーピングするために2次元配列にしています。
|
||||||
async fn evtx_to_xml(
|
async fn evtx_to_xml(
|
||||||
&mut self,
|
&mut self,
|
||||||
evtx_parsers: Vec<EvtxParser<File>>,
|
evtx_parsers: Vec<EvtxParser<File>>,
|
||||||
) -> Vec<SerializedEvtxRecord<String>> {
|
evtx_files: &Vec<PathBuf>,
|
||||||
|
) -> Vec<Vec<SerializedEvtxRecord<String>>> {
|
||||||
// evtx_parser.records_json()でevtxをxmlに変換するJobを作成
|
// evtx_parser.records_json()でevtxをxmlに変換するJobを作成
|
||||||
let handles: Vec<JoinHandle<Vec<err::Result<SerializedEvtxRecord<String>>>>> = evtx_parsers
|
let handles: Vec<JoinHandle<Vec<err::Result<SerializedEvtxRecord<String>>>>> = evtx_parsers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -130,71 +171,129 @@ impl Detection {
|
|||||||
|
|
||||||
// 作成したjobを実行し(handle.awaitの部分)、スレッドの実行時にエラーが発生した場合、標準エラー出力に出しておく
|
// 作成したjobを実行し(handle.awaitの部分)、スレッドの実行時にエラーが発生した場合、標準エラー出力に出しておく
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
|
let mut evtx_file_index = 0;
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let future_result = handle.await;
|
let future_result = handle.await;
|
||||||
if future_result.is_err() {
|
if future_result.is_err() {
|
||||||
eprintln!("{}", future_result.unwrap_err());
|
let evtx_filepath = &evtx_files[evtx_file_index].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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evtx_file_index = evtx_file_index + 1;
|
||||||
ret.push(future_result.unwrap());
|
ret.push(future_result.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// xmlの変換でエラーが出た場合、標準エラー出力に出しておく
|
// パースに失敗しているレコードを除外して、返す。
|
||||||
|
// SerializedEvtxRecord<String>がどのEvtxParserからパースされたのか分かるようにするため、2次元配列のまま返す。
|
||||||
|
let mut evtx_file_index = 0;
|
||||||
return ret
|
return ret
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.map(|parse_results| {
|
||||||
.filter_map(|parse_result| {
|
let ret = parse_results
|
||||||
if parse_result.is_err() {
|
.into_iter()
|
||||||
eprintln!("{}", parse_result.unwrap_err());
|
.filter_map(|parse_result| {
|
||||||
return Option::None;
|
if parse_result.is_err() {
|
||||||
}
|
let evtx_filepath = &evtx_files[evtx_file_index].display();
|
||||||
|
let errmsg = format!(
|
||||||
return Option::Some(parse_result.unwrap());
|
"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;
|
||||||
|
}
|
||||||
|
return Option::Some(parse_result.unwrap());
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
evtx_file_index = evtx_file_index + 1;
|
||||||
|
return ret;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// xmlからjsonに変換します。
|
// xmlからjsonに変換します。
|
||||||
async fn xml_to_json(&mut self, xml_records: Vec<SerializedEvtxRecord<String>>) -> Vec<Value> {
|
async fn xml_to_json(
|
||||||
|
&mut self,
|
||||||
|
xml_records: Vec<Vec<SerializedEvtxRecord<String>>>,
|
||||||
|
evtx_files: &Vec<PathBuf>,
|
||||||
|
) -> Vec<Vec<Value>> {
|
||||||
// xmlからjsonに変換するJobを作成
|
// xmlからjsonに変換するJobを作成
|
||||||
let handles: Vec<JoinHandle<Result<Value, Error>>> = xml_records
|
let handles: Vec<Vec<JoinHandle<Result<Value, Error>>>> = xml_records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|xml_record| {
|
.map(|xml_records| {
|
||||||
return spawn(async move {
|
return xml_records
|
||||||
return serde_json::from_str(&xml_record.data);
|
.into_iter()
|
||||||
});
|
.map(|xml_record| {
|
||||||
|
return spawn(async move {
|
||||||
|
return serde_json::from_str(&xml_record.data);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// 作成したjobを実行し(handle.awaitの部分)、スレッドの実行時にエラーが発生した場合、標準エラー出力に出しておく
|
// 作成したjobを実行し(handle.awaitの部分)、スレッドの実行時にエラーが発生した場合、標準エラー出力に出しておく
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
for handle in handles {
|
let mut evtx_file_index = 0;
|
||||||
let future_result = handle.await;
|
for handles_per_evtxfile in handles {
|
||||||
if future_result.is_err() {
|
let mut sub_ret = vec![];
|
||||||
eprintln!("{}", future_result.unwrap_err());
|
for handle in handles_per_evtxfile {
|
||||||
continue;
|
let future_result = handle.await;
|
||||||
}
|
if future_result.is_err() {
|
||||||
|
let evtx_filepath = &evtx_files[evtx_file_index].display();
|
||||||
ret.push(future_result.unwrap());
|
let errmsg = format!(
|
||||||
}
|
"Failed to serialize from event xml to json. EventFile:{} Error:{}",
|
||||||
|
evtx_filepath,
|
||||||
// xmlの変換でエラーが出た場合、標準エラー出力に出しておく
|
future_result.unwrap_err()
|
||||||
return ret
|
);
|
||||||
.into_iter()
|
AlertMessage::alert(&mut std::io::stdout().lock(), errmsg).ok();
|
||||||
.filter_map(|parse_result| {
|
continue;
|
||||||
if parse_result.is_err() {
|
|
||||||
eprintln!("{}", parse_result.unwrap_err());
|
|
||||||
return Option::None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Option::Some(parse_result.unwrap());
|
sub_ret.push(future_result.unwrap());
|
||||||
|
}
|
||||||
|
ret.push(sub_ret);
|
||||||
|
evtx_file_index = evtx_file_index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONの変換に失敗したものを除外して、返す。
|
||||||
|
// ValueがどのEvtxParserからパースされたのか分かるようにするため、2次元配列のまま返す。
|
||||||
|
let mut evtx_file_index = 0;
|
||||||
|
return ret
|
||||||
|
.into_iter()
|
||||||
|
.map(|parse_results| {
|
||||||
|
let successed = parse_results
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|parse_result| {
|
||||||
|
if parse_result.is_err() {
|
||||||
|
let evtx_filepath = &evtx_files[evtx_file_index].display();
|
||||||
|
let errmsg = format!(
|
||||||
|
"Failed to serialize from event xml to json. EventFile:{} Error:{}",
|
||||||
|
evtx_filepath,
|
||||||
|
parse_result.unwrap_err()
|
||||||
|
);
|
||||||
|
AlertMessage::alert(&mut std::io::stdout().lock(), errmsg).ok();
|
||||||
|
return Option::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Option::Some(parse_result.unwrap());
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
evtx_file_index = evtx_file_index + 1;
|
||||||
|
|
||||||
|
return successed;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 検知ロジックを実行します。
|
// 検知ロジックを実行します。
|
||||||
async fn execute_rule(&mut self, rules: Vec<RuleNode>, records: Vec<Value>) {
|
async fn execute_rule(&mut self, rules: Vec<RuleNode>, records: Vec<EvtxRecordInfo>) {
|
||||||
// 複数スレッドで所有権を共有するため、recordsをArcでwwap
|
// 複数スレッドで所有権を共有するため、recordsをArcでwwap
|
||||||
let mut records_arcs = vec![];
|
let mut records_arcs = vec![];
|
||||||
for record_chunk in Detection::chunks(records, num_cpus::get() * 4) {
|
for record_chunk in Detection::chunks(records, num_cpus::get() * 4) {
|
||||||
@@ -213,9 +312,9 @@ impl Detection {
|
|||||||
|
|
||||||
let handle: JoinHandle<Vec<bool>> = spawn(async move {
|
let handle: JoinHandle<Vec<bool>> = spawn(async move {
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
for record in records_arc_clone.iter() {
|
for record_info in records_arc_clone.iter() {
|
||||||
for rule in rules_clones.iter() {
|
for rule in rules_clones.iter() {
|
||||||
if rule.select(record) {
|
if rule.select(&record_info.record) {
|
||||||
// TODO ここはtrue/falseじゃなくて、ruleとrecordのタプルをretにpushする実装に変更したい。
|
// TODO ここはtrue/falseじゃなくて、ruleとrecordのタプルをretにpushする実装に変更したい。
|
||||||
ret.push(true);
|
ret.push(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -234,22 +333,25 @@ impl Detection {
|
|||||||
for record_chunk_arc in &records_arcs {
|
for record_chunk_arc in &records_arcs {
|
||||||
let mut handles_ret_ite = handles_ite.next().unwrap().await.unwrap().into_iter();
|
let mut handles_ret_ite = handles_ite.next().unwrap().await.unwrap().into_iter();
|
||||||
for rule in rules_arc.iter() {
|
for rule in rules_arc.iter() {
|
||||||
for record_arc in record_chunk_arc.iter() {
|
for record_info_arc in record_chunk_arc.iter() {
|
||||||
if handles_ret_ite.next().unwrap() == true {
|
if handles_ret_ite.next().unwrap() == false {
|
||||||
// TODO メッセージが多いと、rule.select()よりもこの処理の方が時間かかる。
|
continue;
|
||||||
message.insert(
|
|
||||||
record_arc,
|
|
||||||
rule.yaml["title"].as_str().unwrap_or("").to_string(),
|
|
||||||
rule.yaml["output"].as_str().unwrap_or("").to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO メッセージが多いと、rule.select()よりもこの処理の方が時間かかる。
|
||||||
|
message.insert(
|
||||||
|
record_info_arc.evtx_filepath.to_string(),
|
||||||
|
&record_info_arc.record,
|
||||||
|
rule.yaml["title"].as_str().unwrap_or("").to_string(),
|
||||||
|
rule.yaml["output"].as_str().unwrap_or("").to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作
|
// 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作
|
||||||
fn chunks(ary: Vec<Value>, size: usize) -> Vec<Vec<Value>> {
|
fn chunks<T>(ary: Vec<T>, size: usize) -> Vec<Vec<T>> {
|
||||||
let arylen = ary.len();
|
let arylen = ary.len();
|
||||||
let mut ite = ary.into_iter();
|
let mut ite = ary.into_iter();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use regex::Regex;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::{self, Write};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -16,6 +17,7 @@ pub struct Message {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DetectInfo {
|
pub struct DetectInfo {
|
||||||
|
pub filepath: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub detail: String,
|
pub detail: String,
|
||||||
}
|
}
|
||||||
@@ -33,7 +35,13 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// メッセージを設定
|
/// メッセージを設定
|
||||||
pub fn insert(&mut self, event_record: &Value, event_title: String, output: String) {
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
target_file: String,
|
||||||
|
event_record: &Value,
|
||||||
|
event_title: String,
|
||||||
|
output: String,
|
||||||
|
) {
|
||||||
if output.is_empty() {
|
if output.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -42,6 +50,7 @@ impl Message {
|
|||||||
let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
|
let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
|
||||||
let time = Message::get_event_time(event_record).unwrap_or(default_time);
|
let time = Message::get_event_time(event_record).unwrap_or(default_time);
|
||||||
let detect_info = DetectInfo {
|
let detect_info = DetectInfo {
|
||||||
|
filepath: target_file,
|
||||||
title: event_title,
|
title: event_title,
|
||||||
detail: message.to_string(),
|
detail: message.to_string(),
|
||||||
};
|
};
|
||||||
@@ -146,14 +155,14 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AlertMessage {
|
impl AlertMessage {
|
||||||
pub fn alert(contents: String) {
|
pub fn alert<W: Write>(w: &mut W, contents: String) -> io::Result<()> {
|
||||||
println!("[ERROR] {}", contents);
|
writeln!(w, "[ERROR] {}", contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::detections::print::Message;
|
use crate::detections::print::{AlertMessage, Message};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -175,6 +184,7 @@ mod tests {
|
|||||||
"##;
|
"##;
|
||||||
let event_record_1: Value = serde_json::from_str(json_str_1).unwrap();
|
let event_record_1: Value = serde_json::from_str(json_str_1).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
|
"a".to_string(),
|
||||||
&event_record_1,
|
&event_record_1,
|
||||||
"test1".to_string(),
|
"test1".to_string(),
|
||||||
"CommandLine1: %CommandLine%".to_string(),
|
"CommandLine1: %CommandLine%".to_string(),
|
||||||
@@ -196,6 +206,7 @@ mod tests {
|
|||||||
"##;
|
"##;
|
||||||
let event_record_2: Value = serde_json::from_str(json_str_2).unwrap();
|
let event_record_2: Value = serde_json::from_str(json_str_2).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
|
"a".to_string(),
|
||||||
&event_record_2,
|
&event_record_2,
|
||||||
"test2".to_string(),
|
"test2".to_string(),
|
||||||
"CommandLine2: %CommandLine%".to_string(),
|
"CommandLine2: %CommandLine%".to_string(),
|
||||||
@@ -217,6 +228,7 @@ mod tests {
|
|||||||
"##;
|
"##;
|
||||||
let event_record_3: Value = serde_json::from_str(json_str_3).unwrap();
|
let event_record_3: Value = serde_json::from_str(json_str_3).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
|
"a".to_string(),
|
||||||
&event_record_3,
|
&event_record_3,
|
||||||
"test3".to_string(),
|
"test3".to_string(),
|
||||||
"CommandLine3: %CommandLine%".to_string(),
|
"CommandLine3: %CommandLine%".to_string(),
|
||||||
@@ -233,6 +245,7 @@ mod tests {
|
|||||||
"##;
|
"##;
|
||||||
let event_record_4: Value = serde_json::from_str(json_str_4).unwrap();
|
let event_record_4: Value = serde_json::from_str(json_str_4).unwrap();
|
||||||
message.insert(
|
message.insert(
|
||||||
|
"a".to_string(),
|
||||||
&event_record_4,
|
&event_record_4,
|
||||||
"test4".to_string(),
|
"test4".to_string(),
|
||||||
"CommandLine4: %CommandLine%".to_string(),
|
"CommandLine4: %CommandLine%".to_string(),
|
||||||
@@ -240,7 +253,15 @@ mod tests {
|
|||||||
|
|
||||||
let display = format!("{}", format_args!("{:?}", message));
|
let display = format!("{}", format_args!("{:?}", message));
|
||||||
println!("display::::{}", display);
|
println!("display::::{}", display);
|
||||||
let expect = "Message { map: {1970-01-01T00:00:00Z: [DetectInfo { title: \"test4\", detail: \"CommandLine4: hoge\" }], 1996-02-27T01:05:01Z: [DetectInfo { title: \"test1\", detail: \"CommandLine1: hoge\" }, DetectInfo { title: \"test2\", detail: \"CommandLine2: hoge\" }], 2000-01-21T09:06:01Z: [DetectInfo { title: \"test3\", detail: \"CommandLine3: hoge\" }]} }";
|
let expect = "Message { map: {1970-01-01T00:00:00Z: [DetectInfo { filepath: \"a\", title: \"test4\", detail: \"CommandLine4: hoge\" }], 1996-02-27T01:05:01Z: [DetectInfo { filepath: \"a\", title: \"test1\", detail: \"CommandLine1: hoge\" }, DetectInfo { filepath: \"a\", title: \"test2\", detail: \"CommandLine2: hoge\" }], 2000-01-21T09:06:01Z: [DetectInfo { filepath: \"a\", title: \"test3\", detail: \"CommandLine3: hoge\" }]} }";
|
||||||
assert_eq!(display, expect);
|
assert_eq!(display, expect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_message() {
|
||||||
|
let input = "TEST!";
|
||||||
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
AlertMessage::alert(&mut stdout, input.to_string()).expect("[ERROR] TEST!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -5,6 +5,7 @@ use std::{fs, path::PathBuf};
|
|||||||
use yamato_event_analyzer::afterfact::after_fact;
|
use yamato_event_analyzer::afterfact::after_fact;
|
||||||
use yamato_event_analyzer::detections::configs;
|
use yamato_event_analyzer::detections::configs;
|
||||||
use yamato_event_analyzer::detections::detection;
|
use yamato_event_analyzer::detections::detection;
|
||||||
|
use yamato_event_analyzer::detections::print::AlertMessage;
|
||||||
use yamato_event_analyzer::omikuji::Omikuji;
|
use yamato_event_analyzer::omikuji::Omikuji;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -21,7 +22,9 @@ fn main() {
|
|||||||
fn collect_evtxfiles(dirpath: &str) -> Vec<PathBuf> {
|
fn collect_evtxfiles(dirpath: &str) -> Vec<PathBuf> {
|
||||||
let entries = fs::read_dir(dirpath);
|
let entries = fs::read_dir(dirpath);
|
||||||
if entries.is_err() {
|
if entries.is_err() {
|
||||||
eprintln!("{}", entries.unwrap_err());
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
AlertMessage::alert(&mut stdout, format!("{}", entries.unwrap_err())).ok();
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +53,13 @@ fn collect_evtxfiles(dirpath: &str) -> Vec<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn print_credits() {
|
fn print_credits() {
|
||||||
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
match fs::read_to_string("./credits.txt") {
|
match fs::read_to_string("./credits.txt") {
|
||||||
Ok(contents) => println!("{}", contents),
|
Ok(contents) => println!("{}", contents),
|
||||||
Err(err) => println!("{}", err),
|
Err(err) => {
|
||||||
|
AlertMessage::alert(&mut stdout, format!("{}", err)).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
src/yaml.rs
17
src/yaml.rs
@@ -1,6 +1,7 @@
|
|||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate yaml_rust;
|
extern crate yaml_rust;
|
||||||
|
|
||||||
|
use crate::detections::print::AlertMessage;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
@@ -34,6 +35,8 @@ impl ParseYaml {
|
|||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
let entry = entry.ok()?;
|
let entry = entry.ok()?;
|
||||||
if entry.file_type().ok()?.is_file() {
|
if entry.file_type().ok()?.is_file() {
|
||||||
|
let stdout = std::io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
match self.read_file(entry.path()) {
|
match self.read_file(entry.path()) {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
match YamlLoader::load_from_str(&s) {
|
match YamlLoader::load_from_str(&s) {
|
||||||
@@ -45,11 +48,21 @@ impl ParseYaml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("fail to read file\n{}\n{} ", s, e),
|
Err(e) => {
|
||||||
|
AlertMessage::alert(
|
||||||
|
&mut stdout,
|
||||||
|
format!("fail to read file\n{}\n{} ", s, e),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("fail to read file: {}\n{} ", entry.path().display(), e)
|
AlertMessage::alert(
|
||||||
|
&mut stdout,
|
||||||
|
format!("fail to read file: {}\n{} ", entry.path().display(), e),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user