diff --git a/Cargo.lock b/Cargo.lock index bd8e7f5b3..c7b230785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,13 +261,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e2d530f35b40a84124146478cd16f34225306a8441998836466a2e2961c950" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -345,6 +344,15 @@ dependencies = [ "strsim 0.11.1", ] +[[package]] +name = "clap_complete" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b4be9c4c4b1f30b78d8a750e0822b6a6102d97e62061c583a6c1dea2dfb33ae" +dependencies = [ + "clap 4.5.9", +] + [[package]] name = "clap_derive" version = "4.5.8" @@ -832,6 +840,7 @@ dependencies = [ "chrono", "cidr-utils", "clap 4.5.9", + "clap_complete", "comfy-table", "compact_str", "console", @@ -1472,9 +1481,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "ppv-lite86" @@ -1572,9 +1581,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -1963,18 +1972,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -1998,9 +2007,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 5fef1b804..acef2f51c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ bytesize = "1.*" chrono = "0.4.*" cidr-utils = "0.6.*" clap = { version = "4.*", features = ["derive", "cargo", "color"]} +clap_complete = "*" comfy-table = "7.*" compact_str = "0.7.*" console = "0.15.*" diff --git a/rules b/rules index 0a43d7115..718ca07c0 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 0a43d711543ed209af9c901ad7e6baa26373d222 +Subproject commit 718ca07c0352b5d7075c112098f171dabfc33abb diff --git a/src/detections/configs.rs b/src/detections/configs.rs index cdd9e5229..4a5df2dff 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -7,7 +7,9 @@ use crate::options::pivot::PIVOT_KEYWORD; use crate::options::profile::{load_profile, Profile}; use aho_corasick::{AhoCorasick, AhoCorasickBuilder, MatchKind}; use chrono::{DateTime, Days, Duration, Local, Months, Utc}; -use clap::{ArgAction, ArgGroup, Args, ColorChoice, Command, CommandFactory, Parser, Subcommand}; +use clap::{ + ArgAction, ArgGroup, Args, ColorChoice, Command, CommandFactory, Parser, Subcommand, ValueHint, +}; use compact_str::CompactString; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; @@ -111,6 +113,7 @@ impl StoredStatic { _ => false, }; let common_options = match &input_config.as_ref().unwrap().action { + Some(Action::AutoComplete(opt)) => opt.common_options, Some(Action::CsvTimeline(opt)) => opt.output_options.common_options, Some(Action::JsonTimeline(opt)) => opt.output_options.common_options, Some(Action::LevelTuning(opt)) => opt.common_options, @@ -296,6 +299,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::AutoComplete(opt)) => opt.output.as_ref(), _ => None, }; let general_ch_abbr = create_output_filter_config( @@ -884,6 +888,10 @@ pub enum Action { /// List the output profiles ListProfiles(CommonOptions), + #[clap(display_order = 383)] + /// Make the auto complete + AutoComplete(AutoCompleteOptions), + #[clap( author = "Yamato Security (https://github.com/Yamato-Security/hayabusa - @SecurityYamato)", help_template = "\nHayabusa v2.17.0 - Dev Build\n{author-with-newline}\n{usage-heading}\n hayabusa.exe computer-metrics [OPTIONS]\n\n{all-args}", @@ -911,6 +919,7 @@ impl Action { Action::ListProfiles(_) => 9, Action::Search(_) => 10, Action::ComputerMetrics(_) => 11, + Action::AutoComplete(_) => 12, } } else { 100 @@ -931,6 +940,7 @@ impl Action { Action::ListProfiles(_) => "list-profiles", Action::Search(_) => "search", Action::ComputerMetrics(_) => "computer-metrics", + Action::AutoComplete(_) => "auto-complete", } } else { "" @@ -938,6 +948,16 @@ impl Action { } } +#[derive(Args, Clone, Debug)] +pub struct AutoCompleteOptions { + #[clap(flatten)] + pub common_options: CommonOptions, + + /// Save the auto complete in shell format (ex: auto-complete.sh) + #[arg(help_heading = Some("Output"), short = 'o', long, value_name = "FILE", display_order = 410)] + pub output: Option, +} + #[derive(Args, Clone, Debug)] pub struct DetectCommonOption { /// Scan JSON formatted logs instead of .evtx (.json or .jsonl) @@ -945,7 +965,7 @@ pub struct DetectCommonOption { pub json_input: bool, /// Specify additional evtx file extensions (ex: evtx_data) - #[arg(help_heading = Some("General Options"), long = "target-file-ext", value_name = "FILE-EXT...", use_value_delimiter = true, value_delimiter = ',', display_order = 460)] + #[arg(help_heading = Some("General Options"), long = "target-file-ext", value_name = "FILE-EXT...", use_value_delimiter = true, value_delimiter = ',', display_order = 460, value_hint(ValueHint::FilePath))] pub evtx_file_ext: Option>, /// Number of threads (default: optimal number for performance) @@ -970,8 +990,8 @@ pub struct DetectCommonOption { default_value = "./rules/config", hide_default_value = true, value_name = "DIR", - display_order = 442 - )] + display_order = 442, + value_hint(ValueHint::FilePath))] pub config: PathBuf, /// Output verbose information diff --git a/src/main.rs b/src/main.rs index 1bed3e2d4..d39c0463a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,7 @@ use hayabusa::detections::utils::{ check_setting_path, get_writable_color, output_and_data_stack_for_html, output_profile_name, }; use hayabusa::filter::create_channel_filter; +use hayabusa::options::auto_complete::auto_complete; use hayabusa::options::htmlreport::{self, HTML_REPORTER}; use hayabusa::options::pivot::create_output; use hayabusa::options::pivot::PIVOT_KEYWORD; @@ -154,6 +155,13 @@ impl App { println!(); return; } + + //ロゴと時間が表示さないように実行したい + if let Action::AutoComplete(_) = &stored_static.config.action.as_ref().unwrap() { + auto_complete(app, stored_static.output_path.as_ref()); + return; + } + if !stored_static.common_options.quiet { self.output_logo(stored_static); write_color_buffer(&BufferWriter::stdout(ColorChoice::Always), None, "", true).ok(); @@ -352,6 +360,11 @@ impl App { self.print_contributors(); return; } + + Action::AutoComplete(_) => { + panic!("This should not be called here."); + } + Action::LogonSummary(_) => { let mut target_output_path = Nested::::new(); if let Some(path) = &stored_static.output_path { diff --git a/src/options/auto_complete.rs b/src/options/auto_complete.rs new file mode 100644 index 000000000..25423ceb7 --- /dev/null +++ b/src/options/auto_complete.rs @@ -0,0 +1,34 @@ +use std::{env, path::PathBuf}; + +use clap_complete::{generate_to, Generator, Shell}; +use dialoguer::Select; + +pub fn auto_complete(app: &mut clap::Command, output_path: Option<&PathBuf>) { + let shell = select_shell(); + print_completer(shell, app, output_path); +} + +fn select_shell() -> Shell { + let items: Vec = vec![Shell::Bash, Shell::Elvish, Shell::Fish, Shell::PowerShell]; + + let selection = Select::new() + .with_prompt("Which shell are you using?") + .items(&items) + .interact() + .unwrap(); + + items[selection] +} +fn print_completer( + generator: G, + app: &mut clap::Command, + output_path: Option<&PathBuf>, +) { + let mut name = "auto-complete".to_string(); + if output_path.is_some() { + name = output_path.unwrap().to_str().unwrap().to_string(); + } + let out_dir: PathBuf = env::current_dir().expect("can't get current directory"); + + let _ = generate_to(generator, app, name, out_dir); +} diff --git a/src/options/mod.rs b/src/options/mod.rs index acf43098b..3ad324935 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -1,3 +1,4 @@ +pub mod auto_complete; pub mod geoip_search; pub mod htmlreport; pub mod level_tuning;