diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index e2b0cdf8..e278da1e 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -1,5 +1,20 @@ # 変更点 +## v1.4 [2022/XX/XX] + +**新機能:** + +- XXX + +**改善:** + +- Clap Crateパッケージの更新 (#413) (@hitenkoku) +- オプションの指定がないときに、`--help`と同じ画面出力を行うように変更した。(#387) (@hitenkoku) + +**バグ修正:** + +- XXX + ## v1.3.2 [2022/06/13] - evtxクレートを0.7.2から0.7.3に更新し、パッケージを全部更新した。 (@YamatoSecurity) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dfda1cf..176e0ea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changes +## v1.4 [2022/XX/XX] + +**New Features:** + +- XXX + +**Enhancements:** + +- Updated clap crate package to version 3. (#413) (@hitnekoku) +- Updated the default usage and help menu. (#387) (@hitenkoku) + +**Bug Fixes:** + +- XXX + ## v1.3.2 [2022/06/13] **Enhancements:** diff --git a/Cargo.lock b/Cargo.lock index 4aa7cd15..f9856a8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check", ] @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "arrayref" @@ -220,24 +220,39 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "d53da17d37dba964b9b3ecb5c5a1f193a2762c700e6829201e645b9381c99dc7" dependencies = [ "atty", "bitflags", + "clap_derive", "clap_lex", "indexmap", + "once_cell", "strsim 0.10.0", "termcolor", "textwrap 0.15.0", ] [[package]] -name = "clap_lex" -version = "0.2.0" +name = "clap_derive" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "c11d40217d16aee8508cc8e5fde8b4ff24639758608e5374e731b53f85749fb9" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" dependencies = [ "os_str_bytes", ] @@ -280,9 +295,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -301,26 +316,26 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "lazy_static", "memoffset", + "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" dependencies = [ "cfg-if", - "lazy_static", + "once_cell", ] [[package]] @@ -617,20 +632,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "git2" -version = "0.13.25" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -647,12 +662,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - [[package]] name = "hashbrown" version = "0.12.1" @@ -664,19 +673,19 @@ dependencies = [ [[package]] name = "hayabusa" -version = "1.3.2" +version = "1.4.0-dev" dependencies = [ "base64", "bytesize", "chrono", - "clap 2.34.0", + "clap 3.2.5", "crossbeam-utils", "csv", "downcast-rs", "evtx", "flate2", "git2", - "hashbrown 0.12.1", + "hashbrown", "hex", "hhmmss", "hyper", @@ -701,6 +710,12 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -804,12 +819,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "6c6392766afd7964e2531940894cffe4bd8d7d17dbc3c1c4857040fd4b33bdb3" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -899,7 +914,7 @@ dependencies = [ "anyhow", "atty", "chrono", - "clap 3.1.18", + "clap 3.2.5", "file-chunker", "indicatif", "memmap2", @@ -925,9 +940,9 @@ checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libgit2-sys" -version = "0.12.26+1.3.0" +version = "0.13.4+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" dependencies = [ "cc", "libc", @@ -1225,6 +1240,30 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1275,9 +1314,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "f53dc8cf16a769a6f677e09e7ff2cd4be1ea0f48754aac39520536962011de0d" dependencies = [ "proc-macro2", ] @@ -1625,9 +1664,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -1810,9 +1849,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" @@ -1857,9 +1896,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -1947,9 +1986,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1957,9 +1996,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -1972,9 +2011,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1982,9 +2021,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -1995,9 +2034,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index a9ebd306..a99e1da3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,46 +1,46 @@ [package] name = "hayabusa" -version = "1.3.2" +version = "1.4.0-dev" authors = ["Yamato Security @SecurityYamato"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "3.*", features = ["derive", "cargo"]} evtx = { git = "https://github.com/Yamato-Security/hayabusa-evtx.git" , rev = "158d496" , features = ["fast-alloc"]} -quick-xml = {version = "0.23.0", features = ["serialize"] } -serde = { version = "1.0.*", features = ["derive"] } +quick-xml = {version = "0.*", features = ["serialize"] } +serde = { version = "1.*", features = ["derive"] } serde_json = { version = "1.0"} -serde_derive = "1.0.*" -clap = "2.*" +serde_derive = "1.*" regex = "1.5.*" csv = "1.1.*" base64 = "*" -flate2 = "1.0.*" -lazy_static = "1.4.0" -chrono = "0.4.19" +flate2 = "1.*" +lazy_static = "1.4.*" +chrono = "0.4.*" yaml-rust = "0.4.*" linked-hash-map = "0.5.*" tokio = { version = "1", features = ["full"] } -num_cpus = "1.13.*" -downcast-rs = "1.2.0" +num_cpus = "1.*" +downcast-rs = "1.*" hhmmss = "*" pbr = "*" hashbrown = "0.12.*" hex = "0.4.*" -git2 = "0.13" +git2 = "0.*" termcolor = "*" -prettytable-rs = "0.8" +prettytable-rs = "0.*" krapslog = "*" terminal_size = "*" -bytesize = "1.1" -hyper = "0.14.19" -lock_api = "0.4.7" -crossbeam-utils = "0.8.8" +bytesize = "1.*" +hyper = "0.14.*" +lock_api = "0.4.*" +crossbeam-utils = "0.8.*" [target.'cfg(windows)'.dependencies] -is_elevated = "0.1.2" -static_vcruntime = "2.0" +is_elevated = "0.1.*" +static_vcruntime = "2.*" [target.'cfg(unix)'.dependencies] #Mac and Linux openssl = { version = "*", features = ["vendored"] } #vendored is needed to compile statically. diff --git a/README-Japanese.md b/README-Japanese.md index 7c19a2f0..7fa767f4 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -321,40 +321,45 @@ macOSの環境設定から「セキュリティとプライバシー」を開き ## コマンドラインオプション -```bash +``` USAGE: - -d, --directory [DIRECTORY] '.evtxファイルを持つディレクトリのパス。' - -f, --filepath [FILE_PATH] '1つの.evtxファイルのパス。' - -F, --full-data '全てのフィールド情報を出力する。' - -r, --rules [RULE_DIRECTORY/RULE_FILE] 'ルールファイルまたはルールファイルを持つディレクトリ。(デフォルト: .\rules)' - -C, --config [RULE_CONFIG_DIRECTORY] 'ルールフォルダのコンフィグディレクトリ(デフォルト: .\rules\config)' - -o, --output [CSV_TIMELINE] 'タイムラインをCSV形式で保存する。(例: results.csv)' - --all-tags '出力したCSVファイルにルール内のタグ情報を全て出力する。' - -R, --hide-record-id 'イベントレコードIDを表示しない。' - -v, --verbose '詳細な情報を出力する。' - -V, --visualize-timeline 'イベント頻度タイムラインを出力する。' - -D, --enable-deprecated-rules 'Deprecatedルールを有効にする。' - -n, --enable-noisy-rules 'Noisyルールを有効にする。' - -u, --update-rules 'rulesフォルダをhayabusa-rulesのgithubリポジトリの最新版に更新する。' - -m, --min-level [LEVEL] '結果出力をするルールの最低レベル。(デフォルト: informational)' - -l, --live-analysis 'ローカル端末のC:\Windows\System32\winevt\Logsフォルダを解析する。(Windowsのみ。管理者権限が必要。)' - --start-timeline [START_TIMELINE] '解析対象とするイベントログの開始時刻。(例: "2020-02-22 00:00:00 +09:00")' - --end-timeline [END_TIMELINE] '解析対象とするイベントログの終了時刻。(例: "2022-02-22 23:59:59 +09:00")' - --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-time 'アメリカ形式で日付と時刻を出力する。 (例: 02-22-2022 10:00:00.123 PM -06:00)' - --US-military-time '24時間制(ミリタリータイム)のアメリカ形式で日付と時刻を出力する。 (例: 02-22-2022 22:00:00.123 -06:00)' - --European-time 'ヨーロッパ形式で日付と時刻を出力する。 (例: 22-02-2022 22:00:00.123 +02:00)' - -U, --utc 'UTC形式で日付と時刻を出力する。(デフォルト: 現地時間)' - --no-color 'カラー出力を無効にする。' - -t, --thread-number [NUMBER] 'スレッド数。(デフォルト: パフォーマンスに最適な数値)' - -s, --statistics 'イベントIDの統計情報を表示する。' - -L, --logon-summary '成功と失敗したログオン情報の要約を出力する。' - -q, --quiet 'Quietモード。起動バナーを表示しない。' - -Q, --quiet-errors 'Quiet errorsモード。エラーログを保存しない。' - --level-tuning [LEVEL_TUNING_FILE] 'ルールlevelのチューニング (デフォルト: .\rules\config\level_tuning.txt)' - -p, --pivot-keywords-list 'ピボットキーワードの一覧作成。' - --contributors 'コントリビュータの一覧表示。' + hayabusa.exe -f file.evtx [OPTIONS] / hayabusa.exe -d evtx-directory [OPTIONS] + +OPTIONS: + --European-time ヨーロッパ形式で日付と時刻を出力する (例: 22-02-2022 22:00:00.123 +02:00) + --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) + --all-tags 出力したCSVファイルにルール内のタグ情報を全て出力する + -c, --config ルールフォルダのコンフィグディレクトリ (デフォルト: ./rules/config) + --contributors コントリビュータの一覧表示 + -d, --directory .evtxファイルを持つディレクトリのパス + -D, --enable-deprecated-rules Deprecatedルールを有効にする + --end-timeline 解析対象とするイベントログの終了時刻 (例: "2022-02-22 23:59:59 +09:00") + -f, --filepath 1つの.evtxファイルに対して解析を行う + -F, --full-data 全てのフィールド情報を出力する + -h, --help ヘルプ情報を表示する + -l, --live-analysis ローカル端末のC:\Windows\System32\winevt\Logsフォルダを解析する + -L, --logon-summary 成功と失敗したログオン情報の要約を出力する + --level-tuning ルールlevelのチューニング (デフォルト: ./rules/config/level_tuning.txt) + -m, --min-level 結果出力をするルールの最低レベル (デフォルト: informational) + -n, --enable-noisy-rules Noisyルールを有効にする + --no_color カラー出力を無効にする + -o, --output タイムラインをCSV形式で保存する (例: results.csv) + -p, --pivot-keywords-list ピボットキーワードの一覧作成 + -q, --quiet Quietモード: 起動バナーを表示しない + -Q, --quiet-errors Quiet errorsモード: エラーログを保存しない + -r, --rules ルールファイルまたはルールファイルを持つディレクトリ (デフォルト: ./rules) + -R, --hide-record-id イベントレコードIDを表示しない + -s, --statistics イベントIDの統計情報を表示する + --start-timeline 解析対象とするイベントログの開始時刻 (例: "2020-02-22 00:00:00 +09:00") + -t, --thread-number スレッド数 (デフォルト: パフォーマンスに最適な数値) + -u, --update-rules rulesフォルダをhayabusa-rulesのgithubリポジトリの最新版に更新する + -U, --UTC UTC形式で日付と時刻を出力する (デフォルト: 現地時間) + -v, --verbose 詳細な情報を出力する + -V, --visualize-timeline イベント頻度タイムラインを出力する + --version バージョン情報を表示する ``` ## 使用例 diff --git a/README.md b/README.md index 123f43fd..799511f2 100644 --- a/README.md +++ b/README.md @@ -319,40 +319,45 @@ You should now be able to run hayabusa. ## Command Line Options -```bash +``` USAGE: - -d, --directory [DIRECTORY] 'Directory of multiple .evtx files.' - -f, --filepath [FILE_PATH] 'File path to one .evtx file.' - -F, --full-data 'Print all field information.' - -r, --rules [RULE_DIRECTORY/RULE_FILE] 'Rule file or directory (Default: .\rules)' - -C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\rules\config)' - -o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)' - --all-tags 'Output all tags when saving to a CSV file.' - -R, --hide-record-id 'Do not display the EventRecordID number.' - -v, --verbose 'Output verbose information.' - -V, --visualize-timeline 'Output event frequency timeline.' - -D, --enable-deprecated-rules 'Enable rules marked as deprecated.' - -n, --enable-noisy-rules 'Enable rules marked as noisy.' - -u, --update-rules 'Update to the latest rules in the hayabusa-rules github repository.' - -m, --min-level [LEVEL] 'Minimum level for rules. (Default: informational)' - -l, --live-analysis 'Analyze the local C:\Windows\System32\winevt\Logs folder (Windows Only. Administrator privileges required.)' - --start-timeline [START_TIMELINE] 'Start time of the event logs to load. (Ex: "2020-02-22 00:00:00 +09:00")' - --end-timeline [END_TIMELINE] 'End time of the event logs to load. (Ex: "2022-02-22 23:59:59 +09:00")' - --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-time 'Output timestamp in US time format. (Ex: 02-22-2022 10:00:00.123 PM -06:00)' - --US-military-time 'Output timestamp in US military time format. (Ex: 02-22-2022 22:00:00.123 -06:00)' - --European-time 'Output timestamp in European time format. (Ex: 22-02-2022 22:00:00.123 +02:00)' - -U, --utc 'Output time in UTC format. (Default: local time)' - --no-color 'Disable color output.' - -t, --thread-number [NUMBER] 'Thread number. (Default: Optimal number for performance.)' - -s, --statistics 'Prints statistics of event IDs.' - -L, --logon-summary 'Successful and failed logons summary.' - -q, --quiet 'Quiet mode. Do not display the launch banner.' - -Q, --quiet-errors 'Quiet errors mode. Do not save error logs.' - --level-tuning [LEVEL_TUNING_FILE] 'Tune alert levels. (Default: .\rules\config\level_tuning.txt)' - -p, --pivot-keywords-list 'Create a list of pivot keywords.' - --contributors 'Prints the list of contributors.' + hayabusa.exe -f file.evtx [OPTIONS] / hayabusa.exe -d evtx-directory [OPTIONS] + +OPTIONS: + --European-time Output timestamp in European time format (ex: 22-02-2022 22:00:00.123 +02:00) + --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) + --all-tags Output all tags when saving to a CSV file + -c, --config Specify custom rule config folder (default: ./rules/config) + --contributors Print the list of contributors + -d, --directory Directory of multiple .evtx files + -D, --enable-deprecated-rules Enable rules marked as deprecated + --end-timeline End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00") + -f, --filepath File path to one .evtx file + -F, --full-data Print all field information + -h, --help Print help information + -l, --live-analysis Analyze the local C:\Windows\System32\winevt\Logs folder + -L, --logon-summary Print a summary of successful and failed logons + --level-tuning Tune alert levels (default: ./rules/config/level_tuning.txt) + -m, --min-level Minimum level for rules (default: informational) + -n, --enable-noisy-rules Enable rules marked as noisy + --no-color Disable color output + -o, --output Save the timeline in CSV format (ex: results.csv) + -p, --pivot-keywords-list Create a list of pivot keywords + -q, --quiet Quiet mode: do not display the launch banner + -Q, --quiet-errors Quiet errors mode: do not save error logs + -r, --rules Specify a rule directory or file (default: ./rules) + -R, --hide-record-ID Do not display EventRecordID numbers + -s, --statistics Print statistics of event IDs + --start-timeline Start time of the event logs to load (ex: "2020-02-22 00:00:00 +09:00") + -t, --thread-number Thread number (default: optimal number for performance) + -u, --update-rules Update to the latest rules in the hayabusa-rules github repository + -U, --UTC Output time in UTC format (default: local time) + -v, --verbose Output verbose information + -V, --visualize-timeline Output event frequency timeline + --version Print version information ``` ## Usage Examples @@ -627,7 +632,7 @@ Please check out the current rules to use as a template in creating new ones or ## Hayabusa v.s. Converted Sigma Rules Sigma rules need to first be converted to hayabusa rule format explained [here](https://github.com/Yamato-Security/hayabusa-rules/blob/main/tools/sigmac/README.md). -Most rules are compatible with the sigma format so you can use them just like sigma rules to convert to other SIEM formats. +Almost all hayabusa rules are compatible with the sigma format so you can use them just like sigma rules to convert to other SIEM formats. Hayabusa rules are designed solely for Windows event log analysis and have the following benefits: 1. An extra `details` field to display additional information taken from only the useful fields in the log. @@ -739,6 +744,10 @@ At the least, if you like our tool then please give us a star on Github and show Please submit any bugs you find [here.](https://github.com/Yamato-Security/hayabusa/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5Bbug%5D) This project is currently actively maintained and we are happy to fix any bugs reported. +If you find any issues (false positives, bugs, etc...) with Hayabusa rules, please report them to the hayabusa-rules github issues page [here](https://github.com/Yamato-Security/hayabusa-rules/issues/new). + +If you find any issues (false positives, bugs, etc...) with Sigma rules, please report them to the upstream SigmaHQ github issues page [here](https://github.com/SigmaHQ/sigma/issues). + # License Hayabusa is released under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) and all rules are released under the [Detection Rule License (DRL) 1.1](https://github.com/SigmaHQ/sigma/blob/master/LICENSE.Detection.Rules.md). diff --git a/rules b/rules index deb6026f..4d5b76a3 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit deb6026fcf452600829c52852f6283d2c808bc69 +Subproject commit 4d5b76a37db4b2f225968c71fdce196564857cb7 diff --git a/src/afterfact.rs b/src/afterfact.rs index 1e36212e..a6edaa50 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1,4 +1,5 @@ use crate::detections::configs; +use crate::detections::configs::TERM_SIZE; use crate::detections::print; use crate::detections::print::{AlertMessage, IS_HIDE_RECORD_ID}; use crate::detections::utils; @@ -18,7 +19,7 @@ use std::io::BufWriter; use std::io::Write; use std::process; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; -use terminal_size::{terminal_size, Width}; +use terminal_size::Width; #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] @@ -63,7 +64,7 @@ lazy_static! { pub fn set_output_color() -> HashMap { let read_result = utils::read_csv("config/level_color.txt"); let mut color_map: HashMap = HashMap::new(); - if configs::CONFIG.read().unwrap().args.is_present("no-color") { + if configs::CONFIG.read().unwrap().args.no_color { return color_map; } if read_result.is_err() { @@ -166,7 +167,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.value_of("output") { + if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output { // output to file match File::create(csv_path) { Ok(file) => Box::new(BufWriter::new(file)), @@ -362,19 +363,13 @@ fn emit_csv( writeln!(disp_wtr_buf, "Results Summary:").ok(); disp_wtr.print(&disp_wtr_buf).ok(); - let size = terminal_size(); - let terminal_width = match size { + let terminal_width = match *TERM_SIZE { Some((Width(w), _)) => w as usize, None => 100, }; println!(); - if configs::CONFIG - .read() - .unwrap() - .args - .is_present("visualize-timeline") - { + if configs::CONFIG.read().unwrap().args.visualize_timeline { _print_timeline_hist(timestamps, terminal_width, 3); println!(); } @@ -439,7 +434,7 @@ fn _get_serialized_disp_output(dispformat: Option) -> String { if !*IS_HIDE_RECORD_ID { titles.insert(5, "RecordID"); } - if configs::CONFIG.read().unwrap().args.is_present("full-data") { + if configs::CONFIG.read().unwrap().args.full_data { titles.push("RecordInformation"); } return format!("{}\n", titles.join("|")); @@ -598,7 +593,7 @@ fn _print_detection_summary_by_computer( } fn format_time(time: &DateTime, date_only: bool) -> String { - if configs::CONFIG.read().unwrap().args.is_present("utc") { + if configs::CONFIG.read().unwrap().args.utc { format_rfc(time, date_only) } else { format_rfc(&time.with_timezone(&Local), date_only) @@ -607,7 +602,7 @@ fn format_time(time: &DateTime, date_only: bool) -> String { /// get timestamp to input datetime. fn _get_timestamp(time: &DateTime) -> i64 { - if configs::CONFIG.read().unwrap().args.is_present("utc") { + if configs::CONFIG.read().unwrap().args.utc { time.timestamp() } else { let offset_sec = Local.timestamp(0, 0).offset().local_minus_utc(); @@ -621,31 +616,31 @@ where Tz::Offset: std::fmt::Display, { let time_args = &configs::CONFIG.read().unwrap().args; - if time_args.is_present("rfc-2822") { + if time_args.rfc_2822 { if date_only { time.format("%a, %e %b %Y").to_string() } else { time.format("%a, %e %b %Y %H:%M:%S %:z").to_string() } - } else if time_args.is_present("rfc-3339") { + } else if time_args.rfc_3339 { if date_only { time.format("%Y-%m-%d").to_string() } else { time.format("%Y-%m-%d %H:%M:%S%.6f%:z").to_string() } - } else if time_args.is_present("US-time") { + } else if time_args.us_time { if date_only { time.format("%m-%d-%Y").to_string() } else { time.format("%m-%d-%Y %I:%M:%S%.3f %p %:z").to_string() } - } else if time_args.is_present("US-military-time") { + } else if time_args.us_military_time { if date_only { time.format("%m-%d-%Y").to_string() } else { time.format("%m-%d-%Y %H:%M:%S%.3f %:z").to_string() } - } else if time_args.is_present("European-time") { + } else if time_args.european_time { if date_only { time.format("%d-%m-%Y").to_string() } else { diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 16167612..3da13610 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -3,14 +3,17 @@ use crate::detections::pivot::PIVOT_KEYWORD; use crate::detections::print::AlertMessage; use crate::detections::utils; use chrono::{DateTime, Utc}; -use clap::{App, AppSettings, Arg, ArgMatches}; +use clap::{App, CommandFactory, Parser}; use hashbrown::HashMap; use hashbrown::HashSet; use lazy_static::lazy_static; use regex::Regex; +use std::path::PathBuf; use std::sync::RwLock; +use terminal_size::{terminal_size, Height, Width}; + lazy_static! { - pub static ref CONFIG: RwLock = RwLock::new(ConfigReader::new()); + pub static ref CONFIG: RwLock> = RwLock::new(ConfigReader::new()); pub static ref LEVELMAP: HashMap = { let mut levelmap = HashMap::new(); levelmap.insert("INFORMATIONAL".to_owned(), 1); @@ -22,109 +25,209 @@ lazy_static! { }; pub static ref EVENTKEY_ALIAS: EventKeyAliasConfig = load_eventkey_alias(&format!( "{}/eventkey_alias.txt", - CONFIG.read().unwrap().folder_path + CONFIG.read().unwrap().args.config.as_path().display() )); 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 TERM_SIZE: Option<(Width, Height)> = terminal_size(); } -#[derive(Clone)] -pub struct ConfigReader { - pub args: ArgMatches<'static>, - pub folder_path: String, +pub struct ConfigReader<'a> { + pub app: App<'a>, + pub args: Config, + 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() } } -impl ConfigReader { +#[derive(Parser)] +#[clap( + name = "Hayabusa", + usage = "hayabusa.exe -f file.evtx [OPTIONS] / hayabusa.exe -d evtx-directory [OPTIONS]", + author = "Yamato Security (https://github.com/Yamato-Security/hayabusa) @SecurityYamato)", + version, + term_width = 400 +)] +pub struct Config { + /// Directory of multiple .evtx files + #[clap(short = 'd', long, value_name = "DIRECTORY")] + pub directory: Option, + + /// File path to one .evtx file + #[clap(short = 'f', long, value_name = "FILE_PATH")] + pub filepath: Option, + + /// Print all field information + #[clap(short = 'F', long = "full-data")] + pub full_data: bool, + + /// Specify a rule directory or file (default: ./rules) + #[clap( + short = 'r', + long, + default_value = "./rules", + hide_default_value = true, + value_name = "RULE_DIRECTORY/RULE_FILE" + )] + pub rules: PathBuf, + + /// Specify custom rule config folder (default: ./rules/config) + #[clap( + short = 'c', + long, + default_value = "./rules/config", + hide_default_value = true, + value_name = "RULE_CONFIG_DIRECTORY" + )] + pub config: PathBuf, + + /// Save the timeline in CSV format (ex: results.csv) + #[clap(short = 'o', long, value_name = "CSV_TIMELINE")] + pub output: Option, + + /// Output all tags when saving to a CSV file + #[clap(long = "all-tags")] + pub all_tags: bool, + + /// Do not display EventRecordID numbers + #[clap(short = 'R', long = "hide-record-id")] + pub hide_record_id: bool, + + /// Output verbose information + #[clap(short = 'v', long)] + pub verbose: bool, + + /// Output event frequency timeline + #[clap(short = 'V', long = "visualize-timeline")] + pub visualize_timeline: bool, + + /// Enable rules marked as deprecated + #[clap(short = 'D', long = "enable-deprecated-rules")] + pub enable_deprecated_rules: bool, + + /// Enable rules marked as noisy + #[clap(short = 'n', long = "enable-noisy-rules")] + pub enable_noisy_rules: bool, + + /// Update to the latest rules in the hayabusa-rules github repository + #[clap(short = 'u', long = "update-rules")] + pub update_rules: bool, + + /// Minimum level for rules (default: informational) + #[clap( + short = 'm', + long = "min-level", + default_value = "informational", + hide_default_value = true, + value_name = "LEVEL" + )] + pub min_level: String, + + /// Analyze the local C:\Windows\System32\winevt\Logs folder + #[clap(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(long = "start-timeline", value_name = "START_TIMELINE")] + pub start_timeline: Option, + + /// End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00") + #[clap(long = "end-timeline", value_name = "END_TIMELINE")] + pub end_timeline: Option, + + /// Output timestamp in RFC 2822 format (ex: Fri, 22 Feb 2022 22:00:00 -0600) + #[clap(long = "RFC-2822")] + pub rfc_2822: bool, + + /// Output timestamp in RFC 3339 format (ex: 2022-02-22 22:00:00.123456-06:00) + #[clap(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(long = "US-time")] + pub us_time: bool, + + /// Output timestamp in US military time format (ex: 02-22-2022 22:00:00.123 -06:00) + #[clap(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(long = "European-time")] + pub european_time: bool, + + /// Output time in UTC format (default: local time) + #[clap(short = 'U', long = "UTC")] + pub utc: bool, + + /// Disable color output + #[clap(long = "no-color")] + pub no_color: bool, + + /// Thread number (default: optimal number for performance) + #[clap(short, long = "thread-number", value_name = "NUMBER")] + pub thread_number: Option, + + /// Print statistics of event IDs + #[clap(short, long)] + pub statistics: bool, + + /// Print a summary of successful and failed logons + #[clap(short = 'L', long = "logon-summary")] + pub logon_summary: bool, + + /// Tune alert levels (default: ./rules/config/level_tuning.txt) + #[clap( + long = "level-tuning", + default_value = "./rules/config/level_tuning.txt", + hide_default_value = true, + value_name = "LEVEL_TUNING_FILE" + )] + pub level_tuning: PathBuf, + + /// Quiet mode: do not display the launch banner + #[clap(short, long)] + pub quiet: bool, + + /// Quiet errors mode: do not save error logs + #[clap(short = 'Q', long = "quiet-errors")] + pub quiet_errors: bool, + + /// Create a list of pivot keywords + #[clap(short = 'p', long = "pivot-keywords-list")] + pub pivot_keywords_list: bool, + + /// Print the list of contributors + #[clap(long)] + pub contributors: bool, +} + +impl ConfigReader<'_> { pub fn new() -> Self { - let arg = build_app(); - let folder_path_str = arg.value_of("config").unwrap_or("rules/config").to_string(); + let parse = Config::parse(); + let help_term_width = if let Some((Width(w), _)) = *TERM_SIZE { + w as usize + } else { + 400 + }; + let build_cmd = Config::command() + .term_width(help_term_width) + .help_template("\n\nUSAGE:\n {usage}\n\nOPTIONS:\n{options}"); ConfigReader { - args: arg, - folder_path: folder_path_str, + app: build_cmd, + args: parse, + headless_help: String::default(), event_timeline_config: load_eventcode_info("config/statistics_event_info.txt"), target_eventids: load_target_ids("config/target_eventids.txt"), } } } -fn build_app<'a>() -> ArgMatches<'a> { - let program = std::env::args() - .next() - .and_then(|s| { - std::path::PathBuf::from(s) - .file_stem() - .map(|s| s.to_string_lossy().into_owned()) - }) - .unwrap(); - - if is_test_mode() { - return ArgMatches::default(); - } - - let usages = "-d, --directory [DIRECTORY] 'Directory of multiple .evtx files.' - -f, --filepath [FILE_PATH] 'File path to one .evtx file.' - -F, --full-data 'Print all field information.' - -r, --rules [RULE_DIRECTORY/RULE_FILE] 'Rule directory or file (Default: .\\rules)' - -C, --config [RULE_CONFIG_DIRECTORY] 'Rule config folder. (Default: .\\rules\\config)' - -o, --output [CSV_TIMELINE] 'Save the timeline in CSV format. (Ex: results.csv)' - --all-tags 'Output all tags when saving to a CSV file.' - -R, --hide-record-id 'Do not display EventRecordID number.' - -v, --verbose 'Output verbose information.' - -V, --visualize-timeline 'Output event frequency timeline.' - -D, --enable-deprecated-rules 'Enable rules marked as deprecated.' - -n, --enable-noisy-rules 'Enable rules marked as noisy.' - -u, --update-rules 'Update to the latest rules in the hayabusa-rules github repository.' - -m, --min-level [LEVEL] 'Minimum level for rules. (Default: informational)' - -l, --live-analysis 'Analyze the local C:\\Windows\\System32\\winevt\\Logs folder (Windows Only. Administrator privileges required.)' - --start-timeline [START_TIMELINE] 'Start time of the event logs to load. (Ex: \"2020-02-22 00:00:00 +09:00\")' - --end-timeline [END_TIMELINE] 'End time of the event logs to load. (Ex: \"2022-02-22 23:59:59 +09:00\")' - --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-time 'Output timestamp in US time format. (Ex: 02-22-2022 10:00:00.123 PM -06:00)' - --US-military-time 'Output timestamp in US military time format. (Ex: 02-22-2022 22:00:00.123 -06:00)' - --European-time 'Output timestamp in European time format. (Ex: 22-02-2022 22:00:00.123 +02:00)' - -U, --utc 'Output time in UTC format. (Default: local time)' - --no-color 'Disable color output.' - -t, --thread-number [NUMBER] 'Thread number. (Default: Optimal number for performance.)' - -s, --statistics 'Prints statistics of event IDs.' - -L, --logon-summary 'Successful and failed logons summary.' - -q, --quiet 'Quiet mode. Do not display the launch banner.' - -Q, --quiet-errors 'Quiet errors mode. Do not save error logs.' - -p, --pivot-keywords-list 'Create a list of pivot keywords.' - --contributors 'Prints the list of contributors.'"; - App::new(&program) - .about("Hayabusa: Aiming to be the world's greatest Windows event log analysis tool!") - .version("1.3.2") - .author("Yamato Security (https://github.com/Yamato-Security/hayabusa) @SecurityYamato") - .setting(AppSettings::VersionlessSubcommands) - .arg( - // TODO: When update claps to 3.x, these can write in usage texts... - Arg::from_usage("--level-tuning [LEVEL_TUNING_FILE] 'Tune alert levels. (Default: .\\rules\\config\\level_tuning.txt)'") - .default_value("./rules/config/level_tuning.txt"), - ) - .usage(usages) - .args_from_usage(usages) - .get_matches() -} - -fn is_test_mode() -> bool { - for i in std::env::args() { - if i == "--test" { - return true; - } - } - - false -} - #[derive(Debug, Clone)] pub struct TargetEventIds { ids: HashSet, @@ -186,9 +289,8 @@ 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.value_of("start-timeline") { - match DateTime::parse_from_str(s_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00 + let start_time = if let Some(s_time) = &CONFIG.read().unwrap().args.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 { Ok(dt) => Some(dt.with_timezone(&Utc)), @@ -201,10 +303,10 @@ impl TargetEventTime { None } } - } else { - None - }; - let end_time = if let Some(e_time) = CONFIG.read().unwrap().args.value_of("end-timeline") { + } else { + None + }; + let end_time = if let Some(e_time) = &CONFIG.read().unwrap().args.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 23a38cd8..9a3417d2 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -6,10 +6,10 @@ use crate::detections::print::AlertMessage; use crate::detections::print::DetectInfo; use crate::detections::print::ERROR_LOG_STACK; use crate::detections::print::MESSAGES; -use crate::detections::print::PIVOT_KEYWORD_LIST_FLAG; -use crate::detections::print::QUIET_ERRORS_FLAG; -use crate::detections::print::STATISTICS_FLAG; use crate::detections::print::{CH_CONFIG, IS_HIDE_RECORD_ID, TAGS_CONFIG}; +use crate::detections::print::{ + LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, STATISTICS_FLAG, +}; use crate::detections::rule; use crate::detections::rule::AggResult; use crate::detections::rule::RuleNode; @@ -20,11 +20,10 @@ use hashbrown; use hashbrown::HashMap; use serde_json::Value; use std::fmt::Write; +use std::path::Path; use std::sync::Arc; use tokio::{runtime::Runtime, spawn, task::JoinHandle}; -const DIRPATH_RULES: &str = "rules"; - // イベントファイルの1レコード分の情報を保持する構造体 #[derive(Clone, Debug)] pub struct EvtxRecordInfo { @@ -58,16 +57,15 @@ impl Detection { // ルールファイルをパースします。 pub fn parse_rule_files( level: String, - rulespath: Option<&str>, + rulespath: &Path, exclude_ids: &filter::RuleExclude, ) -> Vec { // ルールファイルのパースを実行 let mut rulefile_loader = ParseYaml::new(); - let result_readdir = - rulefile_loader.read_dir(rulespath.unwrap_or(DIRPATH_RULES), &level, exclude_ids); + 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.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -89,7 +87,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.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::warn(&errmsg_body).ok(); err_msgs.iter().for_each(|err_msg| { @@ -120,12 +118,7 @@ impl Detection { .map(|rule_file_tuple| rule::create_rule(rule_file_tuple.0, rule_file_tuple.1)) .filter_map(return_if_success) .collect(); - if !configs::CONFIG - .read() - .unwrap() - .args - .is_present("logon-summary") - { + if !*LOGONSUMMARY_FLAG { let _ = &rulefile_loader .rule_load_cnt .insert(String::from("rule parsing error"), parseerror_count); @@ -276,7 +269,7 @@ impl Detection { .map(|str| str.to_owned()) .collect(); let output = Detection::create_count_output(rule, &agg_result); - let rec_info = if configs::CONFIG.read().unwrap().args.is_present("full-data") { + let rec_info = if configs::CONFIG.read().unwrap().args.full_data { Option::Some(String::default()) } else { Option::None @@ -411,12 +404,13 @@ mod tests { use crate::detections::rule::AggResult; use crate::filter; use chrono::{TimeZone, Utc}; + use std::path::Path; use yaml_rust::YamlLoader; #[test] fn test_parse_rule_files() { let level = "informational"; - let opt_rule_path = Some("./test_files/rules/level_yaml"); + let opt_rule_path = Path::new("./test_files/rules/level_yaml"); let cole = Detection::parse_rule_files(level.to_owned(), opt_rule_path, &filter::exclude_ids()); assert_eq!(5, cole.len()); diff --git a/src/detections/print.rs b/src/detections/print.rs index 1ab92db4..4733b3c9 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -48,42 +48,23 @@ lazy_static! { "./logs/errorlog-{}.log", Local::now().format("%Y%m%d_%H%M%S") ); - pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG - .read() - .unwrap() - .args - .is_present("quiet-errors"); + pub static ref QUIET_ERRORS_FLAG: bool = configs::CONFIG.read().unwrap().args.quiet_errors; pub static ref ERROR_LOG_STACK: Mutex> = Mutex::new(Vec::new()); - pub static ref STATISTICS_FLAG: bool = configs::CONFIG - .read() - .unwrap() - .args - .is_present("statistics"); - pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG - .read() - .unwrap() - .args - .is_present("logon-summary"); + pub static ref STATISTICS_FLAG: bool = configs::CONFIG.read().unwrap().args.statistics; + pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG.read().unwrap().args.logon_summary; pub static ref TAGS_CONFIG: HashMap = Message::create_output_filter_config( "config/output_tag.txt", true, - configs::CONFIG.read().unwrap().args.is_present("all-tags") + configs::CONFIG.read().unwrap().args.all_tags ); pub static ref CH_CONFIG: HashMap = Message::create_output_filter_config( "config/channel_abbreviations.txt", false, - configs::CONFIG.read().unwrap().args.is_present("all-tags") + configs::CONFIG.read().unwrap().args.all_tags ); - pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = configs::CONFIG - .read() - .unwrap() - .args - .is_present("pivot-keywords-list"); - pub static ref IS_HIDE_RECORD_ID: bool = configs::CONFIG - .read() - .unwrap() - .args - .is_present("hide-record-id"); + pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = + configs::CONFIG.read().unwrap().args.pivot_keywords_list; + pub static ref IS_HIDE_RECORD_ID: bool = configs::CONFIG.read().unwrap().args.hide_record_id; } impl Default for Message { diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs index e7bb9ecc..3f32f028 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.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -188,7 +188,7 @@ impl TimeFrameInfo { tnum.retain(|c| c != 'd'); } else { let errmsg = format!("Timeframe is invalid. Input value:{}", value); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -224,7 +224,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.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 59f8f2d1..7f20781b 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -185,13 +185,9 @@ pub fn get_event_value<'a>(key: &str, event_value: &'a Value) -> Option<&'a Valu } pub fn get_thread_num() -> usize { - let def_thread_num_str = num_cpus::get().to_string(); + let cpu_num = num_cpus::get(); let conf = configs::CONFIG.read().unwrap(); - conf.args - .value_of("thread-number") - .unwrap_or(def_thread_num_str.as_str()) - .parse::() - .unwrap() + conf.args.thread_number.unwrap_or(cpu_num) } pub fn create_tokio_runtime() -> Runtime { @@ -228,7 +224,7 @@ pub fn create_rec_info(data: Value, path: String, keys: &[String]) -> EvtxRecord // EvtxRecordInfoを作る let data_str = data.to_string(); - let rec_info = if configs::CONFIG.read().unwrap().args.is_present("full-data") { + let rec_info = if configs::CONFIG.read().unwrap().args.full_data { Option::Some(create_recordinfos(&data)) } else { Option::None @@ -279,7 +275,7 @@ fn create_recordinfos(record: &Value) -> String { .collect(); // 標準出力する時はセルがハイプ区切りになるので、パイプ区切りにしない - if configs::CONFIG.read().unwrap().args.is_present("output") { + if configs::CONFIG.read().unwrap().args.output.is_some() { summary.join(" | ") } else { summary.join(" ") diff --git a/src/filter.rs b/src/filter.rs index 20feac1a..425829e3 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -29,21 +29,28 @@ impl RuleExclude { pub fn exclude_ids() -> RuleExclude { let mut exclude_ids = RuleExclude::default(); - if !configs::CONFIG - .read() - .unwrap() - .args - .is_present("enable-noisy-rules") - { + if !configs::CONFIG.read().unwrap().args.enable_noisy_rules { exclude_ids.insert_ids(&format!( "{}/noisy_rules.txt", - configs::CONFIG.read().unwrap().folder_path + configs::CONFIG + .read() + .unwrap() + .args + .config + .as_path() + .display() )); }; exclude_ids.insert_ids(&format!( "{}/exclude_rules.txt", - configs::CONFIG.read().unwrap().folder_path + configs::CONFIG + .read() + .unwrap() + .args + .config + .as_path() + .display() )); exclude_ids @@ -53,7 +60,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.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::warn(&format!("{} does not exist", filename)).ok(); } if !*QUIET_ERRORS_FLAG { diff --git a/src/main.rs b/src/main.rs index 2ae395cc..fcd72e05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,22 +86,14 @@ impl App { } let analysis_start_time: DateTime = Local::now(); - // Show usage when no arguments. if std::env::args().len() == 1 { self.output_logo(); - println!(); - write_color_buffer( - BufferWriter::stdout(ColorChoice::Always), - None, - configs::CONFIG.read().unwrap().args.usage(), - ) - .ok(); + configs::CONFIG.write().unwrap().app.print_help().ok(); println!(); return; } - - if !configs::CONFIG.read().unwrap().args.is_present("quiet") { + if !configs::CONFIG.read().unwrap().args.quiet { self.output_logo(); println!(); self.output_eggs(&format!( @@ -120,12 +112,7 @@ impl App { return; } - if configs::CONFIG - .read() - .unwrap() - .args - .is_present("update-rules") - { + if configs::CONFIG.read().unwrap().args.update_rules { match self.update_rules() { Ok(output) => { if output != "You currently have the latest rules." { @@ -153,10 +140,11 @@ impl App { return; } - if let Some(csv_path) = configs::CONFIG.read().unwrap().args.value_of("output") { + if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output { let pivot_key_unions = PIVOT_KEYWORD.read().unwrap(); pivot_key_unions.iter().for_each(|(key, _)| { - let keywords_file_name = csv_path.to_owned() + "-" + key + ".txt"; + let keywords_file_name = + csv_path.as_path().display().to_string() + "-" + key + ".txt"; if Path::new(&keywords_file_name).exists() { AlertMessage::alert(&format!( " The file {} already exists. Please specify a different filename.", @@ -165,10 +153,10 @@ impl App { .ok(); } }); - if Path::new(csv_path).exists() { + if csv_path.exists() { AlertMessage::alert(&format!( " The file {} already exists. Please specify a different filename.", - csv_path + csv_path.as_os_str().to_str().unwrap() )) .ok(); return; @@ -198,20 +186,16 @@ impl App { .ok(); println!(); } - if configs::CONFIG - .read() - .unwrap() - .args - .is_present("live-analysis") - { + if configs::CONFIG.read().unwrap().args.live_analysis { let live_analysis_list = self.collect_liveanalysis_files(); 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.value_of("filepath") { - if !filepath.ends_with(".evtx") - || Path::new(filepath) + } else if let Some(filepath) = &configs::CONFIG.read().unwrap().args.filepath { + if filepath.extension().unwrap_or_else(|| OsStr::new(".")) != "evtx" + || filepath + .as_path() .file_stem() .unwrap_or_else(|| OsStr::new(".")) .to_str() @@ -226,36 +210,27 @@ impl App { return; } self.analysis_files(vec![PathBuf::from(filepath)], &time_filter); - } else if let Some(directory) = configs::CONFIG.read().unwrap().args.value_of("directory") { - let evtx_files = self.collect_evtxfiles(directory); + } else if let Some(directory) = &configs::CONFIG.read().unwrap().args.directory { + let evtx_files = self.collect_evtxfiles(directory.as_os_str().to_str().unwrap()); if evtx_files.is_empty() { AlertMessage::alert("No .evtx files were found.").ok(); return; } self.analysis_files(evtx_files, &time_filter); - } else if configs::CONFIG - .read() - .unwrap() - .args - .is_present("contributors") - { + } else if configs::CONFIG.read().unwrap().args.contributors { self.print_contributors(); return; - } else if configs::CONFIG - .read() - .unwrap() - .args - .is_present("level-tuning") - && std::env::args() - .into_iter() - .any(|arg| arg.contains("level-tuning")) + } else if std::env::args() + .into_iter() + .any(|arg| arg.contains("level-tuning")) { let level_tuning_config_path = configs::CONFIG .read() .unwrap() .args - .value_of("level-tuning") - .unwrap_or("./rules/config/level_tuning.txt") + .level_tuning + .as_path() + .display() .to_string(); if Path::new(&level_tuning_config_path).exists() { @@ -265,8 +240,10 @@ impl App { .read() .unwrap() .args - .value_of("rules") - .unwrap_or("rules"), + .rules + .as_os_str() + .to_str() + .unwrap(), ) { AlertMessage::alert(&err).ok(); } @@ -277,6 +254,14 @@ impl App { .ok(); } return; + } else { + write_color_buffer( + BufferWriter::stdout(ColorChoice::Always), + None, + &configs::CONFIG.read().unwrap().headless_help, + ) + .ok(); + return; } let analysis_end_time: DateTime = Local::now(); @@ -290,7 +275,7 @@ impl App { .ok(); println!(); - // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが9となるのでエラーログファイル自体が生成されない。 + // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが0となるのでエラーログファイル自体が生成されない。 if ERROR_LOG_STACK.lock().unwrap().len() > 0 { AlertMessage::create_error_log(ERROR_LOG_PATH.to_string()); } @@ -315,10 +300,13 @@ impl App { }; //ファイル出力の場合 - if let Some(pivot_file) = configs::CONFIG.read().unwrap().args.value_of("output") { + if let Some(pivot_file) = &configs::CONFIG.read().unwrap().args.output { pivot_key_unions.iter().for_each(|(key, pivot_keyword)| { let mut f = BufWriter::new( - fs::File::create(pivot_file.to_owned() + "-" + key + ".txt").unwrap(), + fs::File::create( + pivot_file.as_path().display().to_string() + "-" + key + ".txt", + ) + .unwrap(), ); f.write_all(create_output(String::default(), key, pivot_keyword).as_bytes()) .unwrap(); @@ -328,7 +316,12 @@ impl App { "Pivot keyword results saved to the following files:\n".to_string(); pivot_key_unions.iter().for_each(|(key, _)| { - writeln!(output, "{}", &(pivot_file.to_owned() + "-" + key + ".txt")).ok(); + writeln!( + output, + "{}", + &(pivot_file.as_path().display().to_string() + "-" + key + ".txt") + ) + .ok(); }); write_color_buffer(BufferWriter::stdout(ColorChoice::Always), None, &output).ok(); } else { @@ -379,7 +372,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.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -424,7 +417,9 @@ impl App { fn print_contributors(&self) { match fs::read_to_string("./contributors.txt") { - Ok(contents) => println!("{}", contents), + Ok(contents) => { + write_color_buffer(BufferWriter::stdout(ColorChoice::Always), None, &contents).ok(); + } Err(err) => { AlertMessage::alert(&format!("{}", err)).ok(); } @@ -436,8 +431,7 @@ impl App { .read() .unwrap() .args - .value_of("min-level") - .unwrap_or("informational") + .min_level .to_uppercase(); write_color_buffer( BufferWriter::stdout(ColorChoice::Always), @@ -458,7 +452,7 @@ impl App { let rule_files = detection::Detection::parse_rule_files( level, - configs::CONFIG.read().unwrap().args.value_of("rules"), + &configs::CONFIG.read().unwrap().args.rules, &filter::exclude_ids(), ); @@ -476,7 +470,7 @@ impl App { let mut detection = detection::Detection::new(rule_files); let mut total_records: usize = 0; for evtx_file in evtx_files { - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { println!("Checking target evtx FilePath: {:?}", &evtx_file); } let cnt_tmp: usize; @@ -484,7 +478,7 @@ impl App { total_records += cnt_tmp; pb.inc(); } - if configs::CONFIG.read().unwrap().args.is_present("output") { + if configs::CONFIG.read().unwrap().args.output.is_some() { println!(); println!(); println!("Analysis finished. Please wait while the results are being saved."); @@ -530,7 +524,7 @@ impl App { evtx_filepath, record_result.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg).ok(); } if !*QUIET_ERRORS_FLAG { @@ -660,7 +654,7 @@ impl App { fn output_logo(&self) { let fp = &"art/logo.txt".to_string(); let content = fs::read_to_string(fp).unwrap_or_default(); - let output_color = if configs::CONFIG.read().unwrap().args.is_present("no-color") { + let output_color = if configs::CONFIG.read().unwrap().args.no_color { None } else { Some(Color::Green) diff --git a/src/timeline/statistics.rs b/src/timeline/statistics.rs index ab9e60bf..3ae81b9a 100644 --- a/src/timeline/statistics.rs +++ b/src/timeline/statistics.rs @@ -1,4 +1,5 @@ -use crate::detections::{configs, detection::EvtxRecordInfo, utils}; +use crate::detections::print::{LOGONSUMMARY_FLAG, STATISTICS_FLAG}; +use crate::detections::{detection::EvtxRecordInfo, utils}; use hashbrown::HashMap; #[derive(Debug)] @@ -34,12 +35,7 @@ impl EventStatistics { pub fn evt_stats_start(&mut self, records: &[EvtxRecordInfo]) { // 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。 - if !configs::CONFIG - .read() - .unwrap() - .args - .is_present("statistics") - { + if !*STATISTICS_FLAG { return; } @@ -53,13 +49,8 @@ impl EventStatistics { } pub fn logon_stats_start(&mut self, records: &[EvtxRecordInfo]) { - // 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。 - if !configs::CONFIG - .read() - .unwrap() - .args - .is_present("logon-summary") - { + // 引数でlogon-summaryオプションが指定されている時だけ、統計情報を出力する。 + if !*LOGONSUMMARY_FLAG { return; } diff --git a/src/timeline/timelines.rs b/src/timeline/timelines.rs index 6eb1025d..34f8bc8f 100644 --- a/src/timeline/timelines.rs +++ b/src/timeline/timelines.rs @@ -1,3 +1,4 @@ +use crate::detections::print::{LOGONSUMMARY_FLAG, STATISTICS_FLAG}; use crate::detections::{configs::CONFIG, detection::EvtxRecordInfo}; use prettytable::{Cell, Row, Table}; @@ -19,8 +20,8 @@ impl Timeline { pub fn new() -> Timeline { let totalcnt = 0; let filepath = String::default(); - let starttm = "".to_string(); - let endtm = "".to_string(); + let starttm = String::default(); + let endtm = String::default(); let statslst = HashMap::new(); let statsloginlst = HashMap::new(); @@ -35,12 +36,10 @@ impl Timeline { } pub fn tm_stats_dsp_msg(&mut self) { - let statics_flag = CONFIG.read().unwrap().args.is_present("statistics"); - if !statics_flag { + if !*STATISTICS_FLAG { return; } // 出力メッセージ作成 - //println!("map -> {:#?}", evtstat_map); let mut sammsges: Vec = Vec::new(); sammsges.push("---------------------------------------".to_string()); sammsges.push(format!("Evtx File Path: {}", self.stats.filepath)); @@ -66,8 +65,7 @@ impl Timeline { } pub fn tm_logon_stats_dsp_msg(&mut self) { - let logon_summary_flag = CONFIG.read().unwrap().args.is_present("logon-summary"); - if !logon_summary_flag { + if !*LOGONSUMMARY_FLAG { return; } // 出力メッセージ作成 diff --git a/src/yaml.rs b/src/yaml.rs index 97522faf..49c1ba12 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -68,7 +68,7 @@ impl ParseYaml { "fail to read metadata of file: {}", path.as_ref().to_path_buf().display(), ); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::alert(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -100,7 +100,7 @@ impl ParseYaml { path.as_ref().to_path_buf().display(), read_content.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -121,7 +121,7 @@ impl ParseYaml { path.as_ref().to_path_buf().display(), yaml_contents.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -173,7 +173,7 @@ impl ParseYaml { entry.path().display(), read_content.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -194,7 +194,7 @@ impl ParseYaml { entry.path().display(), yaml_contents.unwrap_err() ); - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { AlertMessage::warn(&errmsg)?; } if !*QUIET_ERRORS_FLAG { @@ -256,7 +256,7 @@ impl ParseYaml { .or_insert(0); *status_cnt += 1; - if configs::CONFIG.read().unwrap().args.is_present("verbose") { + if configs::CONFIG.read().unwrap().args.verbose { println!("Loaded yml file path: {}", filepath); } @@ -272,12 +272,7 @@ impl ParseYaml { return Option::None; } - if !configs::CONFIG - .read() - .unwrap() - .args - .is_present("enable-deprecated-rules") - { + if !configs::CONFIG.read().unwrap().args.enable_deprecated_rules { let rule_status = &yaml_doc["status"].as_str().unwrap_or_default(); if *rule_status == "deprecated" { let entry = self