Skip to content

Commit

Permalink
feat: add log-metrics command feature
Browse files Browse the repository at this point in the history
  • Loading branch information
fukusuket committed Nov 12, 2024
1 parent 23032ed commit 38da485
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 43 deletions.
114 changes: 114 additions & 0 deletions src/detections/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub struct StoredStatic {
pub logon_summary_flag: bool,
pub search_flag: bool,
pub computer_metrics_flag: bool,
pub log_metrics_flag: bool,
pub search_option: Option<SearchOption>,
pub output_option: Option<OutputOption>,
pub pivot_keyword_list_flag: bool,
Expand Down Expand Up @@ -151,6 +152,7 @@ impl StoredStatic {
Some(Action::PivotKeywordsList(opt)) => opt.detect_common_options.quiet_errors,
Some(Action::Search(opt)) => opt.quiet_errors,
Some(Action::ComputerMetrics(opt)) => opt.quiet_errors,
Some(Action::LogMetrics(opt)) => opt.detect_common_options.quiet_errors,
_ => false,
};
let common_options = match &input_config.as_ref().unwrap().action {
Expand All @@ -165,6 +167,7 @@ impl StoredStatic {
Some(Action::UpdateRules(opt)) => opt.common_options,
Some(Action::Search(opt)) => opt.common_options,
Some(Action::ComputerMetrics(opt)) => opt.common_options,
Some(Action::LogMetrics(opt)) => opt.common_options,
None => CommonOptions {
no_color: false,
quiet: false,
Expand All @@ -180,6 +183,7 @@ impl StoredStatic {
Some(Action::PivotKeywordsList(opt)) => &opt.detect_common_options.config,
Some(Action::Search(opt)) => &opt.config,
Some(Action::ComputerMetrics(opt)) => &opt.config,
Some(Action::LogMetrics(opt)) => &opt.detect_common_options.config,
_ => &binding,
};
let verbose_flag = match &input_config.as_ref().unwrap().action {
Expand All @@ -190,6 +194,7 @@ impl StoredStatic {
Some(Action::PivotKeywordsList(opt)) => opt.detect_common_options.verbose,
Some(Action::Search(opt)) => opt.verbose,
Some(Action::ComputerMetrics(opt)) => opt.verbose,
Some(Action::LogMetrics(opt)) => opt.detect_common_options.verbose,
_ => false,
};
let json_input_flag = match &input_config.as_ref().unwrap().action {
Expand All @@ -199,6 +204,7 @@ impl StoredStatic {
Some(Action::EidMetrics(opt)) => opt.detect_common_options.json_input,
Some(Action::PivotKeywordsList(opt)) => opt.detect_common_options.json_input,
Some(Action::ComputerMetrics(opt)) => opt.json_input,
Some(Action::LogMetrics(opt)) => opt.detect_common_options.json_input,
_ => false,
};
let is_valid_min_level = match &input_config.as_ref().unwrap().action {
Expand Down Expand Up @@ -347,6 +353,7 @@ impl StoredStatic {
Some(Action::LogonSummary(opt)) => opt.output.as_ref(),
Some(Action::Search(opt)) => opt.output.as_ref(),
Some(Action::ComputerMetrics(opt)) => opt.output.as_ref(),
Some(Action::LogMetrics(opt)) => opt.output.as_ref(),
_ => None,
};
let general_ch_abbr = create_output_filter_config(
Expand Down Expand Up @@ -570,6 +577,7 @@ impl StoredStatic {
Some(Action::LogonSummary(opt)) => opt.input_args.recover_records,
Some(Action::PivotKeywordsList(opt)) => opt.input_args.recover_records,
Some(Action::Search(opt)) => opt.input_args.recover_records,
Some(Action::LogMetrics(opt)) => opt.input_args.recover_records,
_ => false,
};
let timeline_offset = match &input_config.as_ref().unwrap().action {
Expand All @@ -582,6 +590,7 @@ impl StoredStatic {
Some(Action::PivotKeywordsList(opt)) => opt.input_args.timeline_offset.clone(),
Some(Action::Search(opt)) => opt.input_args.timeline_offset.clone(),
Some(Action::ComputerMetrics(opt)) => opt.input_args.timeline_offset.clone(),
Some(Action::LogMetrics(opt)) => opt.input_args.timeline_offset.clone(),
_ => None,
};
let include_status: HashSet<CompactString> = match &input_config.as_ref().unwrap().action {
Expand Down Expand Up @@ -692,6 +701,7 @@ impl StoredStatic {
metrics_flag: action_id == 3,
search_flag: action_id == 10,
computer_metrics_flag: action_id == 11,
log_metrics_flag: action_id == 12,
search_option: extract_search_options(input_config.as_ref().unwrap()),
output_option: extract_output_options(input_config.as_ref().unwrap()),
pivot_keyword_list_flag: action_id == 4,
Expand Down Expand Up @@ -830,6 +840,7 @@ fn check_thread_number(config: &Config) -> Option<usize> {
Action::LogonSummary(opt) => opt.detect_common_options.thread_number,
Action::EidMetrics(opt) => opt.detect_common_options.thread_number,
Action::PivotKeywordsList(opt) => opt.detect_common_options.thread_number,
Action::LogMetrics(opt) => opt.detect_common_options.thread_number,
_ => None,
}
}
Expand Down Expand Up @@ -857,6 +868,16 @@ pub enum Action {
/// Save the timeline in JSON/JSONL format.
JsonTimeline(JSONOutputOption),

#[clap(
author = "Yamato Security (https://github.com/Yamato-Security/hayabusa - @SecurityYamato)",
help_template = "\nHayabusa v2.19.0 - Dev Build\n{author-with-newline}\n{usage-heading}\n hayabusa.exe log-metrics <INPUT> [OPTIONS]\n\n{all-args}",
term_width = 400,
display_order = 382,
disable_help_flag = true
)]
/// Print log file metrics
LogMetrics(LogMetricsOption),

#[clap(
author = "Yamato Security (https://github.com/Yamato-Security/hayabusa - @SecurityYamato)",
help_template = "\nHayabusa v2.19.0 - Dev Build\n{author-with-newline}\n{usage-heading}\n hayabusa.exe logon-summary <INPUT> [OPTIONS]\n\n{all-args}",
Expand Down Expand Up @@ -962,6 +983,7 @@ impl Action {
Action::ListProfiles(_) => 9,
Action::Search(_) => 10,
Action::ComputerMetrics(_) => 11,
Action::LogMetrics(_) => 12,
}
} else {
100
Expand All @@ -982,6 +1004,7 @@ impl Action {
Action::ListProfiles(_) => "list-profiles",
Action::Search(_) => "search",
Action::ComputerMetrics(_) => "computer-metrics",
Action::LogMetrics(_) => "log-metrics",
}
} else {
""
Expand Down Expand Up @@ -1781,6 +1804,54 @@ pub struct ComputerMetricsOption {
pub clobber: bool,
}

#[derive(Args, Clone, Debug)]
pub struct LogMetricsOption {
#[clap(flatten)]
pub input_args: InputOption,

/// Save the Metrics in CSV format (ex: metrics.csv)
#[arg(help_heading = Some("Output"), short = 'o', long, value_name = "FILE", display_order = 410)]
pub output: Option<PathBuf>,

#[clap(flatten)]
pub common_options: CommonOptions,

#[clap(flatten)]
pub detect_common_options: DetectCommonOption,

/// Output timestamp in European time format (ex: 22-02-2022 22:00:00.123 +02:00)
#[arg(help_heading = Some("Time Format"), long = "European-time", display_order = 50)]
pub european_time: bool,

/// Output timestamp in original ISO-8601 format (ex: 2022-02-22T10:10:10.1234567Z) (Always UTC)
#[arg(help_heading = Some("Time Format"), short = 'O', long = "ISO-8601", display_order = 90)]
pub iso_8601: bool,

/// Output timestamp in RFC 2822 format (ex: Fri, 22 Feb 2022 22:00:00 -0600)
#[arg(help_heading = Some("Time Format"), long = "RFC-2822", display_order = 180)]
pub rfc_2822: bool,

/// Output timestamp in RFC 3339 format (ex: 2022-02-22 22:00:00.123456-06:00)
#[arg(help_heading = Some("Time Format"), long = "RFC-3339", display_order = 180)]
pub rfc_3339: bool,

/// Output timestamp in US military time format (ex: 02-22-2022 22:00:00.123 -06:00)
#[arg(help_heading = Some("Time Format"), long = "US-military-time", display_order = 210)]
pub us_military_time: bool,

/// Output timestamp in US time format (ex: 02-22-2022 10:00:00.123 PM -06:00)
#[arg(help_heading = Some("Time Format"), long = "US-time", display_order = 210)]
pub us_time: bool,

/// Output time in UTC format (default: local time)
#[arg(help_heading = Some("Time Format"), short = 'U', long = "UTC", display_order = 210)]
pub utc: bool,

/// Overwrite files when saving
#[arg(help_heading = Some("General Options"), short='C', long = "clobber", display_order = 290, requires = "output")]
pub clobber: bool,
}

#[derive(Parser, Clone, Debug)]
#[clap(
author = "Yamato Security (https://github.com/Yamato-Security/hayabusa - @SecurityYamato)",
Expand Down Expand Up @@ -2462,6 +2533,49 @@ fn extract_output_options(config: &Config) -> Option<OutputOption> {
enable_all_rules: false,
scan_all_evtx_files: false,
}),
Action::LogMetrics(option) => Some(OutputOption {
input_args: option.input_args.clone(),
profile: None,
common_options: option.common_options,
enable_deprecated_rules: false,
enable_unsupported_rules: false,
exclude_status: None,
include_tag: None,
include_category: None,
exclude_category: None,
min_level: String::default(),
exact_level: None,
enable_noisy_rules: false,
end_timeline: None,
start_timeline: None,
eid_filter: false,
proven_rules: false,
exclude_tag: None,
detect_common_options: option.detect_common_options.clone(),
european_time: false,
iso_8601: false,
rfc_2822: false,
rfc_3339: false,
us_military_time: false,
us_time: false,
utc: false,
visualize_timeline: false,
rules: Path::new("./rules").to_path_buf(),
html_report: None,
no_summary: false,
clobber: option.clobber,
include_eid: None,
exclude_eid: None,
no_field: false,
no_pwsh_field_extraction: false,
remove_duplicate_data: false,
remove_duplicate_detections: false,
no_wizard: true,
include_status: None,
sort_events: false,
enable_all_rules: false,
scan_all_evtx_files: false,
}),
Action::Search(option) => Some(OutputOption {
input_args: option.input_args.clone(),
enable_deprecated_rules: false,
Expand Down
3 changes: 2 additions & 1 deletion src/detections/detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ impl Detection {
if !(stored_static.logon_summary_flag
|| stored_static.search_flag
|| stored_static.metrics_flag
|| stored_static.computer_metrics_flag)
|| stored_static.computer_metrics_flag
|| stored_static.log_metrics_flag)
{
Detection::print_rule_load_info(
&rulefile_loader.rulecounter,
Expand Down
45 changes: 18 additions & 27 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,10 @@ impl App {
}
println!();
}
Action::EidMetrics(_) | Action::Search(_) => {
Action::EidMetrics(_)
| Action::ComputerMetrics(_)
| Action::LogMetrics(_)
| Action::Search(_) => {
if let Some(path) = &stored_static.output_path {
if !(stored_static.output_option.as_ref().unwrap().clobber)
&& utils::check_file_expect_not_exist(
Expand All @@ -416,28 +419,6 @@ impl App {
&stored_static.html_report_flag,
);
}
Action::ComputerMetrics(_) => {
if let Some(path) = &stored_static.output_path {
if !(stored_static.output_option.as_ref().unwrap().clobber)
&& utils::check_file_expect_not_exist(
path.as_path(),
format!(
" The file {} already exists. Please specify a different filename or add the -C, --clobber option to overwrite.\n",
path.as_os_str().to_str().unwrap()
),
)
{
return;
}
}
self.analysis_start(&target_extensions, &time_filter, stored_static);
output_saved_file(
&stored_static.output_path,
"Saved results",
&stored_static.html_report_flag,
);
println!();
}
Action::PivotKeywordsList(_) => {
load_pivot_keywords(
utils::check_setting_path(
Expand Down Expand Up @@ -1103,6 +1084,7 @@ impl App {
|| stored_static.logon_summary_flag
|| stored_static.search_flag
|| stored_static.computer_metrics_flag
|| stored_static.log_metrics_flag
|| stored_static.output_option.as_ref().unwrap().no_wizard)
{
CHECKPOINT
Expand Down Expand Up @@ -1471,7 +1453,8 @@ impl App {
if !(stored_static.logon_summary_flag
|| stored_static.search_flag
|| stored_static.metrics_flag
|| stored_static.computer_metrics_flag)
|| stored_static.computer_metrics_flag
|| stored_static.log_metrics_flag)
{
println!("Loading detection rules. Please wait.");
} else if stored_static.logon_summary_flag {
Expand All @@ -1482,14 +1465,17 @@ impl App {
println!("Currently scanning for event ID metrics. Please wait.");
} else if stored_static.computer_metrics_flag {
println!("Currently scanning for computer metrics. Please wait.");
} else if stored_static.log_metrics_flag {
println!("Currently scanning for log metrics. Please wait.");
}
println!();

let mut rule_files = vec![];
if !(stored_static.logon_summary_flag
|| stored_static.search_flag
|| stored_static.metrics_flag
|| stored_static.computer_metrics_flag)
|| stored_static.computer_metrics_flag
|| stored_static.log_metrics_flag)
{
rule_files = detection::Detection::parse_rule_files(
&level,
Expand All @@ -1511,7 +1497,8 @@ impl App {
let unused_rules_option = stored_static.logon_summary_flag
|| stored_static.search_flag
|| stored_static.computer_metrics_flag
|| stored_static.metrics_flag;
|| stored_static.metrics_flag
|| stored_static.log_metrics_flag;
if !unused_rules_option && rule_files.is_empty() {
AlertMessage::alert(
"No rules were loaded. Please download the latest rules with the update-rules command.\r\n",
Expand Down Expand Up @@ -1647,12 +1634,15 @@ impl App {
tl.search_dsp_msg(event_timeline_config, stored_static);
} else if stored_static.computer_metrics_flag {
tl.computer_metrics_dsp_msg(stored_static)
} else if stored_static.log_metrics_flag {
tl.log_metrics_dsp_msg(stored_static)
}
if !(stored_static.metrics_flag
|| stored_static.logon_summary_flag
|| stored_static.search_flag
|| stored_static.pivot_keyword_list_flag
|| stored_static.computer_metrics_flag)
|| stored_static.computer_metrics_flag
|| stored_static.log_metrics_flag)
{
let mut log_records = detection.add_aggcondition_msges(&self.rt, stored_static);
if stored_static.is_low_memory {
Expand Down Expand Up @@ -2178,6 +2168,7 @@ impl App {
// 以下のコマンドの際にはルールにかけない
if !(stored_static.metrics_flag
|| stored_static.logon_summary_flag
|| stored_static.log_metrics_flag
|| stored_static.search_flag)
{
// ruleファイルの検知
Expand Down
Loading

0 comments on commit 38da485

Please sign in to comment.