diff --git a/src/afterfact.rs b/src/afterfact.rs index 030216b5..57142ec7 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -27,41 +27,6 @@ use std::process; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; use terminal_size::Width; -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] -pub struct CsvFormat<'a> { - timestamp: &'a str, - computer: &'a str, - channel: &'a str, - event_i_d: &'a str, - level: &'a str, - mitre_attack: &'a str, - #[serde(skip_serializing_if = "Option::is_none")] - record_i_d: Option<&'a str>, - rule_title: &'a str, - details: &'a str, - #[serde(skip_serializing_if = "Option::is_none")] - record_information: Option<&'a str>, - rule_file: &'a str, - evtx_file: &'a str, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] -pub struct DisplayFormat<'a> { - timestamp: &'a str, - pub computer: &'a str, - pub channel: &'a str, - pub event_i_d: &'a str, - pub level: &'a str, - #[serde(skip_serializing_if = "Option::is_none")] - record_i_d: Option<&'a str>, - pub rule_title: &'a str, - pub details: &'a str, - #[serde(skip_serializing_if = "Option::is_none")] - pub record_information: Option<&'a str>, -} - lazy_static! { pub static ref OUTPUT_COLOR: HashMap = set_output_color(); } @@ -251,50 +216,13 @@ fn emit_csv( timestamps.push(_get_timestamp(time)); for detect_info in detect_infos { detected_record_idset.insert(format!("{}_{}", time, detect_info.eventid)); - let level = detect_info.level.to_string(); - let time_str = format_time(time, false); if displayflag { - let record_id = detect_info - .record_id - .as_ref() - .map(|recinfo| _format_cellpos(recinfo, ColPos::Other)); - let recinfo = detect_info - .record_information - .as_ref() - .map(|recinfo| _format_cellpos(recinfo, ColPos::Last)); - let ctr_char_exclude_details = detect_info - .detail - .chars() - .filter(|&c| !c.is_control()) - .collect::(); - - let details = if ctr_char_exclude_details.is_empty() { - "-".to_string() - } else { - ctr_char_exclude_details - }; - - let dispformat: _ = DisplayFormat { - timestamp: &_format_cellpos(&time_str, ColPos::First), - level: &_format_cellpos( - level_abbr.get(&level).unwrap_or(&level), - ColPos::Other, - ), - computer: &_format_cellpos(&detect_info.computername, ColPos::Other), - event_i_d: &_format_cellpos(&detect_info.eventid, ColPos::Other), - channel: &_format_cellpos(&detect_info.channel, ColPos::Other), - rule_title: &_format_cellpos(&detect_info.alert, ColPos::Other), - details: &_format_cellpos(&details, ColPos::Other), - record_information: recinfo.as_deref(), - record_i_d: record_id.as_deref(), - }; - //ヘッダーのみを出力 if plus_header { write_color_buffer( &disp_wtr, get_writable_color(None), - &_get_serialized_disp_output(None), + &_get_serialized_disp_output(PROFILES.as_ref().unwrap().clone(), true), true, ) .ok(); @@ -302,31 +230,18 @@ fn emit_csv( } write_color_buffer( &disp_wtr, - get_writable_color(_get_output_color(&color_map, &detect_info.level)), - &_get_serialized_disp_output(Some(dispformat)), + get_writable_color(_get_output_color(&color_map, LEVEL_ABBR.get(&detect_info.level).unwrap_or(&String::default()))), + &_get_serialized_disp_output(detect_info.ext_field.clone(), false), false, ) .ok(); } else { // csv output format - wtr.serialize(CsvFormat { - timestamp: &time_str, - level: level_abbr.get(&level).unwrap_or(&level).trim(), - computer: &detect_info.computername, - event_i_d: &detect_info.eventid, - channel: &detect_info.channel, - mitre_attack: &detect_info.tag_info, - rule_title: &detect_info.alert, - details: &detect_info.detail, - record_information: detect_info.record_information.as_deref(), - evtx_file: &detect_info.filepath, - rule_file: Path::new(&detect_info.rulepath) - .file_name() - .unwrap() - .to_str() - .unwrap(), - record_i_d: detect_info.record_id.as_deref(), - })?; + if plus_header { + wtr.write_record(detect_info.ext_field.keys())?; + plus_header = false; + } + wtr.write_record(detect_info.ext_field.values())?; } let level_suffix = *configs::LEVELMAP .get(&detect_info.level.to_uppercase()) @@ -470,24 +385,24 @@ enum ColPos { Other, } -fn _get_serialized_disp_output(dispformat: Option) -> String { - if dispformat.is_none() { - let mut titles = vec![ - "Timestamp", - "Computer", - "Channel", - "EventID", - "Level", - "RuleTitle", - "Details", - ]; - if !*IS_HIDE_RECORD_ID { - titles.insert(5, "RecordID"); - } - if configs::CONFIG.read().unwrap().args.full_data { - titles.push("RecordInformation"); - } - return titles.join("|"); +fn _get_serialized_disp_output(mut data: LinkedHashMap, header: bool) -> String { + let data_length = &data.len(); + let entries = data.entries(); + let mut ret:Vec = vec![]; + if header { + entries.for_each(|entry|{ + ret.push(entry.key().to_owned()); + }); + } else { + entries.enumerate().for_each(|(i, entry)|{ + if i == 0 { + ret.push(_format_cellpos(entry.get(), ColPos::First)) + } else if i == data_length - 1{ + ret.push(_format_cellpos(entry.get(), ColPos::Last)) + } else { + ret.push(_format_cellpos(entry.get(), ColPos::Other)) + } + }); } let mut disp_serializer = csv::WriterBuilder::new() .double_quote(false) @@ -496,8 +411,7 @@ fn _get_serialized_disp_output(dispformat: Option) -> String { .has_headers(false) .from_writer(vec![]); - disp_serializer.serialize(dispformat.unwrap()).ok(); - + disp_serializer.write_record(ret).ok(); String::from_utf8(disp_serializer.into_inner().unwrap_or_default()).unwrap_or_default() } diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 91fd7f02..3e74a170 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -2,12 +2,15 @@ extern crate csv; use crate::detections::configs; use crate::detections::utils::write_color_buffer; +use crate::options::profile; +use crate::options::profile::PROFILES; +use linked_hash_map::LinkedHashMap; use termcolor::{BufferWriter, Color, ColorChoice}; use crate::detections::message::AlertMessage; use crate::detections::message::DetectInfo; use crate::detections::message::ERROR_LOG_STACK; -use crate::detections::message::{CH_CONFIG, DEFAULT_DETAILS, IS_HIDE_RECORD_ID, TAGS_CONFIG}; +use crate::detections::message::{CH_CONFIG, DEFAULT_DETAILS, TAGS_CONFIG}; use crate::detections::message::{ LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, STATISTICS_FLAG, }; @@ -202,8 +205,12 @@ impl Detection { rule } - /// 条件に合致したレコードを表示するための関数 + /// 条件に合致したレコードを格納するための関数 fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) { + let profile_all_alias = if PROFILES.is_some() { + PROFILES.as_ref().unwrap().values().cloned().collect::>().join("|") + } + else{String::default()}; let tag_info: Vec = match TAGS_CONFIG.is_empty() { false => rule.yaml["tags"] .as_vec() @@ -229,7 +236,7 @@ impl Detection { .record_information .as_ref() .map(|recinfo| recinfo.to_string()); - let rec_id = if !*IS_HIDE_RECORD_ID { + let rec_id = if !profile_all_alias.contains("%RecordID%") { Some( get_serde_number_to_string(&record_info.record["Event"]["System"]["EventRecordID"]) .unwrap_or_default(), @@ -268,6 +275,7 @@ impl Detection { tag_info: tag_info.join(" | "), record_information: opt_record_info, record_id: rec_id, + ext_field: PROFILES.as_ref().unwrap().to_owned(), }; message::insert( &record_info.record, @@ -311,7 +319,8 @@ impl Detection { detail: output, record_information: rec_info, tag_info: tag_info.join(" : "), - record_id: rec_id, + record_id: Some("-".to_owned()), + ext_field: PROFILES.as_ref().unwrap().to_owned(), }; message::insert_message(detect_info, agg_result.start_timedate) diff --git a/src/detections/message.rs b/src/detections/message.rs index e8042c63..0858ab09 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -20,6 +20,8 @@ use std::path::Path; use std::sync::Mutex; use termcolor::{BufferWriter, ColorChoice}; +use super::utils::format_time; + #[derive(Debug, Clone)] pub struct DetectInfo { pub filepath: String, @@ -33,6 +35,7 @@ pub struct DetectInfo { pub tag_info: String, pub record_information: Option, pub record_id: Option, + pub ext_field: LinkedHashMap, } pub struct AlertMessage {} diff --git a/src/main.rs b/src/main.rs index e4199355..98a5b4a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ use hayabusa::detections::pivot::PivotKeyword; use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::omikuji::Omikuji; -use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules}; +use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules, profile::PROFILES}; use hayabusa::{afterfact::after_fact, detections::utils}; use hayabusa::{detections::configs, timeline::timelines::Timeline}; use hayabusa::{detections::utils::write_color_buffer, filter}; diff --git a/src/options/profile.rs b/src/options/profile.rs index 466f5409..8c5f8e78 100644 --- a/src/options/profile.rs +++ b/src/options/profile.rs @@ -10,7 +10,7 @@ use std::path::Path; use yaml_rust::{Yaml, YamlEmitter, YamlLoader}; lazy_static! { - pub static ref PROFILES: Option> = load_profile( + pub static ref PROFILES: Option> = load_profile( check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), "config/default_profile.txt" @@ -46,7 +46,7 @@ fn read_profile_data(profile_path: &str) -> Result, String> { pub fn load_profile( default_profile_path: &str, profile_path: &str, -) -> Option> { +) -> Option> { let conf = &configs::CONFIG.read().unwrap().args; let profile_all: Vec = if conf.profile.is_none() { match read_profile_data(default_profile_path) { @@ -71,7 +71,7 @@ pub fn load_profile( return None; } let profile_data = &profile_all[0]; - let mut ret: HashMap = HashMap::new(); + let mut ret: LinkedHashMap = LinkedHashMap::new(); if let Some(profile_name) = &conf.profile { if !profile_data[profile_name.as_str()].is_badvalue() { profile_data[profile_name.as_str()].clone().as_hash().unwrap().into_iter().for_each(|(k, v)| { @@ -83,8 +83,10 @@ pub fn load_profile( None } } else { - AlertMessage::alert("Not specified --profile").ok(); - None + profile_all[0].clone().as_hash().unwrap().into_iter().for_each(|(k, v)| { + ret.insert(k.as_str().unwrap().to_string(), v.as_str().unwrap().to_string()); + }); + Some(ret) } }