diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 4038661de..d4544b13d 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -9,6 +9,7 @@ **改善:** - 実行時のメモリ利用率を表示する機能を追加した。`--debug`オプションで利用可能。 (#788) (@fukusuket) +- Clap Crateパッケージの更新。更新の関係で`--visualize-timeline` のショートオプションの`-V`を`-T`に変更した。 (#725) (@YamatoSecurity) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 5836e49ac..dd3a813b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ **Enhancements:** -- Added the ability to display memory utilization at runtime. Available with the `--debug` option. (#788) (@fukusuket) +- Added `--debug` option to display memory utilization at runtime. (#788) (@fukusuket) +- Updated clap crate package to version 4 and changed the `--visualize-timeline` short option `-V` to `-T`. (#725) (@YamatoSecurity) **Bug Fixes:** diff --git a/Cargo.lock b/Cargo.lock index c88e50417..705c2cd3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,20 +216,33 @@ checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", - "clap_derive", - "clap_lex", + "clap_lex 0.2.4", "indexmap", - "once_cell", "strsim", "termcolor", "textwrap", ] +[[package]] +name = "clap" +version = "4.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex 0.3.0", + "once_cell", + "strsim", + "termcolor", +] + [[package]] name = "clap_derive" -version = "3.2.18" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck", "proc-macro-error", @@ -247,6 +260,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -638,7 +660,7 @@ dependencies = [ "bitflags", "byteorder", "chrono", - "clap", + "clap 3.2.23", "crc32fast", "dialoguer", "encoding", @@ -846,7 +868,7 @@ dependencies = [ "base64", "bytesize", "chrono", - "clap", + "clap 4.0.26", "comfy-table", "compact_str", "crossbeam-utils", @@ -1154,7 +1176,7 @@ dependencies = [ "anyhow", "atty", "chrono", - "clap", + "clap 3.2.23", "file-chunker", "memmap2", "num_cpus", diff --git a/Cargo.toml b/Cargo.toml index e98591d7a..fa4ad1cee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ rust-version = "1.65.0" [dependencies] itertools = "*" dashmap = "*" -clap = { version = "3.*", features = ["derive", "cargo"]} +clap = { version = "4.*", features = ["derive", "cargo", "color"]} evtx = { git = "https://github.com/Yamato-Security/hayabusa-evtx.git" , features = ["fast-alloc"] , rev = "f07e263" } # 0.8.3 2022/11/20 update quick-xml = {version = "0.25.*", features = ["serialize"] } serde = { version = "1.*", features = ["derive"] } diff --git a/README-Japanese.md b/README-Japanese.md index dc8536954..bee84bb37 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -42,7 +42,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/) - [スクリーンショット](#スクリーンショット) - [起動画面](#起動画面) - [ターミナル出力画面](#ターミナル出力画面) - - [イベント頻度タイムライン出力画面 (`-V`オプション)](#イベント頻度タイムライン出力画面--vオプション) + - [イベント頻度タイムライン出力画面 (`-T`オプション)](#イベント頻度タイムライン出力画面--tオプション) - [結果サマリ画面](#結果サマリ画面) - [HTMLの結果サマリ (`-H`オプション)](#htmlの結果サマリ--hオプション) - [Excelでの解析](#excelでの解析) @@ -85,7 +85,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/) - [プロファイルの比較](#プロファイルの比較) - [Profile Field Aliases](#profile-field-aliases) - [Levelの省略](#levelの省略) - - [MITRE ATT&CK戦術の省略](#mitre-attck戦術の省略) + - [MITRE ATT\&CK戦術の省略](#mitre-attck戦術の省略) - [Channel情報の省略](#channel情報の省略) - [その他の省略](#その他の省略) - [プログレスバー](#プログレスバー) @@ -134,7 +134,7 @@ Hayabusaは従来のWindowsイベントログ分析解析と比較して、分 ![Hayabusa ターミナル出力画面](screenshots/Hayabusa-Results.png) -## イベント頻度タイムライン出力画面 (`-V`オプション) +## イベント頻度タイムライン出力画面 (`-T`オプション) ![Hayabusa イベント頻度タイムライン出力画面](screenshots/HayabusaEventFrequencyTimeline.png) @@ -394,63 +394,67 @@ macOSの環境設定から「セキュリティとプライバシー」を開き ## コマンドラインオプション ``` -USAGE: - hayabusa.exe [OTHER-ACTIONS] [OPTIONS] - -INPUT: - -d, --directory .evtxファイルを持つディレクトリのパス - -f, --file 1つの.evtxファイルに対して解析を行う - -l, --live-analysis ローカル端末のC:\Windows\System32\winevt\Logsフォルダを解析する - -ADVANCED: - -c, --rules-config ルールフォルダのコンフィグディレクトリ (デフォルト: ./rules/config) - -Q, --quiet-errors Quiet errorsモード: エラーログを保存しない - -r, --rules ルールファイルまたはルールファイルを持つディレクトリ (デフォルト: ./rules) - -t, --thread-number スレッド数 (デフォルト: パフォーマンスに最適な数値) - --target-file-ext ... evtx以外の拡張子を解析対象に追加する。 (例1: evtx_data 例2:evtx1 evtx2) - -OUTPUT: - -H, --html-report HTML形式で詳細な結果を出力する (例: results.html) - -j, --json タイムラインの出力をJSON形式で保存する(例: -j -o results.json) - -J, --jsonl タイムラインの出力をJSONL形式で保存する (例: -J -o results.jsonl) - -o, --output タイムラインをCSV形式で保存する (例: results.csv) - -P, --profile 利用する出力プロファイル名を指定する - -DISPLAY-SETTINGS: - --debug デバッグ情報を出力する (メモリ使用量など) - --no-color カラー出力を無効にする - --no-summary 結果概要を出力しない - -q, --quiet Quietモード: 起動バナーを表示しない - -v, --verbose 詳細な情報を出力する - -V, --visualize-timeline イベント頻度タイムラインを出力する - -FILTERING: - -e, --eid-filter イベントIDによるフィルタリングを行う(コンフィグファイル: ./rules/config/target_event_IDs.txt`) - --enable-deprecated-rules Deprecatedルールを有効にする - --exclude-status ... 読み込み対象外とするルール内でのステータス (ex: experimental) (ex: stable test) - -m, --min-level 結果出力をするルールの最低レベル (デフォルト: informational) - -n, --enable-noisy-rules Noisyルールを有効にする - --timeline-end 解析対象とするイベントログの終了時刻 (例: "2022-02-22 23:59:59 +09:00") - --timeline-start 解析対象とするイベントログの開始時刻 (例: "2020-02-22 00:00:00 +09:00") - -OTHER-ACTIONS: - --contributors コントリビュータの一覧表示 - -L, --logon-summary 成功と失敗したログオン情報の要約を出力する - --level-tuning [] ルールlevelのチューニング (デフォルト: ./rules/config/level_tuning.txt) - --list-profiles 利用可能な出力プロファイル名を出力する - -M, --metrics イベントIDの統計情報を表示する - -p, --pivot-keywords-list ピボットキーワードの一覧作成 - --set-default-profile デフォルトの出力コンフィグを設定する - -u, --update-rules rulesフォルダをhayabusa-rulesのgithubリポジトリの最新版に更新する - -TIME-FORMAT: - --European-time ヨーロッパ形式で日付と時刻を出力する (例: 22-02-2022 22:00:00.123 +02:00) - --ISO-8601 ISO-8601形式で日付と時刻を出力する (ex: 2022-02-22T10:10:10.1234567Z) (Always UTC) - --RFC-2822 RFC 2822形式で日付と時刻を出力する (例: Fri, 22 Feb 2022 22:00:00 -0600) - --RFC-3339 RFC 3339形式で日付と時刻を出力する (例: 2022-02-22 22:00:00.123456-06:00) - --US-military-time 24時間制(ミリタリータイム)のアメリカ形式で日付と時刻を出力する (例: 02-22-2022 22:00:00.123 -06:00) - --US-time アメリカ形式で日付と時刻を出力する (例: 02-22-2022 10:00:00.123 PM -06:00) - -U, --UTC UTC形式で日付と時刻を出力する (デフォルト: 現地時間) +Usage: + hayabusa.exe [OTHER-ACTIONS] [OUTPUT] [OPTIONS] + +Options: + -h, --help ヘルプ画面の表示 + -V, --version バージョンの表示 + +Input: + -d, --directory .evtxファイルを持つディレクトリのパス + -f, --file 1つの.evtxファイルに対して解析を行う + -l, --live-analysis ローカル端末のC:\Windows\System32\winevt\Logsフォルダを解析する + +Advanced: + -c, --rules-config ルールフォルダのコンフィグディレクトリ (デフォルト: ./rules/config) + -Q, --quiet-errors Quiet errorsモード: エラーログを保存しない + -r, --rules ルールファイルまたはルールファイルを持つディレクトリ (デフォルト: ./rules) + -t, --thread-number スレッド数 (デフォルト: パフォーマンスに最適な数値) + --target-file-ext evtx以外の拡張子を解析対象に追加する。 (例1: evtx_data 例2:evtx1,evtx2) + +Output: + -H, --html-report HTML形式で詳細な結果を出力する (例: results.html) + -j, --json タイムラインの出力をJSON形式で保存する(例: -j -o results.json) + -J, --jsonl タイムラインの出力をJSONL形式で保存する (例: -J -o results.jsonl) + -o, --output タイムラインをCSV形式で保存する (例: results.csv) + -P, --profile 利用する出力プロファイル名を指定する + +Display Settings: + --no-color カラー出力を無効にする + --no-summary 結果概要を出力しない + -q, --quiet Quietモード: 起動バナーを表示しない + -v, --verbose 詳細な情報を出力する + -T, --visualize-timeline イベント頻度タイムラインを出力する + --debug デバッグ情報を出力する (メモリ使用量など) + +Filtering: + -e, --eid-filter イベントIDによるフィルタリングを行う(コンフィグファイル: ./rules/config/target_event_IDs.txt) + --enable-deprecated-rules Deprecatedルールを有効にする + --exclude-status 読み込み対象外とするルール内でのステータス (ex: experimental) (ex: stable,test) + -m, --min-level 結果出力をするルールの最低レベル (デフォルト: informational) + -n, --enable-noisy-rules Noisyルールを有効にする + --timeline-end 解析対象とするイベントログの終了時刻 (例: "2022-02-22 23:59:59 +09:00") + --timeline-start 解析対象とするイベントログの開始時刻 (例: "2020-02-22 00:00:00 +09:00") + +Other Actions: + --contributors コントリビュータの一覧表示 + -L, --logon-summary 成功と失敗したログオン情報の要約を出力する + --level-tuning [] ルールlevelのチューニング (デフォルト: ./rules/config/level_tuning.txt) + --list-profiles 利用可能な出力プロファイル名を出力する + -M, --metrics イベントIDの統計情報を表示する + -p, --pivot-keywords-list ピボットキーワードの一覧作成 + --set-default-profile デフォルトの出力コンフィグを設定する + -u, --update-rules rulesフォルダをhayabusa-rulesのgithubリポジトリの最新版に更新する + +Time Format: + --European-time ヨーロッパ形式で日付と時刻を出力する (例: 22-02-2022 22:00:00.123 +02:00) + --ISO-8601 ISO-8601形式で日付と時刻を出力する (ex: 2022-02-22T10:10:10.1234567Z) (いつもUTC) + --RFC-2822 RFC 2822形式で日付と時刻を出力する (例: Fri, 22 Feb 2022 22:00:00 -0600) + --RFC-3339 RFC 3339形式で日付と時刻を出力する (例: 2022-02-22 22:00:00.123456-06:00) + --US-military-time 24時間制(ミリタリータイム)のアメリカ形式で日付と時刻を出力する (例: 02-22-2022 22:00:00.123 -06:00) + --US-time アメリカ形式で日付と時刻を出力する (例: 02-22-2022 10:00:00.123 PM -06:00) + -U, --UTC UTC形式で日付と時刻を出力する (デフォルト: 現地時間) ``` ## 使用例 @@ -845,7 +849,7 @@ Hayabusaの結果は`level`毎に文字色が変わります。 ### イベント頻度タイムライン -`-V`または`--visualize-timeline`オプションを使うことで、検知したイベントの数が5以上の時、頻度のタイムライン(スパークライン)を画面に出力します。 +`-T`または`--visualize-timeline`オプションを使うことで、検知したイベントの数が5以上の時、頻度のタイムライン(スパークライン)を画面に出力します。 マーカーの数は最大10個です。デフォルトのCommand PromptとPowerShell Promptでは文字化けがでるので、Windows TerminalやiTerm2等のターミナルをご利用ください。 # Hayabusaルール diff --git a/README.md b/README.md index 04bf105ea..aa3c0555b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre - [Screenshots](#screenshots) - [Startup](#startup) - [Terminal Output](#terminal-output) - - [Event Fequency Timeline (`-V` option)](#event-fequency-timeline--v-option) + - [Event Fequency Timeline (`-T` option)](#event-fequency-timeline--t-option) - [Results Summary](#results-summary) - [HTML Results Summary (`-H` option)](#html-results-summary--h-option) - [Analysis in Excel](#analysis-in-excel) @@ -85,7 +85,7 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre - [Profile Comparison](#profile-comparison) - [Profile Field Aliases](#profile-field-aliases) - [Level Abbrevations](#level-abbrevations) - - [MITRE ATT&CK Tactics Abbreviations](#mitre-attck-tactics-abbreviations) + - [MITRE ATT\&CK Tactics Abbreviations](#mitre-attck-tactics-abbreviations) - [Channel Abbreviations](#channel-abbreviations) - [Other Abbreviations](#other-abbreviations) - [Progress Bar](#progress-bar) @@ -129,7 +129,7 @@ Hayabusa hopes to let analysts get 80% of their work done in 20% of the time whe ![Hayabusa terminal output](screenshots/Hayabusa-Results.png) -## Event Fequency Timeline (`-V` option) +## Event Fequency Timeline (`-T` option) ![Hayabusa Event Frequency Timeline](screenshots/HayabusaEventFrequencyTimeline.png) @@ -389,63 +389,67 @@ You should now be able to run hayabusa. ## Command Line Options ``` -USAGE: - hayabusa.exe [OTHER-ACTIONS] [OPTIONS] - -INPUT: - -d, --directory Directory of multiple .evtx files - -f, --file File path to one .evtx file - -l, --live-analysis Analyze the local C:\Windows\System32\winevt\Logs folder - -ADVANCED: - -c, --rules-config Specify custom rule config directory (default: ./rules/config) - -Q, --quiet-errors Quiet errors mode: do not save error logs - -r, --rules Specify a custom rule directory or file (default: ./rules) - -t, --thread-number Thread number (default: optimal number for performance) - --target-file-ext ... Specify additional target file extensions (ex: evtx_data) (ex: evtx1 evtx2) - -OUTPUT: - -H, --html-report Save detail Results Summary in html (ex: results.html) - -j, --json Save the timeline in JSON format (ex: -j -o results.json) - -J, --jsonl Save the timeline in JSONL format (ex: -J -o results.jsonl) - -o, --output Save the timeline in CSV format (ex: results.csv) - -P, --profile Specify output profile - -DISPLAY-SETTINGS: - --debug Print debug information (memory usage, etc...) - --no-color Disable color output - --no-summary Do not display result summary - -q, --quiet Quiet mode: do not display the launch banner - -v, --verbose Output verbose information - -V, --visualize-timeline Output event frequency timeline - -FILTERING: - -e, --eid-filter Filter by Event IDs (config file: ./rules/config/target_event_IDs.txt) - --enable-deprecated-rules Enable rules marked as deprecated - --exclude-status ... Ignore rules according to status (ex: experimental) (ex: stable test) - -m, --min-level Minimum level for rules (default: informational) - -n, --enable-noisy-rules Enable rules marked as noisy - --timeline-end End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00") - --timeline-start Start time of the event logs to load (ex: "2020-02-22 00:00:00 +09:00") - -OTHER-ACTIONS: - --contributors Print the list of contributors - -L, --logon-summary Print a summary of successful and failed logons - --level-tuning [] Tune alert levels (default: ./rules/config/level_tuning.txt) - --list-profiles List the output profiles - -M, --metrics Print event ID metrics - -p, --pivot-keywords-list Create a list of pivot keywords - --set-default-profile Set default output profile - -u, --update-rules Update to the latest rules in the hayabusa-rules github repository - -TIME-FORMAT: - --European-time Output timestamp in European time format (ex: 22-02-2022 22:00:00.123 +02:00) - --ISO-8601 Output timestamp in ISO-8601 format (ex: 2022-02-22T10:10:10.1234567Z) (Always UTC) - --RFC-2822 Output timestamp in RFC 2822 format (ex: Fri, 22 Feb 2022 22:00:00 -0600) - --RFC-3339 Output timestamp in RFC 3339 format (ex: 2022-02-22 22:00:00.123456-06:00) - --US-military-time Output timestamp in US military time format (ex: 02-22-2022 22:00:00.123 -06:00) - --US-time Output timestamp in US time format (ex: 02-22-2022 10:00:00.123 PM -06:00) - -U, --UTC Output time in UTC format (default: local time) +Usage: + hayabusa.exe [OTHER-ACTIONS] [OUTPUT] [OPTIONS] + +Options: + -h, --help Print help information + -V, --version Print version information + +Input: + -d, --directory Directory of multiple .evtx files + -f, --file File path to one .evtx file + -l, --live-analysis Analyze the local C:\Windows\System32\winevt\Logs folder + +Advanced: + -c, --rules-config Specify custom rule config directory (default: ./rules/config) + -Q, --quiet-errors Quiet errors mode: do not save error logs + -r, --rules Specify a custom rule directory or file (default: ./rules) + -t, --thread-number Thread number (default: optimal number for performance) + --target-file-ext Specify additional target file extensions (ex: evtx_data) (ex: evtx1,evtx2) + +Output: + -H, --html-report Save detail Results Summary in html (ex: results.html) + -j, --json Save the timeline in JSON format (ex: -j -o results.json) + -J, --jsonl Save the timeline in JSONL format (ex: -J -o results.jsonl) + -o, --output Save the timeline in CSV format (ex: results.csv) + -P, --profile Specify output profile + +Display Settings: + --no-color Disable color output + --no-summary Do not display result summary + -q, --quiet Quiet mode: do not display the launch banner + -v, --verbose Output verbose information + -T, --visualize-timeline Output event frequency timeline + --debug Print debug information (memory usage, etc...) + +Filtering: + -e, --eid-filter Filter by Event IDs (config file: ./rules/config/target_event_IDs.txt) + --enable-deprecated-rules Enable rules marked as deprecated + --exclude-status Ignore rules according to status (ex: experimental) (ex: stable,test) + -m, --min-level Minimum level for rules (default: informational) + -n, --enable-noisy-rules Enable rules marked as noisy + --timeline-end End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00") + --timeline-start Start time of the event logs to load (ex: "2020-02-22 00:00:00 +09:00") + +Other Actions: + --contributors Print the list of contributors + -L, --logon-summary Print a summary of successful and failed logons + --level-tuning [] Tune alert levels (default: ./rules/config/level_tuning.txt) + --list-profiles List the output profiles + -M, --metrics Print event ID metrics + -p, --pivot-keywords-list Create a list of pivot keywords + --set-default-profile Set default output profile + -u, --update-rules Update to the latest rules in the hayabusa-rules github repository + +Time Format: + --European-time Output timestamp in European time format (ex: 22-02-2022 22:00:00.123 +02:00) + --ISO-8601 Output timestamp in ISO-8601 format (ex: 2022-02-22T10:10:10.1234567Z) (Always UTC) + --RFC-2822 Output timestamp in RFC 2822 format (ex: Fri, 22 Feb 2022 22:00:00 -0600) + --RFC-3339 Output timestamp in RFC 3339 format (ex: 2022-02-22 22:00:00.123456-06:00) + --US-military-time Output timestamp in US military time format (ex: 02-22-2022 22:00:00.123 -06:00) + --US-time Output timestamp in US time format (ex: 02-22-2022 10:00:00.123 PM -06:00) + -U, --UTC Output time in UTC format (default: local time) ``` ## Usage Examples @@ -839,7 +843,7 @@ Total events, the number of events with hits, data reduction metrics, total and ### Event Fequency Timeline -If you add `-V` or `--visualize-timeline` option, the Event Frequency Timeline feature displays a sparkline frequency timeline of detected events. +If you add `-T` or `--visualize-timeline` option, the Event Frequency Timeline feature displays a sparkline frequency timeline of detected events. Note: There needs to be more than 5 events. Also, the characters will not render correctly on the default Command Prompt or PowerShell Prompt, so please use a terminal like Windows Terminal, iTerm2, etc... # Hayabusa Rules diff --git a/art/logo.txt b/art/logo.txt index 1c52500b7..b308079b5 100644 --- a/art/logo.txt +++ b/art/logo.txt @@ -5,4 +5,4 @@ ║╔═╗║╚═╝║╚╗╔╝║╚═╝║╔═╗║║ ║╠══╗║╚═╝║ ║║ ║║╔═╗║ ║║ ║╔═╗║╚═╝║╚═╝║╚═╝║╔═╗║ ╚╝ ╚╩╝ ╚╝ ╚╝ ╚╝ ╚╩═══╩═══╩═══╩╝ ╚╝ - by Yamato Security \ No newline at end of file + by Yamato Security diff --git a/src/afterfact.rs b/src/afterfact.rs index a5f41a79a..404355923 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -49,7 +49,7 @@ pub fn set_output_color() -> HashMap { .unwrap(), ); let mut color_map: HashMap = HashMap::new(); - if configs::CONFIG.read().unwrap().args.no_color { + if configs::CONFIG.read().unwrap().no_color { return color_map; } if read_result.is_err() { @@ -170,7 +170,7 @@ pub fn after_fact(all_record_cnt: usize) { let mut displayflag = false; let mut target: Box = - if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output { + if let Some(csv_path) = &configs::CONFIG.read().unwrap().output { // output to file match File::create(csv_path) { Ok(file) => Box::new(BufWriter::new(file)), @@ -207,9 +207,9 @@ fn emit_csv( let html_output_flag = *HTML_REPORT_FLAG; let disp_wtr = BufferWriter::stdout(ColorChoice::Always); let mut disp_wtr_buf = disp_wtr.buffer(); - let json_output_flag = configs::CONFIG.read().unwrap().args.json_timeline; - let jsonl_output_flag = configs::CONFIG.read().unwrap().args.jsonl_timeline; - let is_no_summary = configs::CONFIG.read().unwrap().args.no_summary; + let json_output_flag = configs::CONFIG.read().unwrap().json_timeline; + let jsonl_output_flag = configs::CONFIG.read().unwrap().jsonl_timeline; + let is_no_summary = configs::CONFIG.read().unwrap().no_summary; let mut wtr = if json_output_flag || jsonl_output_flag { WriterBuilder::new() @@ -427,7 +427,7 @@ fn emit_csv( }; println!(); - if configs::CONFIG.read().unwrap().args.visualize_timeline { + if configs::CONFIG.read().unwrap().visualize_timeline { _print_timeline_hist(timestamps, terminal_width, 3); println!(); } @@ -674,7 +674,7 @@ fn _print_unique_results( } else { (unique_counts_by_level[i] as f64) / (unique_total_count as f64) * 100.0 }; - if configs::CONFIG.read().unwrap().args.html_report.is_some() { + if configs::CONFIG.read().unwrap().html_report.is_some() { total_detect_md.push(format!( " - {}: {} ({:.2}%)", level_name[0], @@ -706,7 +706,7 @@ fn _print_unique_results( ) .ok(); } - if configs::CONFIG.read().unwrap().args.html_report.is_some() { + if configs::CONFIG.read().unwrap().html_report.is_some() { html_output_stock.extend(total_detect_md.iter()); html_output_stock.extend(unique_detect_md.iter()); } @@ -949,7 +949,7 @@ fn _print_detection_summary_tables( /// get timestamp to input datetime. fn _get_timestamp(time: &DateTime) -> i64 { - if configs::CONFIG.read().unwrap().args.utc || configs::CONFIG.read().unwrap().args.iso_8601 { + if configs::CONFIG.read().unwrap().utc || configs::CONFIG.read().unwrap().iso_8601 { time.timestamp() } else { let offset_sec = Local diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 4ccfffa70..fc531b631 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -2,7 +2,7 @@ use crate::detections::message::AlertMessage; use crate::detections::pivot::{PivotKeyword, PIVOT_KEYWORD}; use crate::detections::utils; use chrono::{DateTime, Utc}; -use clap::{App, CommandFactory, Parser}; +use clap::{ColorChoice, Command, CommandFactory, Parser}; use hashbrown::{HashMap, HashSet}; use lazy_static::lazy_static; use nested::Nested; @@ -13,76 +13,64 @@ use std::sync::RwLock; use terminal_size::{terminal_size, Width}; lazy_static! { - pub static ref CONFIG: RwLock> = RwLock::new(ConfigReader::new()); + pub static ref CONFIG: RwLock = RwLock::new(Config::parse()); + pub static ref CURRENT_EXE_PATH: PathBuf = + current_exe().unwrap().parent().unwrap().to_path_buf(); pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias( - utils::check_setting_path( - &CONFIG.read().unwrap().args.config, - "eventkey_alias.txt", - false - ) - .unwrap_or_else(|| { - utils::check_setting_path( - &CURRENT_EXE_PATH.to_path_buf(), - "rules/config/eventkey_alias.txt", - true, - ) + utils::check_setting_path(&CONFIG.read().unwrap().config, "eventkey_alias.txt", false) + .unwrap_or_else(|| { + utils::check_setting_path( + &CURRENT_EXE_PATH.to_path_buf(), + "rules/config/eventkey_alias.txt", + true, + ) + .unwrap() + }) + .to_str() .unwrap() - }) - .to_str() - .unwrap() ); pub static ref IDS_REGEX: Regex = Regex::new(r"^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$").unwrap(); - pub static ref CURRENT_EXE_PATH: PathBuf = - current_exe().unwrap().parent().unwrap().to_path_buf(); } -pub struct ConfigReader<'a> { - pub app: App<'a>, - pub args: Config, +pub struct ConfigReader { + pub app: Command, pub headless_help: String, pub event_timeline_config: EventInfoConfig, pub target_eventids: TargetEventIds, } -impl Default for ConfigReader<'_> { +impl Default for ConfigReader { fn default() -> Self { Self::new() } } #[derive(Parser, Clone)] -#[clap( +#[command( name = "Hayabusa", - usage = "hayabusa.exe [OTHER-ACTIONS] [OPTIONS]", + override_usage = "hayabusa.exe [OTHER-ACTIONS] [OUTPUT] [OPTIONS]", author = "Yamato Security (https://github.com/Yamato-Security/hayabusa) @SecurityYamato)", - help_template = "\n{name} {version}\n{author}\n\n{usage-heading}\n {usage}\n\n{all-args}\n", + help_template = "\n{name} {version}\n{author}\n\n{usage-heading}\n {usage}\n\n{all-args}", version, term_width = 400 )] pub struct Config { /// Directory of multiple .evtx files - #[clap(help_heading = Some("INPUT"), short = 'd', long, value_name = "DIRECTORY")] + #[arg(help_heading = Some("Input"), short = 'd', long, value_name = "DIRECTORY")] pub directory: Option, /// File path to one .evtx file - #[clap(help_heading = Some("INPUT"), short = 'f', long = "file", value_name = "FILE")] + #[arg(help_heading = Some("Input"), short = 'f', long = "file", value_name = "FILE")] pub filepath: Option, - /// Specify a custom rule directory or file (default: ./rules) - #[clap( - help_heading = Some("ADVANCED"), - short = 'r', - long, - default_value = "./rules", - hide_default_value = true, - value_name = "DIRECTORY/FILE" - )] - pub rules: PathBuf, + /// Analyze the local C:\Windows\System32\winevt\Logs folder + #[arg(help_heading = Some("Input"), short = 'l', long = "live-analysis")] + pub live_analysis: bool, /// Specify custom rule config directory (default: ./rules/config) - #[clap( - help_heading = Some("ADVANCED"), + #[arg( + help_heading = Some("Advanced"), short = 'c', long = "rules-config", default_value = "./rules/config", @@ -91,37 +79,88 @@ pub struct Config { )] pub config: PathBuf, + /// Quiet errors mode: do not save error logs + #[arg(help_heading = Some("Advanced"), short = 'Q', long = "quiet-errors")] + pub quiet_errors: bool, + + /// Specify a custom rule directory or file (default: ./rules) + #[arg( + help_heading = Some("Advanced"), + short = 'r', + long, + default_value = "./rules", + hide_default_value = true, + value_name = "DIRECTORY/FILE" + )] + pub rules: PathBuf, + + /// Thread number (default: optimal number for performance) + #[arg(help_heading = Some("Advanced"), short, long = "thread-number", value_name = "NUMBER")] + pub thread_number: Option, + + /// Specify additional target file extensions (ex: evtx_data) (ex: evtx1,evtx2) + #[arg(help_heading = Some("Advanced"), long = "target-file-ext", use_value_delimiter = true, value_delimiter = ',')] + pub evtx_file_ext: Option>, + + /// Save detail Results Summary in html (ex: results.html) + #[arg(help_heading = Some("Output"), short = 'H', long="html-report", value_name = "FILE")] + pub html_report: Option, + + /// Save the timeline in JSON format (ex: -j -o results.json) + #[arg(help_heading = Some("Output"), short = 'j', long = "json", requires = "output")] + pub json_timeline: bool, + + /// Save the timeline in JSONL format (ex: -J -o results.jsonl) + #[arg(help_heading = Some("Output"), short = 'J', long = "jsonl", requires = "output")] + pub jsonl_timeline: bool, + /// Save the timeline in CSV format (ex: results.csv) - #[clap(help_heading = Some("OUTPUT"), short = 'o', long, value_name = "FILE")] + #[arg(help_heading = Some("Output"), short = 'o', long, value_name = "FILE")] pub output: Option, + /// Specify output profile + #[arg(help_heading = Some("Output"), short = 'P', long = "profile")] + pub profile: Option, + + /// Disable color output + #[arg(help_heading = Some("Display Settings"), long = "no-color")] + pub no_color: bool, + + /// Do not display result summary + #[arg(help_heading = Some("Display Settings"), long = "no-summary")] + pub no_summary: bool, + + /// Quiet mode: do not display the launch banner + #[arg(help_heading = Some("Display Settings"), short, long)] + pub quiet: bool, + /// Output verbose information - #[clap(help_heading = Some("DISPLAY-SETTINGS"), short = 'v', long)] + #[arg(help_heading = Some("Display Settings"), short = 'v', long)] pub verbose: bool, /// Output event frequency timeline - #[clap(help_heading = Some("DISPLAY-SETTINGS"), short = 'V', long = "visualize-timeline")] + #[arg(help_heading = Some("Display Settings"), short = 'T', long = "visualize-timeline")] pub visualize_timeline: bool, - /// Enable rules marked as deprecated - #[clap(help_heading = Some("FILTERING"), long = "enable-deprecated-rules")] - pub enable_deprecated_rules: bool, + /// Print debug information (memory usage, etc...) + #[clap(help_heading = Some("Display Settings"), long = "debug")] + pub debug: bool, /// Filter by Event IDs (config file: ./rules/config/target_event_IDs.txt) - #[clap(help_heading = Some("FILTERING"), short = 'e', long = "eid-filter")] + #[arg(help_heading = Some("Filtering"), short = 'e', long = "eid-filter")] pub eid_filter: bool, - /// Enable rules marked as noisy - #[clap(help_heading = Some("FILTERING"), short = 'n', long = "enable-noisy-rules")] - pub enable_noisy_rules: bool, + /// Enable rules marked as deprecated + #[arg(help_heading = Some("Filtering"), long = "enable-deprecated-rules")] + pub enable_deprecated_rules: bool, - /// Update to the latest rules in the hayabusa-rules github repository - #[clap(help_heading = Some("OTHER-ACTIONS"), short = 'u', long = "update-rules")] - pub update_rules: bool, + /// Ignore rules according to status (ex: experimental) (ex: stable,test) + #[arg(help_heading = Some("Filtering"), long = "exclude-status", value_name = "STATUS", use_value_delimiter = true, value_delimiter = ',')] + pub exclude_status: Option>, /// Minimum level for rules (default: informational) - #[clap( - help_heading = Some("FILTERING"), + #[arg( + help_heading = Some("Filtering"), short = 'm', long = "min-level", default_value = "informational", @@ -130,129 +169,85 @@ pub struct Config { )] pub min_level: String, - /// Analyze the local C:\Windows\System32\winevt\Logs folder - #[clap(help_heading = Some("INPUT"), short = 'l', long = "live-analysis")] - pub live_analysis: bool, - - /// Start time of the event logs to load (ex: "2020-02-22 00:00:00 +09:00") - #[clap(help_heading = Some("FILTERING"), long = "timeline-start", value_name = "DATE")] - pub start_timeline: Option, + /// Enable rules marked as noisy + #[arg(help_heading = Some("Filtering"), short = 'n', long = "enable-noisy-rules")] + pub enable_noisy_rules: bool, /// End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00") - #[clap(help_heading = Some("FILTERING"), long = "timeline-end", value_name = "DATE")] + #[arg(help_heading = Some("Filtering"), long = "timeline-end", value_name = "DATE")] pub end_timeline: Option, - /// Output timestamp in RFC 2822 format (ex: Fri, 22 Feb 2022 22:00:00 -0600) - #[clap(help_heading = Some("TIME-FORMAT"), long = "RFC-2822")] - pub rfc_2822: bool, - - /// Output timestamp in RFC 3339 format (ex: 2022-02-22 22:00:00.123456-06:00) - #[clap(help_heading = Some("TIME-FORMAT"), long = "RFC-3339")] - pub rfc_3339: bool, - - /// Output timestamp in US time format (ex: 02-22-2022 10:00:00.123 PM -06:00) - #[clap(help_heading = Some("TIME-FORMAT"), long = "US-time")] - pub us_time: bool, - - /// Output timestamp in ISO-8601 format (ex: 2022-02-22T10:10:10.1234567Z) (Always UTC) - #[clap(help_heading = Some("TIME-FORMAT"), long = "ISO-8601")] - pub iso_8601: bool, - - /// Output timestamp in US military time format (ex: 02-22-2022 22:00:00.123 -06:00) - #[clap(help_heading = Some("TIME-FORMAT"), long = "US-military-time")] - pub us_military_time: bool, - - /// Output timestamp in European time format (ex: 22-02-2022 22:00:00.123 +02:00) - #[clap(help_heading = Some("TIME-FORMAT"), long = "European-time")] - pub european_time: bool, - - /// Output time in UTC format (default: local time) - #[clap(help_heading = Some("TIME-FORMAT"), short = 'U', long = "UTC")] - pub utc: bool, - - /// Disable color output - #[clap(help_heading = Some("DISPLAY-SETTINGS"), long = "no-color")] - pub no_color: bool, - - /// Thread number (default: optimal number for performance) - #[clap(help_heading = Some("ADVANCED"), short, long = "thread-number", value_name = "NUMBER")] - pub thread_number: Option, + /// Start time of the event logs to load (ex: "2020-02-22 00:00:00 +09:00") + #[arg(help_heading = Some("Filtering"), long = "timeline-start", value_name = "DATE")] + pub start_timeline: Option, - /// Print event ID metrics - #[clap(help_heading = Some("OTHER-ACTIONS"), short='M', long)] - pub metrics: bool, + /// Print the list of contributors + #[arg(help_heading = Some("Other Actions"), long)] + pub contributors: bool, /// Print a summary of successful and failed logons - #[clap(help_heading = Some("OTHER-ACTIONS"), short = 'L', long = "logon-summary")] + #[arg(help_heading = Some("Other Actions"), short = 'L', long = "logon-summary")] pub logon_summary: bool, /// Tune alert levels (default: ./rules/config/level_tuning.txt) - #[clap( - help_heading = Some("OTHER-ACTIONS"), + #[arg( + help_heading = Some("Other Actions"), long = "level-tuning", hide_default_value = true, value_name = "FILE" )] pub level_tuning: Option>, - /// Quiet mode: do not display the launch banner - #[clap(help_heading = Some("DISPLAY-SETTINGS"), short, long)] - pub quiet: bool, + /// List the output profiles + #[arg(help_heading = Some("Other Actions"), long = "list-profiles")] + pub list_profile: bool, - /// Quiet errors mode: do not save error logs - #[clap(help_heading = Some("ADVANCED"), short = 'Q', long = "quiet-errors")] - pub quiet_errors: bool, + /// Print event ID metrics + #[arg(help_heading = Some("Other Actions"), short='M', long)] + pub metrics: bool, /// Create a list of pivot keywords - #[clap(help_heading = Some("OTHER-ACTIONS"), short = 'p', long = "pivot-keywords-list")] + #[arg(help_heading = Some("Other Actions"), short = 'p', long = "pivot-keywords-list")] pub pivot_keywords_list: bool, - /// Print the list of contributors - #[clap(help_heading = Some("OTHER-ACTIONS"), long)] - pub contributors: bool, - - /// Specify additional target file extensions (ex: evtx_data) (ex: evtx1 evtx2) - #[clap(help_heading = Some("ADVANCED"), long = "target-file-ext", multiple_values = true)] - pub evtx_file_ext: Option>, - - /// Ignore rules according to status (ex: experimental) (ex: stable test) - #[clap(help_heading = Some("FILTERING"), long = "exclude-status", multiple_values = true, value_name = "STATUS")] - pub exclude_status: Option>, - - /// Specify output profile - #[clap(help_heading = Some("OUTPUT"), short = 'P', long = "profile")] - pub profile: Option, - /// Set default output profile - #[clap(help_heading = Some("OTHER-ACTIONS"), long = "set-default-profile", value_name = "PROFILE")] + #[arg(help_heading = Some("Other Actions"), long = "set-default-profile", value_name = "PROFILE")] pub set_default_profile: Option, - /// List the output profiles - #[clap(help_heading = Some("OTHER-ACTIONS"), long = "list-profiles")] - pub list_profile: bool, + /// Update to the latest rules in the hayabusa-rules github repository + #[arg(help_heading = Some("Other Actions"), short = 'u', long = "update-rules")] + pub update_rules: bool, - /// Save the timeline in JSON format (ex: -j -o results.json) - #[clap(help_heading = Some("OUTPUT"), short = 'j', long = "json", requires = "output")] - pub json_timeline: bool, + /// 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")] + pub european_time: bool, - /// Save the timeline in JSONL format (ex: -J -o results.jsonl) - #[clap(help_heading = Some("OUTPUT"), short = 'J', long = "jsonl", requires = "output")] - pub jsonl_timeline: bool, + /// Output timestamp in ISO-8601 format (ex: 2022-02-22T10:10:10.1234567Z) (Always UTC) + #[arg(help_heading = Some("Time Format"), long = "ISO-8601")] + pub iso_8601: bool, - /// Do not display result summary - #[clap(help_heading = Some("DISPLAY-SETTINGS"), long = "no-summary")] - pub no_summary: 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")] + pub rfc_2822: bool, - /// Save detail Results Summary in html (ex: results.html) - #[clap(help_heading = Some("OUTPUT"), short = 'H', long="html-report", value_name = "FILE")] - pub html_report: Option, + /// 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")] + pub rfc_3339: bool, - /// Print debug information (memory usage, etc...) - #[clap(help_heading = Some("DISPLAY-SETTINGS"), long = "debug")] - pub debug: 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")] + 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")] + pub us_time: bool, + + /// Output time in UTC format (default: local time) + #[arg(help_heading = Some("Time Format"), short = 'U', long = "UTC")] + pub utc: bool, } -impl ConfigReader<'_> { +impl ConfigReader { pub fn new() -> Self { let parse = Config::parse(); let help_term_width = if let Some((Width(w), _)) = terminal_size() { @@ -261,11 +256,10 @@ impl ConfigReader<'_> { 400 }; let build_cmd = Config::command() - .term_width(help_term_width) - .help_template("\n\nUSAGE:\n {usage}\n\nOPTIONS:\n{options}"); + .color(ColorChoice::Auto) + .term_width(help_term_width); ConfigReader { app: build_cmd, - args: parse.to_owned(), headless_help: String::default(), event_timeline_config: load_eventcode_info( utils::check_setting_path(&parse.config, "channel_eid_info.txt", false) @@ -358,7 +352,7 @@ impl Default for TargetEventTime { impl TargetEventTime { pub fn new() -> Self { let mut parse_success_flag = true; - let start_time = if let Some(s_time) = &CONFIG.read().unwrap().args.start_timeline { + let start_time = if let Some(s_time) = &CONFIG.read().unwrap().start_timeline { match DateTime::parse_from_str(s_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00 .or_else(|_| DateTime::parse_from_str(s_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00 { @@ -375,7 +369,7 @@ impl TargetEventTime { } else { None }; - let end_time = if let Some(e_time) = &CONFIG.read().unwrap().args.end_timeline { + let end_time = if let Some(e_time) = &CONFIG.read().unwrap().end_timeline { match DateTime::parse_from_str(e_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00 .or_else(|_| DateTime::parse_from_str(e_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00 { diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 6a5b96d8f..dbc9f7f7a 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -76,7 +76,7 @@ impl Detection { let result_readdir = rulefile_loader.read_dir(rulespath, &level, exclude_ids); if result_readdir.is_err() { let errmsg = format!("{}", result_readdir.unwrap_err()); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -98,7 +98,7 @@ impl Detection { err_msgs_result.err().iter().for_each(|err_msgs| { let errmsg_body = format!("Failed to parse rule file. (FilePath : {})", rule.rulepath); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::warn(&errmsg_body).ok(); err_msgs.iter().for_each(|err_msg| { @@ -759,7 +759,7 @@ impl Detection { } let mut sorted_ld_rc: Vec<(&String, &u128)> = ld_rc.iter().collect(); sorted_ld_rc.sort_by(|a, b| a.0.cmp(b.0)); - let args = &configs::CONFIG.read().unwrap().args; + let args = configs::CONFIG.read().unwrap(); let mut html_report_stock = Nested::::new(); sorted_ld_rc.into_iter().for_each(|(key, value)| { diff --git a/src/detections/message.rs b/src/detections/message.rs index 2f89203f1..cd9d58c9d 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -41,17 +41,17 @@ lazy_static! { pub static ref MESSAGEKEYS: Mutex>> = Mutex::new(HashSet::new()); pub static ref ALIASREGEX: Regex = Regex::new(r"%[a-zA-Z0-9-_\[\]]+%").unwrap(); pub static ref SUFFIXREGEX: Regex = Regex::new(r"\[([0-9]+)\]").unwrap(); - pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG.read().unwrap().args.quiet_errors; + pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG.read().unwrap().quiet_errors; pub static ref ERROR_LOG_STACK: Mutex> = Mutex::new(Nested::::new()); - pub static ref METRICS_FLAG: bool = configs::CONFIG.read().unwrap().args.metrics; - pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().args.logon_summary; + pub static ref METRICS_FLAG: bool = configs::CONFIG.read().unwrap().metrics; + pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().logon_summary; pub static ref TAGS_CONFIG: HashMap = create_output_filter_config( utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "config/mitre_tactics.txt", true) .unwrap().to_str() .unwrap(), ); pub static ref CH_CONFIG: HashMap = create_output_filter_config( - utils::check_setting_path(&configs::CONFIG.read().unwrap().args.config, "channel_abbreviations.txt", false).unwrap_or_else(|| { + utils::check_setting_path(&configs::CONFIG.read().unwrap().config, "channel_abbreviations.txt", false).unwrap_or_else(|| { utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), "rules/config/channel_abbreviations.txt", true @@ -61,9 +61,9 @@ lazy_static! { .unwrap(), ); pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = - configs::CONFIG.read().unwrap().args.pivot_keywords_list; + configs::CONFIG.read().unwrap().pivot_keywords_list; pub static ref DEFAULT_DETAILS: HashMap = get_default_details( - utils::check_setting_path(&configs::CONFIG.read().unwrap().args.config, "default_details.txt", false).unwrap_or_else(|| { + utils::check_setting_path(&configs::CONFIG.read().unwrap().config, "default_details.txt", false).unwrap_or_else(|| { utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), "rules/config/default_details.txt", true diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs index e248c5b73..c61185854 100644 --- a/src/detections/rule/count.rs +++ b/src/detections/rule/count.rs @@ -86,7 +86,7 @@ fn get_alias_value_in_record( utils::get_event_value(&utils::get_event_id_key(), record).unwrap() ), }; - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -187,7 +187,7 @@ impl TimeFrameInfo { value.retain(|c| c != 'd'); } else { let errmsg = format!("Timeframe is invalid. Input value:{}", value); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -223,7 +223,7 @@ pub fn get_sec_timeframe(rule: &RuleNode) -> Option { } Err(err) => { let errmsg = format!("Timeframe number is invalid. timeframe. {}", err); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 0567d1ef1..84e1e5201 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -120,10 +120,6 @@ pub fn read_csv(filename: &str) -> Result>, String> { Result::Ok(ret) } -pub fn is_target_event_id(s: &str) -> bool { - configs::CONFIG.read().unwrap().target_eventids.is_target(s) -} - pub fn get_event_id_key() -> String { "Event.System.EventID".to_string() } @@ -199,8 +195,11 @@ pub fn get_event_value<'a>(key: &str, event_value: &'a Value) -> Option<&'a Valu pub fn get_thread_num() -> usize { let cpu_num = num_cpus::get(); - let conf = configs::CONFIG.read().unwrap(); - conf.args.thread_number.unwrap_or(cpu_num) + configs::CONFIG + .read() + .unwrap() + .thread_number + .unwrap_or(cpu_num) } pub fn create_tokio_runtime() -> Runtime { @@ -269,7 +268,7 @@ pub fn write_color_buffer( /// no-colorのオプションの指定があるかを確認し、指定されている場合はNoneをかえし、指定されていない場合は引数で指定されたColorをSomeでラップして返す関数 pub fn get_writable_color(color: Option) -> Option { - if configs::CONFIG.read().unwrap().args.no_color { + if configs::CONFIG.read().unwrap().no_color { None } else { color @@ -388,15 +387,15 @@ pub fn check_setting_path(base_path: &Path, path: &str, ignore_err: bool) -> Opt pub fn check_rule_config() -> Result<(), String> { // rules/configのフォルダが存在するかを確認する let exist_rule_config_folder = - if configs::CONFIG.read().unwrap().args.config == CURRENT_EXE_PATH.to_path_buf() { + if configs::CONFIG.read().unwrap().config == CURRENT_EXE_PATH.to_path_buf() { check_setting_path( - &configs::CONFIG.read().unwrap().args.config, + &configs::CONFIG.read().unwrap().config, "rules/config", false, ) .is_some() } else { - check_setting_path(&configs::CONFIG.read().unwrap().args.config, "", false).is_some() + check_setting_path(&configs::CONFIG.read().unwrap().config, "", false).is_some() }; if !exist_rule_config_folder { return Err("The required rules config files were not found. Please download them with --update-rules".to_string()); @@ -413,7 +412,7 @@ pub fn check_rule_config() -> Result<(), String> { ]; let mut not_exist_file = vec![]; for file in &files { - if check_setting_path(&configs::CONFIG.read().unwrap().args.config, file, false).is_none() { + if check_setting_path(&configs::CONFIG.read().unwrap().config, file, false).is_none() { not_exist_file.push(*file); } } @@ -429,7 +428,7 @@ pub fn check_rule_config() -> Result<(), String> { ///タイムゾーンに合わせた情報を情報を取得する関数 pub fn format_time(time: &DateTime, date_only: bool) -> String { - if configs::CONFIG.read().unwrap().args.utc || configs::CONFIG.read().unwrap().args.iso_8601 { + if configs::CONFIG.read().unwrap().utc || configs::CONFIG.read().unwrap().iso_8601 { format_rfc(time, date_only) } else { format_rfc(&time.with_timezone(&Local), date_only) @@ -441,7 +440,7 @@ fn format_rfc(time: &DateTime, date_only: bool) -> String where Tz::Offset: std::fmt::Display, { - let time_args = &configs::CONFIG.read().unwrap().args; + let time_args = configs::CONFIG.read().unwrap(); if time_args.rfc_2822 { if date_only { time.format("%a, %e %b %Y").to_string() diff --git a/src/filter.rs b/src/filter.rs index d5c6d3c55..ef3bd3bb2 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -29,24 +29,12 @@ pub fn exclude_ids() -> RuleExclude { exclude_ids.insert_ids(&format!( "{}/noisy_rules.txt", - configs::CONFIG - .read() - .unwrap() - .args - .config - .as_path() - .display() + configs::CONFIG.read().unwrap().config.as_path().display() )); exclude_ids.insert_ids(&format!( "{}/exclude_rules.txt", - configs::CONFIG - .read() - .unwrap() - .args - .config - .as_path() - .display() + configs::CONFIG.read().unwrap().config.as_path().display() )); exclude_ids @@ -56,7 +44,7 @@ impl RuleExclude { fn insert_ids(&mut self, filename: &str) { let f = File::open(filename); if f.is_err() { - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::warn(&format!("{} does not exist", filename)).ok(); } if !*QUIET_ERRORS_FLAG { diff --git a/src/main.rs b/src/main.rs index 16c425eb2..9e6577db9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,8 @@ use chrono::{DateTime, Datelike, Local}; use evtx::{EvtxParser, ParserSettings}; use hashbrown::{HashMap, HashSet}; use hayabusa::detections::configs::{ - load_pivot_keywords, TargetEventTime, CONFIG, CURRENT_EXE_PATH, + load_pivot_keywords, ConfigReader, EventInfoConfig, TargetEventIds, TargetEventTime, CONFIG, + CURRENT_EXE_PATH, }; use hayabusa::detections::detection::{self, EvtxRecordInfo}; use hayabusa::detections::message::{ @@ -86,6 +87,7 @@ impl App { } fn exec(&mut self) { + let mut configreader = ConfigReader::new(); if *PIVOT_KEYWORD_LIST_FLAG { load_pivot_keywords( utils::check_setting_path( @@ -117,11 +119,12 @@ impl App { // Show usage when no arguments. if std::env::args().len() == 1 { self.output_logo(); - configs::CONFIG.write().unwrap().app.print_help().ok(); + // TODO 別枠でCommandの所をmainで宣言してしまえばok + configreader.app.print_help().ok(); println!(); return; } - if !configs::CONFIG.read().unwrap().args.quiet { + if !configs::CONFIG.read().unwrap().quiet { self.output_logo(); println!(); self.output_eggs(&format!( @@ -139,7 +142,7 @@ impl App { return; } - if configs::CONFIG.read().unwrap().args.list_profile { + if configs::CONFIG.read().unwrap().list_profile { let profile_list = options::profile::get_profile_list("config/profiles.yaml"); write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), @@ -168,20 +171,16 @@ impl App { return; } - if configs::CONFIG.read().unwrap().args.update_rules { + if configs::CONFIG.read().unwrap().update_rules { // エラーが出た場合はインターネット接続がそもそもできないなどの問題点もあるためエラー等の出力は行わない let latest_version_data = if let Ok(data) = Update::get_latest_hayabusa_version() { data } else { None }; - let now_version = &format!( - "v{}", - configs::CONFIG.read().unwrap().app.get_version().unwrap() - ); + let now_version = &format!("v{}", configreader.app.get_version().unwrap()); - match Update::update_rules(configs::CONFIG.read().unwrap().args.rules.to_str().unwrap()) - { + match Update::update_rules(configs::CONFIG.read().unwrap().rules.to_str().unwrap()) { Ok(output) => { if output != "You currently have the latest rules." { write_color_buffer( @@ -240,15 +239,15 @@ impl App { return; } // カレントディレクトリ以外からの実行の際にrules-configオプションの指定がないとエラーが発生することを防ぐための処理 - if configs::CONFIG.read().unwrap().args.config == Path::new("./rules/config") { - configs::CONFIG.write().unwrap().args.config = + if configs::CONFIG.read().unwrap().config == Path::new("./rules/config") { + configs::CONFIG.write().unwrap().config = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "rules/config", true) .unwrap(); } // カレントディレクトリ以外からの実行の際にrulesオプションの指定がないとエラーが発生することを防ぐための処理 - if configs::CONFIG.read().unwrap().args.rules == Path::new("./rules") { - configs::CONFIG.write().unwrap().args.rules = + if configs::CONFIG.read().unwrap().rules == Path::new("./rules") { + configs::CONFIG.write().unwrap().rules = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "rules", true).unwrap(); } // rule configのフォルダ、ファイルを確認してエラーがあった場合は終了とする @@ -258,7 +257,7 @@ impl App { } // pivot 機能でファイルを出力する際に同名ファイルが既に存在していた場合はエラー文を出して終了する。 - if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output { + if let Some(csv_path) = &configs::CONFIG.read().unwrap().output { let pivot_key_unions = PIVOT_KEYWORD.read().unwrap(); pivot_key_unions.iter().for_each(|(key, _)| { let keywords_file_name = @@ -308,7 +307,7 @@ impl App { println!(); } - if let Some(html_path) = &configs::CONFIG.read().unwrap().args.html_report { + if let Some(html_path) = &configs::CONFIG.read().unwrap().html_report { // if already exists same html report file. output alert message and exit if utils::check_file_expect_not_exist( html_path.as_path(), @@ -332,15 +331,20 @@ impl App { ) .ok(); let target_extensions = - configs::get_target_extensions(CONFIG.read().unwrap().args.evtx_file_ext.as_ref()); + configs::get_target_extensions(CONFIG.read().unwrap().evtx_file_ext.as_ref()); - if configs::CONFIG.read().unwrap().args.live_analysis { + if configs::CONFIG.read().unwrap().live_analysis { let live_analysis_list = self.collect_liveanalysis_files(&target_extensions); if live_analysis_list.is_none() { return; } - self.analysis_files(live_analysis_list.unwrap(), &time_filter); - } else if let Some(filepath) = &configs::CONFIG.read().unwrap().args.filepath { + self.analysis_files( + live_analysis_list.unwrap(), + &time_filter, + configreader.event_timeline_config, + configreader.target_eventids, + ); + } else if let Some(filepath) = &configs::CONFIG.read().unwrap().filepath { if !filepath.exists() { AlertMessage::alert(&format!( " The file {} does not exist. Please specify a valid file path.", @@ -370,8 +374,13 @@ impl App { .ok(); return; } - self.analysis_files(vec![PathBuf::from(filepath)], &time_filter); - } else if let Some(directory) = &configs::CONFIG.read().unwrap().args.directory { + self.analysis_files( + vec![PathBuf::from(filepath)], + &time_filter, + configreader.event_timeline_config, + configreader.target_eventids, + ); + } else if let Some(directory) = &configs::CONFIG.read().unwrap().directory { let evtx_files = Self::collect_evtxfiles( directory.as_os_str().to_str().unwrap(), &target_extensions, @@ -380,22 +389,26 @@ impl App { AlertMessage::alert("No .evtx files were found.").ok(); return; } - self.analysis_files(evtx_files, &time_filter); - } else if configs::CONFIG.read().unwrap().args.contributors { + self.analysis_files( + evtx_files, + &time_filter, + configreader.event_timeline_config, + configreader.target_eventids, + ); + } else if configs::CONFIG.read().unwrap().contributors { self.print_contributors(); return; - } else if configs::CONFIG.read().unwrap().args.level_tuning.is_some() { + } else if configs::CONFIG.read().unwrap().level_tuning.is_some() { let level_tuning_val = &configs::CONFIG .read() .unwrap() - .args .level_tuning .to_owned() .unwrap(); let level_tuning_config_path = match level_tuning_val { Some(path) => path.to_owned(), _ => utils::check_setting_path( - &CONFIG.read().unwrap().args.config, + &CONFIG.read().unwrap().config, "level_tuning.txt", false, ) @@ -417,7 +430,6 @@ impl App { configs::CONFIG .read() .unwrap() - .args .rules .as_os_str() .to_str() @@ -436,7 +448,7 @@ impl App { write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, - &configs::CONFIG.read().unwrap().headless_help, + &configreader.headless_help, true, ) .ok(); @@ -463,19 +475,12 @@ impl App { ); } - let output_path = &configs::CONFIG.read().unwrap().args.output; + let output_path = &configs::CONFIG.read().unwrap().output; if let Some(path) = output_path { if let Ok(metadata) = fs::metadata(path) { let output_saved_str = format!( "Saved file: {} ({})", - configs::CONFIG - .read() - .unwrap() - .args - .output - .as_ref() - .unwrap() - .display(), + path.display(), ByteSize::b(metadata.len()).to_string_as(false) ); write_color_buffer( @@ -522,7 +527,7 @@ impl App { }; //ファイル出力の場合 - if let Some(pivot_file) = &configs::CONFIG.read().unwrap().args.output { + if let Some(pivot_file) = &configs::CONFIG.read().unwrap().output { pivot_key_unions.iter().for_each(|(key, pivot_keyword)| { let mut f = BufWriter::new( fs::File::create( @@ -581,7 +586,6 @@ impl App { configs::CONFIG .read() .unwrap() - .args .html_report .as_ref() .unwrap() @@ -590,7 +594,7 @@ impl App { .to_string(), ) } - if configs::CONFIG.read().unwrap().args.debug { + if configs::CONFIG.read().unwrap().debug { println!(); println!("Memory usage stats:"); unsafe { @@ -639,7 +643,7 @@ impl App { let entries = fs::read_dir(dirpath); if entries.is_err() { let errmsg = format!("{}", entries.unwrap_err()); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -702,13 +706,14 @@ impl App { } } } - fn analysis_files(&mut self, evtx_files: Vec, time_filter: &TargetEventTime) { - let level = configs::CONFIG - .read() - .unwrap() - .args - .min_level - .to_uppercase(); + fn analysis_files( + &mut self, + evtx_files: Vec, + time_filter: &TargetEventTime, + event_timeline_config: EventInfoConfig, + target_event_ids: TargetEventIds, + ) { + let level = configs::CONFIG.read().unwrap().min_level.to_uppercase(); write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, @@ -725,8 +730,8 @@ impl App { let total_size_output = format!("Total file size: {}", total_file_size.to_string_as(false)); println!("{}", total_size_output); println!(); - if !(configs::CONFIG.read().unwrap().args.metrics - || configs::CONFIG.read().unwrap().args.logon_summary) + if !(configs::CONFIG.read().unwrap().metrics + || configs::CONFIG.read().unwrap().logon_summary) { println!("Loading detections rules. Please wait."); println!(); @@ -746,7 +751,7 @@ impl App { let rule_files = detection::Detection::parse_rule_files( level, - &configs::CONFIG.read().unwrap().args.rules, + &configs::CONFIG.read().unwrap().rules, &filter::exclude_ids(), ); @@ -765,22 +770,27 @@ impl App { let mut total_records: usize = 0; let mut tl = Timeline::new(); for evtx_file in evtx_files { - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { println!("Checking target evtx FilePath: {:?}", &evtx_file); } let cnt_tmp: usize; - (detection, cnt_tmp, tl) = - self.analysis_file(evtx_file, detection, time_filter, tl.to_owned()); + (detection, cnt_tmp, tl) = self.analysis_file( + evtx_file, + detection, + time_filter, + tl.to_owned(), + &target_event_ids, + ); total_records += cnt_tmp; pb.inc(); } if *METRICS_FLAG { - tl.tm_stats_dsp_msg(); + tl.tm_stats_dsp_msg(event_timeline_config); } if *LOGONSUMMARY_FLAG { tl.tm_logon_stats_dsp_msg(); } - if configs::CONFIG.read().unwrap().args.output.is_some() { + if configs::CONFIG.read().unwrap().output.is_some() { println!(); println!(); println!("Analysis finished. Please wait while the results are being saved."); @@ -799,6 +809,7 @@ impl App { mut detection: detection::Detection, time_filter: &TargetEventTime, mut tl: Timeline, + target_event_ids: &TargetEventIds, ) -> (detection::Detection, usize, Timeline) { let path = evtx_filepath.display(); let parser = self.evtx_to_jsons(&evtx_filepath); @@ -828,7 +839,7 @@ impl App { evtx_filepath, record_result.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -843,8 +854,8 @@ impl App { let data = record_result.as_ref().unwrap().data.borrow(); // channelがnullである場合とEventID Filter optionが指定されていない場合は、target_eventids.txtでイベントIDベースでフィルタする。 if !self._is_valid_channel(data) - || (configs::CONFIG.read().unwrap().args.eid_filter - && !self._is_target_event_id(data)) + || (configs::CONFIG.read().unwrap().eid_filter + && !self._is_target_event_id(data, target_event_ids)) { continue; } @@ -918,15 +929,15 @@ impl App { } /// target_eventids.txtの設定を元にフィルタする。 trueであれば検知確認対象のEventIDであることを意味する。 - fn _is_target_event_id(&self, data: &Value) -> bool { + fn _is_target_event_id(&self, data: &Value, target_event_ids: &TargetEventIds) -> bool { let eventid = utils::get_event_value(&utils::get_event_id_key(), data); if eventid.is_none() { return true; } match eventid.unwrap() { - Value::String(s) => utils::is_target_event_id(&s.replace('\"', "")), - Value::Number(n) => utils::is_target_event_id(&n.to_string().replace('\"', "")), + Value::String(s) => target_event_ids.is_target(&s.replace('\"', "")), + Value::Number(n) => target_event_ids.is_target(&n.to_string().replace('\"', "")), _ => true, // レコードからEventIdが取得できない場合は、特にフィルタしない } } @@ -966,7 +977,7 @@ impl App { let fp = utils::check_setting_path(&CURRENT_EXE_PATH.to_path_buf(), "art/logo.txt", true) .unwrap(); let content = fs::read_to_string(fp).unwrap_or_default(); - let output_color = if configs::CONFIG.read().unwrap().args.no_color { + let output_color = if configs::CONFIG.read().unwrap().no_color { None } else { Some(Color::Green) diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index fa997dbd9..42ba3f2ba 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -12,8 +12,7 @@ use std::sync::RwLock; use crate::detections::configs; lazy_static! { - pub static ref HTML_REPORT_FLAG: bool = - configs::CONFIG.read().unwrap().args.html_report.is_some(); + pub static ref HTML_REPORT_FLAG: bool = configs::CONFIG.read().unwrap().html_report.is_some(); pub static ref HTML_REPORTER: RwLock = RwLock::new(HtmlReporter::new()); } diff --git a/src/options/profile.rs b/src/options/profile.rs index 84f7f9943..34e2a00b4 100644 --- a/src/options/profile.rs +++ b/src/options/profile.rs @@ -149,7 +149,7 @@ pub fn load_profile( default_profile_path: &str, profile_path: &str, ) -> Option> { - let conf = &configs::CONFIG.read().unwrap().args; + let conf = configs::CONFIG.read().unwrap(); if conf.set_default_profile.is_some() { if let Err(e) = set_default_profile(default_profile_path, profile_path) { AlertMessage::alert(&e).ok(); @@ -236,7 +236,7 @@ pub fn set_default_profile(default_profile_path: &str, profile_path: &str) -> Re }; // デフォルトプロファイルを設定する処理 - if let Some(profile_name) = &configs::CONFIG.read().unwrap().args.set_default_profile { + if let Some(profile_name) = &configs::CONFIG.read().unwrap().set_default_profile { if let Ok(mut buf_wtr) = OpenOptions::new() .write(true) .truncate(true) @@ -323,8 +323,9 @@ pub fn get_profile_list(profile_path: &str) -> Nested> { #[cfg(test)] mod tests { - use crate::detections::configs; + use crate::detections::configs::{self, Config}; use crate::options::profile::{get_profile_list, load_profile, Profile}; + use clap::Parser; use compact_str::CompactString; use nested::Nested; @@ -345,7 +346,7 @@ mod tests { /// プロファイルオプションが設定されていないときにロードをした場合のテスト fn test_load_profile_without_profile_option() { - configs::CONFIG.write().unwrap().args.profile = None; + *configs::CONFIG.write().unwrap() = Config::parse(); let expect: Vec<(CompactString, Profile)> = vec![ ( CompactString::new("Timestamp"), @@ -412,7 +413,7 @@ mod tests { /// プロファイルオプションが設定されて`おり、そのオプションに該当するプロファイルが存在する場合のテスト fn test_load_profile_with_profile_option() { - configs::CONFIG.write().unwrap().args.profile = Some("minimal".to_string()); + configs::CONFIG.write().unwrap().profile = Some("minimal".to_string()); let expect: Vec<(CompactString, Profile)> = vec![ ( CompactString::new("Timestamp"), @@ -454,7 +455,7 @@ mod tests { /// プロファイルオプションが設定されているが、対象のオプションが存在しない場合のテスト fn test_load_profile_no_exist_profile_files() { - configs::CONFIG.write().unwrap().args.profile = Some("not_exist".to_string()); + configs::CONFIG.write().unwrap().profile = Some("not_exist".to_string()); //両方のファイルが存在しない場合 assert_eq!( diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index dcf247708..b9a06d7db 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,6 +1,7 @@ use std::fs::File; use std::io::BufWriter; +use crate::detections::configs::EventInfoConfig; use crate::detections::message::{AlertMessage, CH_CONFIG, LOGONSUMMARY_FLAG, METRICS_FLAG}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; use comfy_table::modifiers::UTF8_ROUND_CORNERS; @@ -42,14 +43,14 @@ impl Timeline { self.stats.logon_stats_start(records); } - pub fn tm_stats_dsp_msg(&mut self) { + pub fn tm_stats_dsp_msg(&mut self, event_timeline_config: EventInfoConfig) { if !*METRICS_FLAG { return; } // 出力メッセージ作成 let mut sammsges: Vec = Vec::new(); let total_event_record = format!("\n\nTotal Event Records: {}\n", self.stats.total); - if CONFIG.read().unwrap().args.filepath.is_some() { + if CONFIG.read().unwrap().filepath.is_some() { sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); sammsges.push(total_event_record); sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); @@ -60,7 +61,7 @@ impl Timeline { let header = vec!["Count", "Percent", "Channel", "ID", "Event"]; let target; - let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().args.output { + let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().output { // output to file match File::create(csv_path) { Ok(file) => { @@ -90,12 +91,12 @@ impl Timeline { mapsorted.sort_by(|x, y| y.1.cmp(x.1)); // イベントID毎の出力メッセージ生成 - let stats_msges: Vec> = self.tm_stats_set_msg(mapsorted); + let stats_msges: Vec> = self.tm_stats_set_msg(mapsorted, event_timeline_config); for msgprint in sammsges.iter() { println!("{}", msgprint); } - if CONFIG.read().unwrap().args.output.is_some() { + if CONFIG.read().unwrap().output.is_some() { for msg in stats_msges.iter() { if let Some(ref mut w) = wtr { w.write_record(msg).ok(); @@ -113,7 +114,7 @@ impl Timeline { // 出力メッセージ作成 let mut sammsges: Vec = Vec::new(); let total_event_record = format!("\n\nTotal Event Records: {}\n", self.stats.total); - if CONFIG.read().unwrap().args.filepath.is_some() { + if CONFIG.read().unwrap().filepath.is_some() { sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); sammsges.push(total_event_record); sammsges.push(format!("First Timestamp: {}", self.stats.start_time)); @@ -132,6 +133,7 @@ impl Timeline { fn tm_stats_set_msg( &self, mapsorted: Vec<(&(std::string::String, std::string::String), &usize)>, + event_timeline_config: EventInfoConfig, ) -> Vec> { let mut msges: Vec> = Vec::new(); @@ -142,10 +144,7 @@ impl Timeline { let fmted_channel = channel.replace('\"', ""); // イベント情報取得(eventtitleなど) - let conf = CONFIG - .read() - .unwrap() - .event_timeline_config + let conf = event_timeline_config .get_event_id(&fmted_channel, event_id) .is_some(); // event_id_info.txtに登録あるものは情報設定 @@ -160,10 +159,7 @@ impl Timeline { format!("{:.1}%", (rate * 1000.0).round() / 10.0), ch, event_id.to_string(), - CONFIG - .read() - .unwrap() - .event_timeline_config + event_timeline_config .get_event_id(&fmted_channel, event_id) .unwrap() .evttitle @@ -196,7 +192,7 @@ impl Timeline { } else { let header = vec!["User", "Failed", "Successful"]; let target; - let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().args.output { + let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().output { // output to file match File::create(csv_path) { Ok(file) => { diff --git a/src/yaml.rs b/src/yaml.rs index e16929eb4..04482d8ae 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -42,7 +42,7 @@ impl ParseYaml { rule_status_cnt: HashMap::from([("deprecated".to_string(), 0_u128)]), errorrule_count: 0, exclude_status: configs::convert_option_vecs_to_hs( - configs::CONFIG.read().unwrap().args.exclude_status.as_ref(), + configs::CONFIG.read().unwrap().exclude_status.as_ref(), ), level_map: HashMap::from([ ("INFORMATIONAL".to_owned(), 1), @@ -79,7 +79,7 @@ impl ParseYaml { "fail to read metadata of file: {}", path.as_ref().to_path_buf().display(), ); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::alert(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -111,7 +111,7 @@ impl ParseYaml { path.as_ref().to_path_buf().display(), read_content.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -132,7 +132,7 @@ impl ParseYaml { path.as_ref().to_path_buf().display(), yaml_contents.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -197,7 +197,7 @@ impl ParseYaml { entry.path().display(), read_content.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -218,7 +218,7 @@ impl ParseYaml { entry.path().display(), yaml_contents.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -263,7 +263,7 @@ impl ParseYaml { } if entry_key == "excluded" || (entry_key == "noisy" - && !configs::CONFIG.read().unwrap().args.enable_noisy_rules) + && !configs::CONFIG.read().unwrap().enable_noisy_rules) { return Option::None; } @@ -301,7 +301,7 @@ impl ParseYaml { .or_insert(0); *status_cnt += 1; - if configs::CONFIG.read().unwrap().args.verbose { + if configs::CONFIG.read().unwrap().verbose { println!("Loaded yml file path: {}", filepath); }