Merge branch 'main'

This commit is contained in:
garigariganzy
2022-09-29 23:12:44 +09:00
28 changed files with 1180 additions and 404 deletions

5
.gitignore vendored
View File

@@ -9,4 +9,7 @@ test_*
*.csv
*.json
*.jsonl
hayabusa*
hayabusa*
*.html
*.htm
*.css

View File

@@ -1,14 +1,23 @@
# 変更点
## 1.x.x [2022/XX/XX]
## 1.7.0 [2022/09/29]
**新機能:**
- HTMLレポート機能 (`-H, --html-report`)の追加。 (#689) (@hitenkoku, @nishikawaakira)
**改善:**
- EventID解析のオプションをmetricsオプションに変更した。(旧: -s -> 新: -M) (#706) (@hitenkoku)
**バグ修正:**
- EventID解析のオプションをmetricsオプションに変更した。(旧: `-s, --statistics` -> 新: `-M, --metrics`) (#706) (@hitenkoku)
- ルール更新オプション(`-u`)を利用したときにHayabusaの新バージョンがないかを確認し、表示するようにした。 (#710) (@hitenkoku)
- HTMLレポート内にロゴを追加した。 (#714) (@hitenkoku)
- メトリクスオプション(`-M --metrics`)もしくはログオン情報(`-L --logon-summary`)と`-d`オプションを利用した場合に1つのテーブルで表示されるように修正した。 (#707) (@hitenkoku)
- メトリクスオプションの結果出力にチャンネル列を追加した。 (#707) (@hitenkoku)
- メトリクスオプション(`-M --metrics`)もしくはログオン情報(`-L --logon-summary`)と`-d`オプションを利用した場合に「First Timestamp」と「Last Timestamp」の出力を行わないように修正した。 (#707) (@hitenkoku)
- メトリクスオプションとログオン情報オプションに対してcsv出力機能(`-o --output`)を追加した。 (#707) (@hitenkoku)
- メトリクスオプションの出力を検出回数と全体の割合が1つのセルで表示されていた箇所を2つの列に分けた。 (#707) (@hitenkoku)
- メトリクスオプションとログオン情報の画面出力に利用していたprettytable-rsクレートをcomfy_tableクレートに修正した. (#707) (@hitenkoku)
- HTMLレポート内にfavicon.pngを追加した。 (#722) (@hitenkoku)
## v1.6.0 [2022/09/16]

View File

@@ -1,15 +1,24 @@
# Changes
## 1.x.x [2022/XX/XX]
## 1.7.0 [2022/09/29]
**New Features:**
- Added a HTML summary report output option (`-H, --html-report`). (#689) (@hitenkoku, @nishikawaakira)
**Enhancements:**
- Changed Event ID Statistics option to Event ID Metrics option. (`-s, --statistics` -> `-M, --metrics`) (#706) (@hitenkoku)
(Note: `statistics_event_info.txt` was changed to `event_id_info.txt`.)
**Bug Fixes:**
- Display new version of Hayabusa link when updating rules if there is a newer version. (#710) (@hitenkoku)
- Added logo in HTML summary output. (#714) (@hitenkoku)
- Unified output to one table when using `-M` or `-L` with the `-d` option. (#707) (@hitenkoku)
- Added Channel column to metrics output. (#707) (@hitenkoku)
- Removed First Timestamp and Last Timestamp of `-M` and `-L` option with the `-d` option. (#707) (@hitenkoku)
- Added csv output option(`-o --output`) when `-M` or `-L` option is used. (#707) (@hitenkoku)
- Separated Count and Percent columns in metric output. (#707) (@hitenkoku)
- Changed output table format of the metric option and logon information crate from prettytable-rs to comfy_table. (#707) (@hitenkoku)
- Added favicon.png in HTML summary output. (#722) (@hitenkoku)
## v1.6.0 [2022/09/16]

360
Cargo.lock generated
View File

@@ -244,13 +244,13 @@ dependencies = [
[[package]]
name = "console"
version = "0.15.1"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
dependencies = [
"encode_unicode 0.3.6",
"encode_unicode",
"lazy_static",
"libc",
"once_cell",
"terminal_size 0.1.17",
"unicode-width",
"winapi",
@@ -262,6 +262,16 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
@@ -393,27 +403,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "discard"
version = "1.0.4"
@@ -438,12 +427,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding"
version = "0.2.33"
@@ -508,6 +491,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]]
name = "encoding_rs"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
dependencies = [
"cfg-if",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@@ -659,6 +651,18 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]]
name = "futures-io"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
[[package]]
name = "futures-sink"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
[[package]]
name = "futures-task"
version = "0.3.24"
@@ -672,9 +676,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
dependencies = [
"futures-core",
"futures-io",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@@ -709,6 +716,25 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "h2"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -720,7 +746,7 @@ dependencies = [
[[package]]
name = "hayabusa"
version = "1.7.0-dev"
version = "1.7.0"
dependencies = [
"base64",
"bytesize",
@@ -737,6 +763,7 @@ dependencies = [
"hashbrown",
"hex",
"hhmmss",
"horrorshow",
"hyper",
"is_elevated",
"itertools",
@@ -748,10 +775,11 @@ dependencies = [
"num_cpus",
"openssl",
"pbr",
"prettytable-rs",
"pulldown-cmark",
"quick-xml",
"rand",
"regex",
"reqwest",
"serde",
"serde_derive",
"serde_json",
@@ -793,6 +821,12 @@ dependencies = [
"time 0.2.27",
]
[[package]]
name = "horrorshow"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371fb981840150b1a54f7cb117bf6699f7466a1d4861daac33bc6fe2b5abea0"
[[package]]
name = "http"
version = "0.2.8"
@@ -846,18 +880,33 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa 1.0.3",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.50"
@@ -912,6 +961,12 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06"
[[package]]
name = "ipnet"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
[[package]]
name = "is_elevated"
version = "0.1.2"
@@ -1107,6 +1162,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "miniz_oxide"
version = "0.5.4"
@@ -1128,6 +1189,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "native-tls"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -1201,9 +1280,9 @@ checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "openssl"
version = "0.10.41"
version = "0.10.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13"
dependencies = [
"bitflags",
"cfg-if",
@@ -1242,9 +1321,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.75"
version = "0.9.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce"
dependencies = [
"autocfg",
"cc",
@@ -1325,20 +1404,6 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "prettytable-rs"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f375cb74c23b51d23937ffdeb48b1fbf5b6409d4b9979c1418c1de58bc8f801"
dependencies = [
"atty",
"csv",
"encode_unicode 1.0.0",
"lazy_static",
"term",
"unicode-width",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -1371,9 +1436,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.43"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
checksum = "3edcd08cf4fea98d1ae6c9ddd3b8ccb1acac7c3693d62625969a7daa04a2ae36"
dependencies = [
"unicode-ident",
]
@@ -1477,17 +1542,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.6.0"
@@ -1520,6 +1574,43 @@ dependencies = [
"winapi",
]
[[package]]
name = "reqwest"
version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "rpmalloc"
version = "0.2.2"
@@ -1551,9 +1642,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.35.10"
version = "0.35.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af895b90e5c071badc3136fc10ff0bcfc98747eadbaf43ed8f214e07ba8f8477"
checksum = "fbb2fda4666def1433b1b05431ab402e42a1084285477222b72d6c564c417cef"
dependencies = [
"bitflags",
"errno",
@@ -1584,12 +1675,45 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
dependencies = [
"lazy_static",
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
@@ -1645,6 +1769,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa 1.0.3",
"ryu",
"serde",
]
[[package]]
name = "sha1"
version = "0.6.1"
@@ -1716,6 +1852,15 @@ dependencies = [
"walkdir",
]
[[package]]
name = "slab"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.9.0"
@@ -1823,9 +1968,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.100"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
@@ -1846,17 +1991,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@@ -1894,18 +2028,18 @@ checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]]
name = "thiserror"
version = "1.0.35"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.35"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [
"proc-macro2",
"quote",
@@ -1996,9 +2130,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.21.1"
version = "1.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95"
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
dependencies = [
"autocfg",
"bytes",
@@ -2006,7 +2140,6 @@ dependencies = [
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
@@ -2026,6 +2159,30 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "tower-service"
version = "0.3.2"
@@ -2175,6 +2332,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
@@ -2204,6 +2373,16 @@ version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2278,6 +2457,15 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "winreg"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "winstructs"
version = "0.3.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "hayabusa"
version = "1.7.0-dev"
version = "1.7.0"
authors = ["Yamato Security @SecurityYamato"]
edition = "2021"
@@ -30,7 +30,6 @@ hashbrown = "0.12.*"
hex = "0.4.*"
git2 = "0.*"
termcolor = "*"
prettytable-rs = "0.*"
krapslog = "*"
terminal_size = "*"
bytesize = "1.*"
@@ -39,6 +38,9 @@ lock_api = "0.4.*"
crossbeam-utils = "0.8.*"
num-format = "*"
comfy-table = "6.*"
pulldown-cmark = { version = "0.9.*", default-features = false, features = ["simd"] }
reqwest = {version = "0.11.*", features = ["blocking", "json"]}
horrorshow = "0.8.*"
[build-dependencies]
static_vcruntime = "2.*"

View File

@@ -22,7 +22,7 @@
# Hayabusa について
Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/)グループによって作られた**Windowsイベントログのファストフォレンジックタイムライン生成**および**スレットハンティングツール**です。 Hayabusaは日本語で[「ハヤブサ」](https://en.wikipedia.org/wiki/Peregrine_falcon)を意味し、ハヤブサが世界で最も速く、狩猟(hunting)に優れ、とても訓練しやすい動物であることから選ばれました。[Rust](https://www.rust-lang.org/) で開発され、マルチスレッドに対応し、可能な限り高速に動作するよう配慮されています。[Sigma](https://github.com/SigmaHQ/Sigma)ルールをHayabusaルール形式に変換する[ツール](https://github.com/Yamato-Security/hayabusa-rules/tree/main/tools/sigmac)も提供しています。Hayabusaの検知ルールもSigmaと同様にYML形式であり、カスタマイズ性や拡張性に優れます。稼働中のシステムで実行してライブ調査することも、複数のシステムからログを収集してオフライン調査することも可能です。また、 [Velociraptor](https://docs.velociraptor.app/)と[Hayabusa artifact](https://docs.velociraptor.app/exchange/artifacts/pages/windows.eventlogs.hayabusa/)を用いることで企業向けの広範囲なスレットハンティングとインシデントレスポンスにも活用できます。出力は一つのCSVタイムラインにまとめられ、Excel、[Timeline Explorer](https://ericzimmerman.github.io/#!index.md)、[Elastic Stack](doc/ElasticStackImport/ElasticStackImport-Japanese.md)、[Timesketch](https://timesketch.org/)等で簡単に分析できるようになります。
Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/)グループによって作られた**Windowsイベントログのファストフォレンジックタイムライン生成**および**スレットハンティングツール**です。 Hayabusaは日本語で[「ハヤブサ」](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%A4%E3%83%96%E3%82%B5)を意味し、ハヤブサが世界で最も速く、狩猟(hunting)に優れ、とても訓練しやすい動物であることから選ばれました。[Rust](https://www.rust-lang.org/) で開発され、マルチスレッドに対応し、可能な限り高速に動作するよう配慮されています。[Sigma](https://github.com/SigmaHQ/Sigma)ルールをHayabusaルール形式に変換する[ツール](https://github.com/Yamato-Security/hayabusa-rules/tree/main/tools/sigmac)も提供しています。Hayabusaの検知ルールもSigmaと同様にYML形式であり、カスタマイズ性や拡張性に優れます。稼働中のシステムで実行してライブ調査することも、複数のシステムからログを収集してオフライン調査することも可能です。また、 [Velociraptor](https://docs.velociraptor.app/)と[Hayabusa artifact](https://docs.velociraptor.app/exchange/artifacts/pages/windows.eventlogs.hayabusa/)を用いることで企業向けの広範囲なスレットハンティングとインシデントレスポンスにも活用できます。出力は一つのCSVタイムラインにまとめられ、Excel、[Timeline Explorer](https://ericzimmerman.github.io/#!index.md)、[Elastic Stack](doc/ElasticStackImport/ElasticStackImport-Japanese.md)、[Timesketch](https://timesketch.org/)等で簡単に分析できるようになります。
## 目次
@@ -41,6 +41,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/)
- [Criticalアラートのフィルタリングとコンピュータごとのグルーピング](#criticalアラートのフィルタリングとコンピュータごとのグルーピング)
- [Elastic Stackダッシュボードでの解析](#elastic-stackダッシュボードでの解析)
- [Timesketchでの解析](#timesketchでの解析)
- [HTMLの結果サマリ](#htmlの結果サマリ)
- [タイムラインのサンプル結果](#タイムラインのサンプル結果)
- [特徴&機能](#特徴機能)
- [ダウンロード](#ダウンロード)
@@ -159,6 +160,10 @@ Hayabusaは従来のWindowsイベントログ分析解析と比較して、分
![Timesketch](screenshots/TimesketchAnalysis.png)
## HTMLの結果サマリ
![HTMLResultsSummary](screenshots/HTML-ResultsSummary.png)
# タイムラインのサンプル結果
CSVのタイムライン結果のサンプルは[こちら](https://github.com/Yamato-Security/hayabusa/tree/main/sample-results)で確認できます。
@@ -207,7 +212,7 @@ git clone https://github.com/Yamato-Security/hayabusa.git --recursive
`git pull --recurse-submodules`コマンド、もしくは以下のコマンドで`rules`フォルダを同期し、Hayabusaの最新のルールを更新することができます:
```bash
hayabusa-1.6.0-win-x64.exe -u
hayabusa-1.7.0-win-x64.exe -u
```
アップデートが失敗した場合は、`rules`フォルダの名前を変更してから、もう一回アップデートしてみて下さい。
@@ -312,20 +317,20 @@ Windows PC起動後の初回実行時に時間がかかる場合があります
コマンドプロンプトやWindows Terminalから32ビットもしくは64ビットのWindowsバイナリをHayabusaのルートディレクトリから実行します。
例: `hayabusa-1.6.0-windows-x64.exe`
例: `hayabusa-1.7.0-windows-x64.exe`
## Linux
まず、バイナリに実行権限を与える必要があります。
```bash
chmod +x ./hayabusa-1.6.0-linux-x64-gnu
chmod +x ./hayabusa-1.7.0-linux-x64-gnu
```
次に、Hayabusaのルートディレクトリから実行します
```bash
./hayabusa-1.6.0-linux-x64-gnu
./hayabusa-1.7.0-linux-x64-gnu
```
## macOS
@@ -333,13 +338,13 @@ chmod +x ./hayabusa-1.6.0-linux-x64-gnu
まず、ターミナルやiTerm2からバイナリに実行権限を与える必要があります。
```bash
chmod +x ./hayabusa-1.6.0-mac-intel
chmod +x ./hayabusa-1.7.0-mac-intel
```
次に、Hayabusaのルートディレクトリから実行してみてください
```bash
./hayabusa-1.6.0-mac-intel
./hayabusa-1.7.0-mac-intel
```
macOSの最新版では、以下のセキュリティ警告が出る可能性があります
@@ -353,7 +358,7 @@ macOSの環境設定から「セキュリティとプライバシー」を開き
その後、ターミナルからもう一回実行してみてください:
```bash
./hayabusa-1.6.0-mac-intel
./hayabusa-1.7.0-mac-intel
```
以下の警告が出るので、「開く」をクリックしてください。
@@ -393,6 +398,7 @@ ADVANCED:
--target-file-ext <EVTX_FILE_EXT>... evtx以外の拡張子を解析対象に追加する。 (例1: evtx_data 例evtx1 evtx2)
OUTPUT:
-H, --html-report <FILE> HTML形式で詳細な結果を出力する (例: results.html)
-j, --json タイムラインの出力をJSON形式で保存する例: -j -o results.json
-J, --jsonl タイムラインの出力をJSONL形式で保存する (例: -J -o results.jsonl)
-o, --output <FILE> タイムラインをCSV形式で保存する (例: results.csv)
@@ -437,91 +443,91 @@ TIME-FORMAT:
* つのWindowsイベントログファイルに対してHayabusaを実行する:
```bash
hayabusa-1.6.0-win-x64.exe -f eventlog.evtx
hayabusa-1.7.0-win-x64.exe -f eventlog.evtx
```
* `verbose`プロファイルで複数のWindowsイベントログファイルのあるsample-evtxディレクトリに対して、Hayabusaを実行する:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -P verbose
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -P verbose
```
* 全てのフィールド情報も含めてつのCSVファイルにエクスポートして、Excel、Timeline Explorer、Elastic Stack等でさらに分析することができる(注意: `verbose-details-and-all-field-info`プロファイルを使すると、出力するファイルのサイズがとても大きくなる!):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.csv -P verbose-details-and-all-field-info
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.csv -P verbose-details-and-all-field-info
```
* タイムラインをJSON形式で保存する:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.json -j
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.json -j
```
* Hayabusaルールのみを実行するデフォルトでは`-r .\rules`にあるすべてのルールが利用される):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa -o results.csv
```
* Windowsでデフォルトで有効になっているログに対してのみ、Hayabusaルールを実行する:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default -o results.csv
```
* Sysmonログに対してのみHayabusaルールを実行する:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\sysmon -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\sysmon -o results.csv
```
* Sigmaルールのみを実行する:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\sigma -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\sigma -o results.csv
```
* 廃棄(deprecated)されたルール(`status``deprecated`になっているルール)とノイジールール(`.\rules\config\noisy_rules.txt`にルールIDが書かれているルール)を有効にする:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx --enable-deprecated-rules --enable-noisy-rules -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx --enable-deprecated-rules --enable-noisy-rules -o results.csv
```
* ログオン情報を分析するルールのみを実行し、UTCタイムゾーンで出力する:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default\events\Security\Logons -U -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default\events\Security\Logons -U -o results.csv
```
* 起動中のWindows端末上で実行しAdministrator権限が必要、アラート悪意のある可能性のある動作のみを検知する:
```bash
hayabusa-1.6.0-win-x64.exe -l -m low
hayabusa-1.7.0-win-x64.exe -l -m low
```
* criticalレベルのアラートからピボットキーワードの一覧を作成する(結果は結果毎に`keywords-Ip Address.txt``keywords-Users.txt`等に出力される):
```bash
hayabusa-1.6.0-win-x64.exe -l -m critical -p -o keywords
hayabusa-1.7.0-win-x64.exe -l -m critical -p -o keywords
```
* イベントIDの統計情報を出力する:
```bash
hayabusa-1.6.0-win-x64.exe -f Security.evtx -M
hayabusa-1.7.0-win-x64.exe -f Security.evtx -M
```
* ログオンサマリを出力する:
```bash
hayabusa-1.6.0-win-x64.exe -L -f Security.evtx -M
hayabusa-1.7.0-win-x64.exe -L -f Security.evtx -M
```
* 詳細なメッセージを出力する(処理に時間がかかるファイル、パースエラー等を特定するのに便利):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -v
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -v
```
* Verbose出力の例:
@@ -542,7 +548,7 @@ Checking target evtx FilePath: "./hayabusa-sample-evtx/YamatoSecurity/T1218.004_
* 結果を[Timesketch](https://timesketch.org/)にインポートできるCSV形式に保存する:
```bash
hayabusa-1.6.0-win-x64.exe -d ../hayabusa-sample-evtx --RFC-3339 -o timesketch-import.csv -P timesketch -U
hayabusa-1.7.0-win-x64.exe -d ../hayabusa-sample-evtx --RFC-3339 -o timesketch-import.csv -P timesketch -U
```
* エラーログの出力をさせないようにする:
@@ -854,7 +860,7 @@ Hayabusaルールは、Windowsのイベントログ解析専用に設計され
## 検知レベルのlevelチューニング
Hayabusaルール、Sigmaルールはそれぞれの作者が検知した際のリスクレベルを決めています。
ユーザが独自のリスクレベルに設定するには`./rules/config/level_tuning.txt`に変換情報を書き、`hayabusa-1.6.0-win-x64.exe --level-tuning`を実行することでルールファイルが書き換えられます。
ユーザが独自のリスクレベルに設定するには`./rules/config/level_tuning.txt`に変換情報を書き、`hayabusa-1.7.0-win-x64.exe --level-tuning`を実行することでルールファイルが書き換えられます。
ルールファイルが直接書き換えられることに注意して使用してください。
`./rules/config/level_tuning.txt`の例:
@@ -903,7 +909,9 @@ id,new_level
# Windowsイベントログ設定のススメ
Windows機での悪性な活動を検知する為には、デフォルトのログ設定を改善することが必要です。
以下のサイトを閲覧することをおすすめします。:
どのようなログ設定を有効にする必要があるのか、また、自動的に適切な設定を有効にするためのスクリプトを、別のプロジェクトとして作成しました: [https://github.com/Yamato-Security/EnableWindowsLogSettings](https://github.com/Yamato-Security/EnableWindowsLogSettings)
以下のサイトを閲覧することもおすすめします。:
* [JSCU-NL (Joint Sigint Cyber Unit Netherlands) Logging Essentials](https://github.com/JSCU-NL/logging-essentials)
* [ACSC (Australian Cyber Security Centre) Logging and Fowarding Guide](https://www.cyber.gov.au/acsc/view-all-content/publications/windows-event-logging-and-forwarding)
* [Malware Archaeology Cheat Sheets](https://www.malwarearchaeology.com/cheat-sheets)

View File

@@ -21,7 +21,7 @@
# About Hayabusa
Hayabusa is a **Windows event log fast forensics timeline generator** and **threat hunting tool** created by the [Yamato Security](https://yamatosecurity.connpass.com/) group in Japan. Hayabusa means ["peregrine falcon"](https://en.wikipedia.org/wiki/Peregrine_falcon") in Japanese and was chosen as peregrine falcons are the fastest animal in the world, great at hunting and highly trainable. It is written in [Rust](https://www.rust-lang.org/) and supports multi-threading in order to be as fast as possible. We have provided a [tool](https://github.com/Yamato-Security/hayabusa-rules/tree/main/tools/sigmac) to convert [Sigma](https://github.com/SigmaHQ/sigma) rules into Hayabusa rule format. The Sigma-compatible Hayabusa detection rules are written in YML in order to be as easily customizable and extensible as possible. Hayabusa can be run either on single running systems for live analysis, by gathering logs from single or multiple systems for offline analysis, or by running the [Hayabusa artifact](https://docs.velociraptor.app/exchange/artifacts/pages/windows.eventlogs.hayabusa/) with [Velociraptor](https://docs.velociraptor.app/) for enterprise-wide threat hunting and incident response. The output will be consolidated into a single CSV timeline for easy analysis in Excel, [Timeline Explorer](https://ericzimmerman.github.io/#!index.md), [Elastic Stack](doc/ElasticStackImport/ElasticStackImport-English.md), [Timesketch](https://timesketch.org/), etc...
Hayabusa is a **Windows event log fast forensics timeline generator** and **threat hunting tool** created by the [Yamato Security](https://yamatosecurity.connpass.com/) group in Japan. Hayabusa means ["peregrine falcon"](https://en.wikipedia.org/wiki/Peregrine_falcon) in Japanese and was chosen as peregrine falcons are the fastest animal in the world, great at hunting and highly trainable. It is written in [Rust](https://www.rust-lang.org/) and supports multi-threading in order to be as fast as possible. We have provided a [tool](https://github.com/Yamato-Security/hayabusa-rules/tree/main/tools/sigmac) to convert [Sigma](https://github.com/SigmaHQ/sigma) rules into Hayabusa rule format. The Sigma-compatible Hayabusa detection rules are written in YML in order to be as easily customizable and extensible as possible. Hayabusa can be run either on single running systems for live analysis, by gathering logs from single or multiple systems for offline analysis, or by running the [Hayabusa artifact](https://docs.velociraptor.app/exchange/artifacts/pages/windows.eventlogs.hayabusa/) with [Velociraptor](https://docs.velociraptor.app/) for enterprise-wide threat hunting and incident response. The output will be consolidated into a single CSV timeline for easy analysis in Excel, [Timeline Explorer](https://ericzimmerman.github.io/#!index.md), [Elastic Stack](doc/ElasticStackImport/ElasticStackImport-English.md), [Timesketch](https://timesketch.org/), etc...
## Table of Contents
@@ -40,6 +40,7 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre
- [Critical Alert Filtering and Computer Grouping in Timeline Explorer](#critical-alert-filtering-and-computer-grouping-in-timeline-explorer)
- [Analysis with the Elastic Stack Dashboard](#analysis-with-the-elastic-stack-dashboard)
- [Analysis in Timesketch](#analysis-in-timesketch)
- [HTML Results Summary](#html-results-summary)
- [Analyzing Sample Timeline Results](#analyzing-sample-timeline-results)
- [Features](#features)
- [Downloads](#downloads)
@@ -151,6 +152,10 @@ Hayabusa hopes to let analysts get 80% of their work done in 20% of the time whe
![Timesketch](screenshots/TimesketchAnalysis.png)
## HTML Results Summary
![HTMLResultsSummary](screenshots/HTML-ResultsSummary.png)
# Analyzing Sample Timeline Results
You can check out a sample CSV timeline [here](https://github.com/Yamato-Security/hayabusa/tree/main/sample-results).
@@ -199,7 +204,7 @@ Note: If you forget to use --recursive option, the `rules` folder, which is mana
You can sync the `rules` folder and get latest Hayabusa rules with `git pull --recurse-submodules` or use the following command:
```bash
hayabusa-1.6.0-win-x64.exe -u
hayabusa-1.7.0-win-x64.exe -u
```
If the update fails, you may need to rename the `rules` folder and try again.
@@ -304,20 +309,20 @@ You may experience slow runtime especially on the first run after a reboot due t
In a Command/PowerShell Prompt or Windows Terminal, just run the appropriate 32-bit or 64-bit Windows binary.
Example: `hayabusa-1.6.0-windows-x64.exe`
Example: `hayabusa-1.7.0-windows-x64.exe`
## Linux
You first need to make the binary executable.
```bash
chmod +x ./hayabusa-1.6.0-linux-x64-gnu
chmod +x ./hayabusa-1.7.0-linux-x64-gnu
```
Then run it from the Hayabusa root directory:
```bash
./hayabusa-1.6.0-linux-x64-gnu
./hayabusa-1.7.0-linux-x64-gnu
```
## macOS
@@ -325,13 +330,13 @@ Then run it from the Hayabusa root directory:
From Terminal or iTerm2, you first need to make the binary executable.
```bash
chmod +x ./hayabusa-1.6.0-mac-intel
chmod +x ./hayabusa-1.7.0-mac-intel
```
Then, try to run it from the Hayabusa root directory:
```bash
./hayabusa-1.6.0-mac-intel
./hayabusa-1.7.0-mac-intel
```
On the latest version of macOS, you may receive the following security error when you try to run it:
@@ -345,7 +350,7 @@ Click "Cancel" and then from System Preferences, open "Security & Privacy" and f
After that, try to run it again.
```bash
./hayabusa-1.6.0-mac-intel
./hayabusa-1.7.0-mac-intel
```
The following warning will pop up, so please click "Open".
@@ -384,10 +389,11 @@ ADVANCED:
--target-file-ext <EVTX_FILE_EXT>... Specify additional target file extensions (ex: evtx_data) (ex: evtx1 evtx2)
OUTPUT:
-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 <FILE> Save the timeline in CSV format (ex: results.csv)
-P, --profile <PROFILE> Specify output profile (minimal, standard, verbose, verbose-all-field-info, verbose-details-and-all-field-info)
-H, --html-report <FILE> 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 <FILE> Save the timeline in CSV format (ex: results.csv)
-P, --profile <PROFILE> Specify output profile (minimal, standard, verbose, verbose-all-field-info, verbose-details-and-all-field-info)
DISPLAY-SETTINGS:
--no-color Disable color output
@@ -428,91 +434,91 @@ TIME-FORMAT:
* Run hayabusa against one Windows event log file with default standard profile:
```bash
hayabusa-1.6.0-win-x64.exe -f eventlog.evtx
hayabusa-1.7.0-win-x64.exe -f eventlog.evtx
```
* Run hayabusa against the sample-evtx directory with multiple Windows event log files with the verbose profile:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -P verbose
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -P verbose
```
* Export to a single CSV file for further analysis with excel, timeline explorer, elastic stack, etc... and include all field information (Warning: your file output size will become much larger with the `verbose-details-and-all-field-info` profile!):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.csv -P verbose-details-and-all-field-info
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.csv -P verbose-details-and-all-field-info
```
* Save the timline in JSON format:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.json -j
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -o results.json -j
```
* Only run hayabusa rules (the default is to run all the rules in `-r .\rules`):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa -o results.csv
```
* Only run hayabusa rules for logs that are enabled by default on Windows:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default -o results.csv
```
* Only run hayabusa rules for sysmon logs:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\sysmon -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\sysmon -o results.csv
```
* Only run sigma rules:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\sigma -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\sigma -o results.csv
```
* Enable deprecated rules (those with `status` marked as `deprecated`) and noisy rules (those whose rule ID is listed in `.\rules\config\noisy_rules.txt`):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx --enable-noisy-rules --enable-deprecated-rules -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx --enable-noisy-rules --enable-deprecated-rules -o results.csv
```
* Only run rules to analyze logons and output in the UTC timezone:
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default\events\Security\Logons -U -o results.csv
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default\events\Security\Logons -U -o results.csv
```
* Run on a live Windows machine (requires Administrator privileges) and only detect alerts (potentially malicious behavior):
```bash
hayabusa-1.6.0-win-x64.exe -l -m low
hayabusa-1.7.0-win-x64.exe -l -m low
```
* Create a list of pivot keywords from critical alerts and save the results. (Results will be saved to `keywords-Ip Addresses.txt`, `keywords-Users.txt`, etc...):
```bash
hayabusa-1.6.0-win-x64.exe -l -m critical -p -o keywords
hayabusa-1.7.0-win-x64.exe -l -m critical -p -o keywords
```
* Print Event ID metrics:
```bash
hayabusa-1.6.0-win-x64.exe -f Security.evtx -M
hayabusa-1.7.0-win-x64.exe -f Security.evtx -M
```
* Print logon summary:
```bash
hayabusa-1.6.0-win-x64.exe -L -f Security.evtx -M
hayabusa-1.7.0-win-x64.exe -L -f Security.evtx -M
```
* Print verbose information (useful for determining which files take long to process, parsing errors, etc...):
```bash
hayabusa-1.6.0-win-x64.exe -d .\hayabusa-sample-evtx -v
hayabusa-1.7.0-win-x64.exe -d .\hayabusa-sample-evtx -v
```
* Verbose output example:
@@ -533,7 +539,7 @@ Checking target evtx FilePath: "./hayabusa-sample-evtx/YamatoSecurity/T1218.004_
* Output to a CSV format compatible to import into [Timesketch](https://timesketch.org/):
```bash
hayabusa-1.6.0-win-x64.exe -d ../hayabusa-sample-evtx --RFC-3339 -o timesketch-import.csv -P timesketch -U
hayabusa-1.7.0-win-x64.exe -d ../hayabusa-sample-evtx --RFC-3339 -o timesketch-import.csv -P timesketch -U
```
* Quiet error mode:
@@ -845,7 +851,7 @@ You can also add a rule ID to `./rules/config/noisy_rules.txt` in order to ignor
Hayabusa and Sigma rule authors will determine the risk level of the alert when writing their rules.
However, the actual risk level will differ between environments.
You can tune the risk level of the rules by adding them to `./rules/config/level_tuning.txt` and executing `hayabusa-1.6.0-win-x64.exe --level-tuning` which will update the `level` line in the rule file.
You can tune the risk level of the rules by adding them to `./rules/config/level_tuning.txt` and executing `hayabusa-1.7.0-win-x64.exe --level-tuning` which will update the `level` line in the rule file.
Please note that the rule file will be updated directly.
`./rules/config/level_tuning.txt` sample line:
@@ -894,7 +900,10 @@ There is no "one tool to rule them all" and we have found that each has its own
# Windows Logging Recommendations
In order to properly detect malicious activity on Windows machines, you will need to improve the default log settings. We recommend the following sites for guidance:
In order to properly detect malicious activity on Windows machines, you will need to improve the default log settings.
We have created a seperate project to document what log settings need to be enabled as well as scripts to automatically enable the proper settings at [https://github.com/Yamato-Security/EnableWindowsLogSettings](https://github.com/Yamato-Security/EnableWindowsLogSettings).
We also recommend the following sites for guidance:
* [JSCU-NL (Joint Sigint Cyber Unit Netherlands) Logging Essentials](https://github.com/JSCU-NL/logging-essentials)
* [ACSC (Australian Cyber Security Centre) Logging and Fowarding Guide](https://www.cyber.gov.au/acsc/view-all-content/publications/windows-event-logging-and-forwarding)
* [Malware Archaeology Cheat Sheets](https://www.malwarearchaeology.com/cheat-sheets)

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1,94 @@
body {
margin: 0;
width: 100vw;
height: 100vh;
background-image:url(./background_image.jpg);
}
section {
background-image: none;
background-color:white;
margin: 0 auto;
max-width:860px;
box-shadow: 10px 10px 25px 10px rgba(0, 0, 0, .5);
}
h2 {
padding:0.25em 0.5em;
color:#494949;
background:#f4faff;
border-left : solid 5px #000000;
margin: 16px;
}
h3 {
background-color: rgb(0, 0, 0);
padding:3px;
margin: 16px;
}
#computers_with_most_unique_critical_detections {
color: #ff0000;
border-bottom: solid 3px #ff0000;
}
#top_critical_alerts {
color: #ff0000;
border-bottom: solid 3px #ff0000;
}
#computers_with_most_unique_high_detections {
color: #ffff00;
border-bottom: solid 3px #ffff00;
}
#top_high_alerts {
color: #ffff00;
border-bottom: solid 3px #ffff00;
}
#computers_with_most_unique_medium_detections {
color: #00ffff;
border-bottom: solid 3px #00ffff;
}
#top_medium_alerts {
color: #00ffff;
border-bottom: solid 3px #00ffff;
}
#computers_with_most_unique_low_detections {
color: #00ff00;
border-bottom: solid 3px #00ff00;
}
#top_low_alerts {
color: #00ff00;
border-bottom: solid 3px #00ff00;
}
#computers_with_most_unique_informational_detections {
color: #ffffff;
border-bottom: solid 3px #ffffff;
}
#top_informational_alerts {
color: #ffffff;
border-bottom: solid 3px #ffffff;
}
li {
padding: 5px;
margin: 16px;
}
li:nth-child(odd){
background-color: #f4faff
}
img#logo {
width: 300px;
display: block;
margin-left: auto;
margin-right: auto;
}

BIN
config/html_report/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

2
rules

Submodule rules updated: aaf910cdca...428abf7caa

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

View File

@@ -1,9 +1,7 @@
use crate::detections::configs;
use crate::detections::configs::{CURRENT_EXE_PATH, TERM_SIZE};
use crate::detections::message::{self, LEVEL_ABBR};
use crate::detections::message::{AlertMessage, LEVEL_FULL};
use crate::detections::utils::{self, format_time};
use crate::detections::utils::{get_writable_color, write_color_buffer};
use crate::detections::configs::{self, CURRENT_EXE_PATH, TERM_SIZE};
use crate::detections::message::{self, AlertMessage, LEVEL_ABBR, LEVEL_FULL};
use crate::detections::utils::{self, format_time, get_writable_color, write_color_buffer};
use crate::options::htmlreport;
use crate::options::profile::PROFILES;
use bytesize::ByteSize;
use chrono::{DateTime, Local, TimeZone, Utc};
@@ -24,12 +22,9 @@ use num_format::{Locale, ToFormattedString};
use std::cmp::min;
use std::error::Error;
use std::fs::File;
use std::io;
use std::io::BufWriter;
use std::io::Write;
use std::io::{self, BufWriter, Write};
use std::fs;
use std::fs::{self, File};
use std::process;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use terminal_size::Width;
@@ -210,6 +205,8 @@ fn emit_csv<W: std::io::Write>(
all_record_cnt: u128,
profile: LinkedHashMap<String, String>,
) -> io::Result<()> {
let mut html_output_stock: Vec<String> = vec![];
let html_output_flag = configs::CONFIG.read().unwrap().args.html_report.is_some();
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;
@@ -238,7 +235,7 @@ fn emit_csv<W: std::io::Write>(
HashMap::new();
let mut detect_counts_by_rule_and_level: HashMap<String, HashMap<String, i128>> =
HashMap::new();
let mut rule_title_path_map: HashMap<String, String> = HashMap::new();
let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]);
// レベル別、日ごとの集計用変数の初期化
for level_init in levels {
@@ -372,6 +369,7 @@ fn emit_csv<W: std::io::Write>(
.unwrap()
})
.clone();
rule_title_path_map.insert(detect_info.ruletitle.clone(), detect_info.rulepath.clone());
*detect_counts_by_rules
.entry(Clone::clone(&detect_info.ruletitle))
.or_insert(0) += 1;
@@ -454,31 +452,35 @@ fn emit_csv<W: std::io::Write>(
)
.ok();
write_color_buffer(&disp_wtr, get_writable_color(None), ": ", false).ok();
let saved_alerts_output =
(all_record_cnt - reducted_record_cnt).to_formatted_string(&Locale::en);
write_color_buffer(
&disp_wtr,
get_writable_color(Some(Color::Rgb(255, 255, 0))),
&(all_record_cnt - reducted_record_cnt).to_formatted_string(&Locale::en),
&saved_alerts_output,
false,
)
.ok();
write_color_buffer(&disp_wtr, get_writable_color(None), " / ", false).ok();
let all_record_output = all_record_cnt.to_formatted_string(&Locale::en);
write_color_buffer(
&disp_wtr,
get_writable_color(Some(Color::Rgb(0, 255, 255))),
&all_record_cnt.to_formatted_string(&Locale::en),
&all_record_output,
false,
)
.ok();
write_color_buffer(&disp_wtr, get_writable_color(None), " (", false).ok();
let reduction_output = format!(
"Data reduction: {} events ({:.2}%)",
reducted_record_cnt.to_formatted_string(&Locale::en),
reducted_percent
);
write_color_buffer(
&disp_wtr,
get_writable_color(Some(Color::Rgb(0, 255, 0))),
&format!(
"Data reduction: {} events ({:.2}%)",
reducted_record_cnt.to_formatted_string(&Locale::en),
reducted_percent
),
&reduction_output,
false,
)
.ok();
@@ -487,6 +489,15 @@ fn emit_csv<W: std::io::Write>(
println!();
println!();
if html_output_flag {
html_output_stock.push(format!(
"- Saved alerts and events: {}",
&saved_alerts_output
));
html_output_stock.push(format!("- Total events analyzed: {}", &all_record_output));
html_output_stock.push(format!("- {}", reduction_output));
}
_print_unique_results(
total_detect_counts_by_level,
unique_detect_counts_by_level,
@@ -496,17 +507,44 @@ fn emit_csv<W: std::io::Write>(
);
println!();
_print_detection_summary_by_date(detect_counts_by_date_and_level, &color_map);
_print_detection_summary_by_date(
detect_counts_by_date_and_level,
&color_map,
&mut html_output_stock,
);
println!();
println!();
if html_output_flag {
html_output_stock.push("".to_string());
}
_print_detection_summary_by_computer(detect_counts_by_computer_and_level, &color_map);
_print_detection_summary_by_computer(
detect_counts_by_computer_and_level,
&color_map,
&mut html_output_stock,
);
println!();
if html_output_flag {
html_output_stock.push("".to_string());
}
_print_detection_summary_tables(detect_counts_by_rule_and_level, &color_map);
_print_detection_summary_tables(
detect_counts_by_rule_and_level,
&color_map,
rule_title_path_map,
&mut html_output_stock,
);
println!();
if html_output_flag {
html_output_stock.push("".to_string());
}
}
if html_output_flag {
htmlreport::add_md_data(
"Results Summary {#results_summary}".to_string(),
html_output_stock,
);
}
Ok(())
}
@@ -634,13 +672,16 @@ fn _print_unique_results(
fn _print_detection_summary_by_date(
detect_counts_by_date: HashMap<String, HashMap<String, u128>>,
color_map: &HashMap<String, Colors>,
html_output_stock: &mut Vec<String>,
) {
let buf_wtr = BufferWriter::stdout(ColorChoice::Always);
let mut wtr = buf_wtr.buffer();
wtr.set_color(ColorSpec::new().set_fg(None)).ok();
writeln!(wtr, "Dates with most total detections:").ok();
let output_header = "Dates with most total detections:";
writeln!(wtr, "{}", output_header).ok();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_output_stock.push(format!("- {}", output_header));
}
for (idx, level) in LEVEL_ABBR.values().enumerate() {
// output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施
let detections_by_day = detect_counts_by_date.get(level).unwrap();
@@ -662,26 +703,28 @@ fn _print_detection_summary_by_date(
if !exist_max_data {
max_detect_str = "n/a".to_string();
}
write!(
wtr,
let output_str = format!(
"{}: {}",
LEVEL_FULL.get(level.as_str()).unwrap(),
&max_detect_str
)
.ok();
);
write!(wtr, "{}", output_str).ok();
if idx != LEVEL_ABBR.len() - 1 {
wtr.set_color(ColorSpec::new().set_fg(None)).ok();
write!(wtr, ", ").ok();
}
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_output_stock.push(format!(" - {}", output_str));
}
}
buf_wtr.print(&wtr).ok();
}
/// 各レベル毎で最も高い検知数を出した日付を出力する
/// 各レベル毎で最も高い検知数を出したコンピュータ名を出力する
fn _print_detection_summary_by_computer(
detect_counts_by_computer: HashMap<String, HashMap<String, i128>>,
color_map: &HashMap<String, Colors>,
html_output_stock: &mut Vec<String>,
) {
let buf_wtr = BufferWriter::stdout(ColorChoice::Always);
let mut wtr = buf_wtr.buffer();
@@ -700,6 +743,22 @@ fn _print_detection_summary_by_computer(
sorted_detections.sort_by(|a, b| (-a.1).cmp(&(-b.1)));
// html出力は各種すべてのコンピュータ名を表示するようにする
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_output_stock.push(format!(
"### Computers with most unique {} detections: {{#computers_with_most_unique_{}_detections}}",
LEVEL_FULL.get(level.as_str()).unwrap(),
LEVEL_FULL.get(level.as_str()).unwrap()
));
for x in sorted_detections.iter() {
html_output_stock.push(format!(
"- {} ({})",
x.0,
x.1.to_formatted_string(&Locale::en)
));
}
html_output_stock.push("".to_string());
}
for x in sorted_detections.iter().take(5) {
result_vec.push(format!(
"{} ({})",
@@ -733,6 +792,8 @@ fn _print_detection_summary_by_computer(
fn _print_detection_summary_tables(
detect_counts_by_rule_and_level: HashMap<String, HashMap<String, i128>>,
color_map: &HashMap<String, Colors>,
rule_title_path_map: HashMap<String, String>,
html_output_stock: &mut Vec<String>,
) {
let buf_wtr = BufferWriter::stdout(ColorChoice::Always);
let mut wtr = buf_wtr.buffer();
@@ -757,6 +818,27 @@ fn _print_detection_summary_tables(
sorted_detections.sort_by(|a, b| (-a.1).cmp(&(-b.1)));
// html出力の場合はすべての内容を出力するようにする
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_output_stock.push(format!(
"### Top {} alerts: {{#top_{}_alerts}}",
LEVEL_FULL.get(level.as_str()).unwrap(),
LEVEL_FULL.get(level.as_str()).unwrap()
));
for x in sorted_detections.iter() {
html_output_stock.push(format!(
"- [{}]({}) ({})",
x.0,
rule_title_path_map
.get(x.0)
.unwrap_or(&"<Not Found Path>".to_string())
.replace('\\', "/"),
x.1.to_formatted_string(&Locale::en)
));
}
html_output_stock.push("".to_string());
}
let take_cnt =
if LEVEL_FULL.get(level.as_str()).unwrap_or(&"-".to_string()) == "informational" {
10

View File

@@ -1,6 +1,5 @@
use crate::detections::message::AlertMessage;
use crate::detections::pivot::PivotKeyword;
use crate::detections::pivot::PIVOT_KEYWORD;
use crate::detections::pivot::{PivotKeyword, PIVOT_KEYWORD};
use crate::detections::utils;
use chrono::{DateTime, Utc};
use clap::{App, CommandFactory, Parser};
@@ -248,6 +247,10 @@ pub struct Config {
/// Do not display result summary
#[clap(help_heading = Some("DISPLAY-SETTINGS"), long = "no-summary")]
pub no_summary: 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<PathBuf>,
}
impl ConfigReader<'_> {

View File

@@ -9,19 +9,15 @@ use chrono::{TimeZone, Utc};
use itertools::Itertools;
use termcolor::{BufferWriter, Color, ColorChoice};
use crate::detections::message::AlertMessage;
use crate::detections::message::DetectInfo;
use crate::detections::message::ERROR_LOG_STACK;
use crate::detections::message::{CH_CONFIG, DEFAULT_DETAILS, TAGS_CONFIG};
use crate::detections::message::{
LOGONSUMMARY_FLAG, METRICS_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG,
AlertMessage, DetectInfo, CH_CONFIG, DEFAULT_DETAILS, ERROR_LOG_STACK, LOGONSUMMARY_FLAG,
METRICS_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, TAGS_CONFIG,
};
use crate::detections::pivot::insert_pivot_keyword;
use crate::detections::rule;
use crate::detections::rule::AggResult;
use crate::detections::rule::RuleNode;
use crate::detections::rule::{self, AggResult, RuleNode};
use crate::detections::utils::{get_serde_number_to_string, make_ascii_titlecase};
use crate::filter;
use crate::options::htmlreport::{self};
use crate::yaml::ParseYaml;
use hashbrown::HashMap;
use serde_json::Value;
@@ -31,8 +27,7 @@ use std::path::Path;
use std::sync::Arc;
use tokio::{runtime::Runtime, spawn, task::JoinHandle};
use super::message;
use super::message::LEVEL_ABBR;
use super::message::{self, LEVEL_ABBR};
// イベントファイルの1レコード分の情報を保持する構造体
#[derive(Clone, Debug)]
@@ -605,6 +600,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 mut html_report_stock = Vec::new();
sorted_ld_rc.into_iter().for_each(|(key, value)| {
if value != &0_u128 {
@@ -614,12 +610,16 @@ impl Detection {
""
};
//タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する
println!(
let output_str = format!(
"{} rules: {}{}",
make_ascii_titlecase(key.clone().as_mut()),
value,
disable_flag,
disable_flag
);
println!("{}", output_str);
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_report_stock.push(format!("- {}", output_str));
}
}
});
if err_rc != &0_u128 {
@@ -644,20 +644,24 @@ impl Detection {
} else {
""
};
let output_str = format!(
"{} rules: {} ({:.2}%){}",
make_ascii_titlecase(key.clone().as_mut()),
value,
rate,
deprecated_flag
);
//タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!(
"{} rules: {} ({:.2}%){}",
make_ascii_titlecase(key.clone().as_mut()),
value,
rate,
deprecated_flag
),
&output_str,
true,
)
.ok();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_report_stock.push(format!("- {}", output_str));
}
}
});
println!();
@@ -665,17 +669,32 @@ impl Detection {
let mut sorted_rc: Vec<(&String, &u128)> = rc.iter().collect();
sorted_rc.sort_by(|a, b| a.0.cmp(b.0));
sorted_rc.into_iter().for_each(|(key, value)| {
let output_str = format!("{} rules: {}", key, value);
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!("{} rules: {}", key, value),
&output_str,
true,
)
.ok();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_report_stock.push(format!("- {}", output_str));
}
});
println!("Total enabled detection rules: {}", total_loaded_rule_cnt);
let tmp_total_detect_output =
format!("Total enabled detection rules: {}", total_loaded_rule_cnt);
println!("{}", tmp_total_detect_output);
println!();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
html_report_stock.push(format!("- {}", tmp_total_detect_output));
}
if !html_report_stock.is_empty() {
htmlreport::add_md_data(
"General Overview {#general_overview}".to_string(),
html_report_stock,
);
}
}
}

View File

@@ -1,9 +1,6 @@
extern crate lazy_static;
use crate::detections::configs;
use crate::detections::configs::CURRENT_EXE_PATH;
use crate::detections::utils;
use crate::detections::utils::get_serde_number_to_string;
use crate::detections::utils::write_color_buffer;
use crate::detections::configs::{self, CURRENT_EXE_PATH};
use crate::detections::utils::{self, get_serde_number_to_string, write_color_buffer};
use crate::options::profile::PROFILES;
use chrono::{DateTime, Local, Utc};
use dashmap::DashMap;
@@ -13,10 +10,8 @@ use linked_hash_map::LinkedHashMap;
use regex::Regex;
use serde_json::Value;
use std::env;
use std::fs::create_dir;
use std::fs::File;
use std::io::BufWriter;
use std::io::{self, Write};
use std::fs::{create_dir, File};
use std::io::{self, BufWriter, Write};
use std::path::Path;
use std::sync::Mutex;
use termcolor::{BufferWriter, ColorChoice};

View File

@@ -2,17 +2,15 @@ extern crate base64;
extern crate csv;
extern crate regex;
use crate::detections::configs;
use crate::detections::configs::CURRENT_EXE_PATH;
use crate::detections::configs::{self, CURRENT_EXE_PATH};
use hashbrown::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use chrono::Local;
use termcolor::Color;
use tokio::runtime::Builder;
use tokio::runtime::Runtime;
use tokio::runtime::{Builder, Runtime};
use chrono::{DateTime, TimeZone, Utc};
use regex::Regex;
@@ -28,6 +26,7 @@ use std::vec;
use termcolor::{BufferWriter, ColorSpec, WriteColor};
use super::detection::EvtxRecordInfo;
use super::message::AlertMessage;
pub fn concat_selection_key(key_list: &[String]) -> String {
return key_list
@@ -481,6 +480,15 @@ where
}
}
/// Check file path exist. If path is existed, output alert message.
pub fn check_file_expect_not_exist(path: &Path, exist_alert_str: String) -> bool {
let ret = path.exists();
if ret {
AlertMessage::alert(&exist_alert_str).ok();
}
ret
}
#[cfg(test)]
mod tests {
use std::path::Path;

View File

@@ -1,7 +1,5 @@
use crate::detections::configs;
use crate::detections::message::AlertMessage;
use crate::detections::message::ERROR_LOG_STACK;
use crate::detections::message::QUIET_ERRORS_FLAG;
use crate::detections::message::{AlertMessage, ERROR_LOG_STACK, QUIET_ERRORS_FLAG};
use hashbrown::HashMap;
use regex::Regex;
use std::fs::File;

View File

@@ -6,3 +6,8 @@ pub mod omikuji;
pub mod options;
pub mod timeline;
pub mod yaml;
<<<<<<< HEAD
=======
#[macro_use]
extern crate horrorshow;
>>>>>>> d91fd31392813c79a33cf5dc10eae06db2ce2613

View File

@@ -7,8 +7,9 @@ use bytesize::ByteSize;
use chrono::{DateTime, Datelike, Local};
use evtx::{EvtxParser, ParserSettings};
use hashbrown::{HashMap, HashSet};
use hayabusa::detections::configs::{load_pivot_keywords, TargetEventTime, TARGET_EXTENSIONS};
use hayabusa::detections::configs::{CONFIG, CURRENT_EXE_PATH};
use hayabusa::detections::configs::{
load_pivot_keywords, TargetEventTime, CONFIG, CURRENT_EXE_PATH, TARGET_EXTENSIONS,
};
use hayabusa::detections::detection::{self, EvtxRecordInfo};
use hayabusa::detections::message::{
AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, LOGONSUMMARY_FLAG, METRICS_FLAG,
@@ -18,8 +19,9 @@ use hayabusa::detections::pivot::PivotKeyword;
use hayabusa::detections::pivot::PIVOT_KEYWORD;
use hayabusa::detections::rule::{get_detection_keys, RuleNode};
use hayabusa::omikuji::Omikuji;
use hayabusa::options::htmlreport::{self, HTML_REPORTER};
use hayabusa::options::profile::PROFILES;
use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules};
use hayabusa::options::{level_tuning::LevelTuning, update::Update};
use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timelines::Timeline};
use hayabusa::{detections::utils::write_color_buffer, filter};
@@ -91,6 +93,17 @@ impl App {
return;
}
let analysis_start_time: DateTime<Local> = Local::now();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
let output_data = vec![format!(
"- Start time: {}",
analysis_start_time.format("%Y/%m/%d %H:%M")
)];
htmlreport::add_md_data(
"General Overview {#general_overview}".to_string(),
output_data,
);
}
// Show usage when no arguments.
if std::env::args().len() == 1 {
self.output_logo();
@@ -107,7 +120,6 @@ impl App {
&analysis_start_time.day().to_owned()
));
}
if !self.is_matched_architecture_and_binary() {
AlertMessage::alert(
"The hayabusa version you ran does not match your PC architecture.\nPlease use the correct architecture. (Binary ending in -x64.exe for 64-bit and -x86.exe for 32-bit.)",
@@ -118,9 +130,19 @@ impl App {
}
if configs::CONFIG.read().unwrap().args.update_rules {
match UpdateRules::update_rules(
configs::CONFIG.read().unwrap().args.rules.to_str().unwrap(),
) {
// エラーが出た場合はインターネット接続がそもそもできないなどの問題点もあるためエラー等の出力は行わない
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()
);
match Update::update_rules(configs::CONFIG.read().unwrap().args.rules.to_str().unwrap())
{
Ok(output) => {
if output != "You currently have the latest rules." {
write_color_buffer(
@@ -137,6 +159,33 @@ impl App {
}
}
println!();
if latest_version_data.is_some()
&& now_version
!= &latest_version_data
.as_ref()
.unwrap_or(now_version)
.replace('\"', "")
{
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!(
"There is a new version of Hayabusa: {}",
latest_version_data.unwrap().replace('\"', "")
),
true,
)
.ok();
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
"You can download it at https://github.com/Yamato-Security/hayabusa/releases",
true,
)
.ok();
}
println!();
return;
}
// 実行時のexeファイルのパスをベースに変更する必要があるためデフォルトの値であった場合はそのexeファイルと同一階層を探すようにする
@@ -170,20 +219,21 @@ impl App {
pivot_key_unions.iter().for_each(|(key, _)| {
let keywords_file_name =
csv_path.as_path().display().to_string() + "-" + key + ".txt";
if Path::new(&keywords_file_name).exists() {
AlertMessage::alert(&format!(
utils::check_file_expect_not_exist(
Path::new(&keywords_file_name),
format!(
" The file {} already exists. Please specify a different filename.",
&keywords_file_name
))
.ok();
}
),
);
});
if csv_path.exists() {
AlertMessage::alert(&format!(
if utils::check_file_expect_not_exist(
csv_path,
format!(
" The file {} already exists. Please specify a different filename.",
csv_path.as_os_str().to_str().unwrap()
))
.ok();
),
) {
return;
}
}
@@ -214,6 +264,29 @@ impl App {
println!();
}
if let Some(html_path) = &configs::CONFIG.read().unwrap().args.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(),
format!(
" The file {} already exists. Please specify a different filename.",
html_path.to_str().unwrap()
),
) {
return;
}
}
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!(
"Start time: {}\n",
analysis_start_time.format("%Y/%m/%d %H:%M")
),
true,
)
.ok();
if configs::CONFIG.read().unwrap().args.live_analysis {
let live_analysis_list = self.collect_liveanalysis_files();
if live_analysis_list.is_none() {
@@ -322,15 +395,22 @@ impl App {
let analysis_end_time: DateTime<Local> = Local::now();
let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time);
let elapsed_output_str = format!("Elapsed Time: {}", &analysis_duration.hhmmssxxx());
write_color_buffer(
&BufferWriter::stdout(ColorChoice::Always),
None,
&format!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()),
&elapsed_output_str,
true,
)
.ok();
println!();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
let output_data = vec![format!("- {}", elapsed_output_str)];
htmlreport::add_md_data(
"General Overview {#general_overview}".to_string(),
output_data,
);
}
// Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが0となるのでエラーログファイル自体が生成されない。
if ERROR_LOG_STACK.lock().unwrap().len() > 0 {
AlertMessage::create_error_log(ERROR_LOG_PATH.to_string());
@@ -408,6 +488,22 @@ impl App {
});
}
}
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
let html_str = HTML_REPORTER.read().unwrap().clone().create_html();
htmlreport::create_html_file(
html_str,
configs::CONFIG
.read()
.unwrap()
.args
.html_report
.as_ref()
.unwrap()
.to_str()
.unwrap_or("")
.to_string(),
)
}
}
#[cfg(not(target_os = "windows"))]
@@ -504,7 +600,6 @@ impl App {
}
}
}
fn analysis_files(&mut self, evtx_files: Vec<PathBuf>, time_filter: &TargetEventTime) {
let level = configs::CONFIG
.read()
@@ -525,11 +620,23 @@ impl App {
let meta = fs::metadata(file_path).ok();
total_file_size += ByteSize::b(meta.unwrap().len());
}
println!("Total file size: {}", total_file_size.to_string_as(false));
let total_size_output = format!("Total file size: {}", total_file_size.to_string_as(false));
println!("{}", total_size_output);
println!();
println!("Loading detections rules. Please wait.");
println!();
if configs::CONFIG.read().unwrap().args.html_report.is_some() {
let output_data = vec![
format!("- Analyzed event files: {}", evtx_files.len()),
format!("- {}", total_size_output),
];
htmlreport::add_md_data(
"General Overview #{general_overview}".to_string(),
output_data,
);
}
let rule_files = detection::Detection::parse_rule_files(
level,
&configs::CONFIG.read().unwrap().args.rules,
@@ -549,15 +656,23 @@ impl App {
self.rule_keys = self.get_all_keys(&rule_files);
let mut detection = detection::Detection::new(rule_files);
let mut total_records: usize = 0;
let mut tl = Timeline::new();
for evtx_file in evtx_files {
if configs::CONFIG.read().unwrap().args.verbose {
println!("Checking target evtx FilePath: {:?}", &evtx_file);
}
let cnt_tmp: usize;
(detection, cnt_tmp) = self.analysis_file(evtx_file, detection, time_filter);
(detection, cnt_tmp, tl) =
self.analysis_file(evtx_file, detection, time_filter, tl.clone());
total_records += cnt_tmp;
pb.inc();
}
if *METRICS_FLAG {
tl.tm_stats_dsp_msg();
}
if *LOGONSUMMARY_FLAG {
tl.tm_logon_stats_dsp_msg();
}
if configs::CONFIG.read().unwrap().args.output.is_some() {
println!();
println!();
@@ -576,15 +691,15 @@ impl App {
evtx_filepath: PathBuf,
mut detection: detection::Detection,
time_filter: &TargetEventTime,
) -> (detection::Detection, usize) {
mut tl: Timeline,
) -> (detection::Detection, usize, Timeline) {
let path = evtx_filepath.display();
let parser = self.evtx_to_jsons(evtx_filepath.clone());
let mut record_cnt = 0;
if parser.is_none() {
return (detection, record_cnt);
return (detection, record_cnt, tl);
}
let mut tl = Timeline::new();
let mut parser = parser.unwrap();
let mut records = parser.records_json_value();
@@ -653,10 +768,7 @@ impl App {
}
}
tl.tm_stats_dsp_msg();
tl.tm_logon_stats_dsp_msg();
(detection, record_cnt)
(detection, record_cnt, tl)
}
async fn create_rec_infos(

165
src/options/htmlreport.rs Normal file
View File

@@ -0,0 +1,165 @@
use hashbrown::HashMap;
use horrorshow::helper::doctype;
use horrorshow::prelude::*;
use lazy_static::lazy_static;
use pulldown_cmark::{html, Options, Parser};
use std::fs::{create_dir, File};
use std::io::{BufWriter, Write};
use std::path::Path;
use std::sync::RwLock;
lazy_static! {
pub static ref HTML_REPORTER: RwLock<HtmlReporter> = RwLock::new(HtmlReporter::new());
}
#[derive(Clone)]
pub struct HtmlReporter {
pub section_order: Vec<String>,
pub md_datas: HashMap<String, Vec<String>>,
}
impl HtmlReporter {
pub fn new() -> HtmlReporter {
let (init_section_order, init_data) = get_init_md_data_map();
HtmlReporter {
section_order: init_section_order,
md_datas: init_data,
}
}
/// return converted String from md_data(markdown fmt string).
pub fn create_html(self) -> String {
let mut options = Options::empty();
options.insert(Options::ENABLE_TABLES);
options.insert(Options::ENABLE_HEADING_ATTRIBUTES);
options.insert(Options::ENABLE_FOOTNOTES);
let mut md_data = vec![];
for section_name in self.section_order {
if let Some(v) = self.md_datas.get(&section_name) {
md_data.push(format!("## {}\n", &section_name));
if v.is_empty() {
md_data.push("not found data.\n".to_string());
} else {
md_data.push(v.join("\n"));
}
}
}
let md_str = md_data.join("\n");
let parser = Parser::new_ext(&md_str, options);
let mut ret = String::new();
html::push_html(&mut ret, parser);
ret
}
}
impl Default for HtmlReporter {
fn default() -> Self {
Self::new()
}
}
/// get html report section data in LinkedHashMap
fn get_init_md_data_map() -> (Vec<String>, HashMap<String, Vec<String>>) {
let mut ret = HashMap::new();
let section_order = vec![
"General Overview {#general_overview}".to_string(),
"Results Summary {#results_summary}".to_string(),
];
for section in section_order.iter() {
ret.insert(section.to_owned(), vec![]);
}
(section_order, ret)
}
pub fn add_md_data(section_name: String, data: Vec<String>) {
let mut md_with_section_data = HTML_REPORTER.write().unwrap().md_datas.clone();
for c in data {
let entry = md_with_section_data
.entry(section_name.clone())
.or_insert(Vec::new());
entry.push(c);
}
HTML_REPORTER.write().unwrap().md_datas = md_with_section_data;
}
/// create html file
pub fn create_html_file(input_html: String, path_str: String) {
let path = Path::new(&path_str);
if !path.parent().unwrap().exists() {
create_dir(path.parent().unwrap()).ok();
}
let mut html_writer = BufWriter::new(File::create(path).unwrap());
let html_data = format!(
"{}",
html! {
: doctype::HTML;
html {
head {
meta(charset="UTF-8");
link(rel="stylesheet", type="text/css", href="./config/html_report/hayabusa_report.css");
link(rel="icon", type="image/png", href="./config/html_report/favicon.png");
}
body {
section {
img(id="logo", src = "./config/html_report/logo.png");
: Raw(input_html.as_str());
}
}
}
}
);
writeln!(html_writer, "{}", html_data).ok();
println!(
"HTML Report was generated. Please check {} for details.",
path_str
);
println!();
}
#[cfg(test)]
mod tests {
use crate::options::htmlreport::HtmlReporter;
#[test]
fn test_create_html() {
let mut html_reporter = HtmlReporter::new();
let general_data = vec![
"- Analyzed event files: 581".to_string(),
"- Total file size: 148.5 MB".to_string(),
"- Excluded rules: 12".to_string(),
"- Noisy rules: 5 (Disabled)".to_string(),
"- Experimental rules: 1935 (65.97%)".to_string(),
"- Stable rules: 215 (7.33%)".to_string(),
"- Test rules: 783 (26.70%)".to_string(),
"- Hayabusa rules: 138".to_string(),
"- Sigma rules: 2795".to_string(),
"- Total enabled detection rules: 2933".to_string(),
"- Elapsed Time: 00:00:29.035".to_string(),
"".to_string(),
];
html_reporter.md_datas.insert(
"General Overview {#general_overview}".to_string(),
general_data.clone(),
);
let general_overview_str = format!(
"<ul>\n<li>{}</li>\n</ul>",
general_data[..general_data.len() - 1]
.join("</li>\n<li>")
.replace("- ", "")
);
let expect_str = format!(
"<h2 id=\"general_overview\">General Overview</h2>\n{}\n<h2 id=\"results_summary\">Results Summary</h2>\n<p>not found data.</p>\n",
general_overview_str
);
assert_eq!(html_reporter.create_html(), expect_str);
}
}

View File

@@ -1,3 +1,4 @@
pub mod htmlreport;
pub mod level_tuning;
pub mod profile;
pub mod update_rules;
pub mod update;

View File

@@ -4,7 +4,8 @@ use crate::filter;
use crate::yaml::ParseYaml;
use chrono::{DateTime, Local, TimeZone};
use git2::Repository;
use std::fs::{self};
use serde_json::Value;
use std::fs::{self, create_dir};
use std::path::Path;
use hashbrown::{HashMap, HashSet};
@@ -12,13 +13,28 @@ use std::cmp::Ordering;
use std::time::SystemTime;
use std::fs::create_dir;
use termcolor::{BufferWriter, ColorChoice};
pub struct UpdateRules {}
pub struct Update {}
impl Update {
/// get latest hayabusa version number.
pub fn get_latest_hayabusa_version() -> Result<Option<String>, Box<dyn std::error::Error>> {
let res = reqwest::blocking::Client::new()
.get("https://api.github.com/repos/Yamato-Security/hayabusa/releases/latest")
.header("User-Agent", "HayabusaUpdateChecker")
.header("Accept", "application/vnd.github.v3+json")
.send()?;
let text = res.text()?;
let json_res: Value = serde_json::from_str(&text)?;
if json_res["tag_name"].is_null() {
Ok(None)
} else {
Ok(Some(json_res["tag_name"].to_string()))
}
}
impl UpdateRules {
/// update rules(hayabusa-rules subrepository)
pub fn update_rules(rule_path: &str) -> Result<String, git2::Error> {
let mut result;
@@ -35,14 +51,14 @@ impl UpdateRules {
)
.ok();
// execution git clone of hayabusa-rules repository when failed open hayabusa repository.
result = UpdateRules::clone_rules(Path::new(rule_path));
result = Update::clone_rules(Path::new(rule_path));
} else if hayabusa_rule_repo.is_ok() {
// case of exist hayabusa-rules repository
UpdateRules::_repo_main_reset_hard(hayabusa_rule_repo.as_ref().unwrap())?;
Update::_repo_main_reset_hard(hayabusa_rule_repo.as_ref().unwrap())?;
// case of failed fetching origin/main, git clone is not executed so network error has occurred possibly.
prev_modified_rules = UpdateRules::get_updated_rules(rule_path, &prev_modified_time);
prev_modified_rules = Update::get_updated_rules(rule_path, &prev_modified_time);
prev_modified_time = fs::metadata(rule_path).unwrap().modified().unwrap();
result = UpdateRules::pull_repository(&hayabusa_rule_repo.unwrap());
result = Update::pull_repository(&hayabusa_rule_repo.unwrap());
} else {
// case of no exist hayabusa-rules repository in rules.
// execute update because submodule information exists if hayabusa repository exists submodule information.
@@ -61,7 +77,7 @@ impl UpdateRules {
for mut submodule in submodules {
submodule.update(true, None)?;
let submodule_repo = submodule.open()?;
if let Err(e) = UpdateRules::pull_repository(&submodule_repo) {
if let Err(e) = Update::pull_repository(&submodule_repo) {
AlertMessage::alert(&format!("Failed submodule update. {}", e)).ok();
is_success_submodule_update = false;
}
@@ -80,16 +96,13 @@ impl UpdateRules {
)
.ok();
// execution git clone of hayabusa-rules repository when failed open hayabusa repository.
result = UpdateRules::clone_rules(rules_path);
result = Update::clone_rules(rules_path);
}
}
if result.is_ok() {
let updated_modified_rules =
UpdateRules::get_updated_rules(rule_path, &prev_modified_time);
result = UpdateRules::print_diff_modified_rule_dates(
prev_modified_rules,
updated_modified_rules,
);
let updated_modified_rules = Update::get_updated_rules(rule_path, &prev_modified_time);
result =
Update::print_diff_modified_rule_dates(prev_modified_rules, updated_modified_rules);
}
result
}
@@ -254,7 +267,7 @@ impl UpdateRules {
#[cfg(test)]
mod tests {
use crate::options::update_rules::UpdateRules;
use crate::options::update::Update;
use std::time::SystemTime;
#[test]
@@ -262,12 +275,12 @@ mod tests {
let prev_modified_time: SystemTime = SystemTime::UNIX_EPOCH;
let prev_modified_rules =
UpdateRules::get_updated_rules("test_files/rules/level_yaml", &prev_modified_time);
Update::get_updated_rules("test_files/rules/level_yaml", &prev_modified_time);
assert_eq!(prev_modified_rules.len(), 5);
let target_time: SystemTime = SystemTime::now();
let prev_modified_rules2 =
UpdateRules::get_updated_rules("test_files/rules/level_yaml", &target_time);
Update::get_updated_rules("test_files/rules/level_yaml", &target_time);
assert_eq!(prev_modified_rules2.len(), 0);
}
}

View File

@@ -2,25 +2,13 @@ use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG};
use crate::detections::{detection::EvtxRecordInfo, utils};
use hashbrown::HashMap;
#[derive(Debug)]
pub struct LogEventInfo {
pub channel: String,
pub eventid: String,
}
impl LogEventInfo {
pub fn new(channel: String, eventid: String) -> LogEventInfo {
LogEventInfo { channel, eventid }
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct EventMetrics {
pub total: usize,
pub filepath: String,
pub start_time: String,
pub end_time: String,
pub stats_list: HashMap<String, usize>,
pub stats_list: HashMap<(String, String), usize>,
pub stats_login_list: HashMap<String, [usize; 2]>,
}
/**
@@ -32,7 +20,7 @@ impl EventMetrics {
filepath: String,
start_time: String,
end_time: String,
stats_list: HashMap<String, usize>,
stats_list: HashMap<(String, String), usize>,
stats_login_list: HashMap<String, [usize; 2]>,
) -> EventMetrics {
EventMetrics {
@@ -78,87 +66,71 @@ impl EventMetrics {
self.filepath = records[0].evtx_filepath.as_str().to_owned();
// sortしなくてもイベントログのTimeframeを取得できるように修正しました。
// sortしないことにより計算量が改善されています。
// もうちょっと感じに書けるといえば書けます。
for record in records.iter() {
let evttime = utils::get_event_value(
if let Some(evttime) = utils::get_event_value(
"Event.System.TimeCreated_attributes.SystemTime",
&record.record,
)
.map(|evt_value| evt_value.to_string());
if evttime.is_none() {
continue;
}
let evttime = evttime.unwrap();
if self.start_time.is_empty() || evttime < self.start_time {
self.start_time = evttime.to_string();
}
if self.end_time.is_empty() || evttime > self.end_time {
self.end_time = evttime;
}
.map(|evt_value| evt_value.to_string())
{
if self.start_time.is_empty() || evttime < self.start_time {
self.start_time = evttime.to_string();
}
if self.end_time.is_empty() || evttime > self.end_time {
self.end_time = evttime;
}
};
}
self.total += records.len();
}
// EventIDで集計
/// EventID`で集計
fn stats_eventid(&mut self, records: &[EvtxRecordInfo]) {
// let mut evtstat_map = HashMap::new();
for record in records.iter() {
let channel = utils::get_event_value("Channel", &record.record);
let evtid = utils::get_event_value("EventID", &record.record);
if channel.is_none() {
continue;
}
if evtid.is_none() {
continue;
}
let ch = channel.unwrap().to_string();
let id = evtid.unwrap().to_string();
let mut chandid = ch + "," + &id;
chandid.retain(|c| c != '"');
//let logdata = LogEventInfo::new(ch , id);
//println!("{:?},{:?}", logdata.channel, logdata.eventid);
let count: &mut usize = self.stats_list.entry(chandid).or_insert(0);
*count += 1;
let channel = if let Some(ch) = utils::get_event_value("Channel", &record.record) {
ch.to_string()
} else {
"-".to_string()
};
if let Some(idnum) = utils::get_event_value("EventID", &record.record) {
let count: &mut usize = self
.stats_list
.entry((idnum.to_string(), channel))
.or_insert(0);
*count += 1;
};
}
// return evtstat_map;
}
// Login event
fn stats_login_eventid(&mut self, records: &[EvtxRecordInfo]) {
for record in records.iter() {
let evtid = utils::get_event_value("EventID", &record.record);
if evtid.is_none() {
continue;
}
let idnum: i64 = if evtid.unwrap().is_number() {
evtid.unwrap().as_i64().unwrap()
} else {
evtid
.unwrap()
.as_str()
.unwrap()
.parse::<i64>()
.unwrap_or_default()
};
if !(idnum == 4624 || idnum == 4625) {
continue;
}
if let Some(evtid) = utils::get_event_value("EventID", &record.record) {
let idnum: i64 = if evtid.is_number() {
evtid.as_i64().unwrap()
} else {
evtid.as_str().unwrap().parse::<i64>().unwrap_or_default()
};
if !(idnum == 4624 || idnum == 4625) {
continue;
}
let username = utils::get_event_value("TargetUserName", &record.record);
let countlist: [usize; 2] = [0, 0];
if idnum == 4624 {
let count: &mut [usize; 2] = self
.stats_login_list
.entry(username.unwrap().to_string())
.or_insert(countlist);
count[0] += 1;
} else if idnum == 4625 {
let count: &mut [usize; 2] = self
.stats_login_list
.entry(username.unwrap().to_string())
.or_insert(countlist);
count[1] += 1;
}
let username = utils::get_event_value("TargetUserName", &record.record);
let countlist: [usize; 2] = [0, 0];
if idnum == 4624 {
let count: &mut [usize; 2] = self
.stats_login_list
.entry(username.unwrap().to_string())
.or_insert(countlist);
count[0] += 1;
} else if idnum == 4625 {
let count: &mut [usize; 2] = self
.stats_login_list
.entry(username.unwrap().to_string())
.or_insert(countlist);
count[1] += 1;
}
};
}
}
}

View File

@@ -1,11 +1,18 @@
use crate::detections::message::{LOGONSUMMARY_FLAG, METRICS_FLAG};
use std::fs::File;
use std::io::BufWriter;
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;
use comfy_table::presets::UTF8_FULL;
use comfy_table::*;
use csv::WriterBuilder;
use downcast_rs::__std::process;
use super::metrics::EventMetrics;
use hashbrown::HashMap;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Timeline {
pub stats: EventMetrics,
}
@@ -41,21 +48,62 @@ impl Timeline {
}
// 出力メッセージ作成
let mut sammsges: Vec<String> = Vec::new();
sammsges.push("---------------------------------------".to_string());
sammsges.push(format!("Evtx File Path: {}", self.stats.filepath));
sammsges.push(format!("Total Event Records: {}\n", self.stats.total));
sammsges.push(format!("First Timestamp: {}", self.stats.start_time));
sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time));
let total_event_record = format!("\nTotal Event Records: {}\n", self.stats.total);
if CONFIG.read().unwrap().args.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));
sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time));
} else {
sammsges.push(total_event_record);
}
let header = vec!["Count", "Percent", "Channel", "ID", "Event"];
let target;
let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().args.output {
// output to file
match File::create(csv_path) {
Ok(file) => {
target = Box::new(BufWriter::new(file));
Some(WriterBuilder::new().from_writer(target))
}
Err(err) => {
AlertMessage::alert(&format!("Failed to open file. {}", err)).ok();
process::exit(1);
}
}
} else {
None
};
if let Some(ref mut w) = wtr {
w.write_record(&header).ok();
}
let mut stats_tb = Table::new();
stats_tb
.load_preset(UTF8_FULL)
.apply_modifier(UTF8_ROUND_CORNERS);
stats_tb.set_header(header);
// 集計件数でソート
let mut mapsorted: Vec<_> = self.stats.stats_list.iter().collect();
mapsorted.sort_by(|x, y| y.1.cmp(x.1));
// イベントID毎の出力メッセージ生成
let stats_msges: Vec<Vec<String>> = self.tm_stats_set_msg(mapsorted);
for msgprint in sammsges.iter() {
println!("{}", msgprint);
}
// イベントID毎の出力メッセージ生成
self.tm_stats_set_msg(mapsorted);
if CONFIG.read().unwrap().args.output.is_some() {
for msg in stats_msges.iter() {
if let Some(ref mut w) = wtr {
w.write_record(msg).ok();
}
}
}
stats_tb.add_rows(stats_msges);
println!("{stats_tb}");
}
pub fn tm_logon_stats_dsp_msg(&mut self) {
@@ -64,12 +112,15 @@ impl Timeline {
}
// 出力メッセージ作成
let mut sammsges: Vec<String> = Vec::new();
sammsges.push("---------------------------------------".to_string());
sammsges.push(format!("Evtx File Path: {}", self.stats.filepath));
sammsges.push(format!("Total Event Records: {}\n", self.stats.total));
sammsges.push(format!("First Timestamp: {}", self.stats.start_time));
sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time));
sammsges.push("---------------------------------------".to_string());
let total_event_record = format!("\nTotal Event Records: {}\n", self.stats.total);
if CONFIG.read().unwrap().args.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));
sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time));
} else {
sammsges.push(total_event_record);
}
for msgprint in sammsges.iter() {
println!("{}", msgprint);
}
@@ -78,11 +129,13 @@ impl Timeline {
}
// イベントID毎の出力メッセージ生成
fn tm_stats_set_msg(&self, mapsorted: Vec<(&std::string::String, &usize)>) {
let mut eid_metrics_tb = Table::new();
eid_metrics_tb.set_header(vec!["Count", "Percent(%)", "channel,ID", "Eventtitle"]);
fn tm_stats_set_msg(
&self,
mapsorted: Vec<(&(std::string::String, std::string::String), &usize)>,
) -> Vec<Vec<String>> {
let mut msges: Vec<Vec<String>> = Vec::new();
for (event_id, event_cnt) in mapsorted.iter() {
for ((event_id, channel), event_cnt) in mapsorted.iter() {
// 件数の割合を算出
let rate: f32 = **event_cnt as f32 / self.stats.total as f32;
@@ -98,38 +151,44 @@ impl Timeline {
.read()
.unwrap()
.event_timeline_config
.get_event_id(*event_id)
.get_event_id(event_id)
.is_some();
// event_id_info.txtに登録あるものは情報設定
// 出力メッセージ1行作成
let fmted_channel = channel.replace('\"', "");
let ch = CH_CONFIG
.get(fmted_channel.to_lowercase().as_str())
.unwrap_or(&fmted_channel)
.to_string();
if conf {
eid_metrics_tb.add_row(vec![
Cell::new(&event_cnt),
Cell::new(&rate),
Cell::new(&event_id),
Cell::new(
&CONFIG
.read()
.unwrap()
.event_timeline_config
.get_event_id(*event_id)
.unwrap()
.evttitle,
),
msges.push(vec![
event_cnt.to_string(),
format!("{:.1}%", (rate * 1000.0).round() / 10.0),
ch,
event_id.to_string(),
CONFIG
.read()
.unwrap()
.event_timeline_config
.get_event_id(event_id)
.unwrap()
.evttitle
.to_string(),
]);
} else {
// 出力メッセージ1行作成
eid_metrics_tb.add_row(vec![
Cell::new(&event_cnt),
Cell::new(&rate),
Cell::new(&event_id),
Cell::new(&"Unknown".to_string()),
msges.push(vec![
event_cnt.to_string(),
format!("{:.1}%", (rate * 1000.0).round() / 10.0),
ch,
event_id.replace('\"', ""),
"Unknown".to_string(),
]);
}
}
println!("{eid_metrics_tb}");
println!();
msges
}
// ユーザ毎のログイン統計情報出力メッセージ生成
/// ユーザ毎のログイン統計情報出力メッセージ生成
fn tm_loginstats_tb_set_msg(&self) {
println!("Logon Summary");
if self.stats.stats_login_list.is_empty() {
@@ -141,23 +200,45 @@ impl Timeline {
println!("{}", msgprint);
}
} else {
let header = vec!["User", "Failed", "Successful"];
let target;
let mut wtr = if let Some(csv_path) = &CONFIG.read().unwrap().args.output {
// output to file
match File::create(csv_path) {
Ok(file) => {
target = Box::new(BufWriter::new(file));
Some(WriterBuilder::new().from_writer(target))
}
Err(err) => {
AlertMessage::alert(&format!("Failed to open file. {}", err)).ok();
process::exit(1);
}
}
} else {
None
};
if let Some(ref mut w) = wtr {
w.write_record(&header).ok();
}
let mut logins_stats_tb = Table::new();
logins_stats_tb.set_header(vec!["User", "Failed", "Successful"]);
logins_stats_tb
.load_preset(UTF8_FULL)
.apply_modifier(UTF8_ROUND_CORNERS);
logins_stats_tb.set_header(&header);
// 集計件数でソート
let mut mapsorted: Vec<_> = self.stats.stats_login_list.iter().collect();
mapsorted.sort_by(|x, y| x.0.cmp(y.0));
for (key, values) in &mapsorted {
let mut username: String = key.to_string();
//key.to_string().retain(|c| c != '\"');
//key.to_string().pop();
username.pop();
username.remove(0);
logins_stats_tb.add_row(vec![
Cell::new(&username),
Cell::new(&values[1].to_string()),
Cell::new(&values[0].to_string()),
]);
let record_data = vec![username, values[1].to_string(), values[0].to_string()];
if let Some(ref mut w) = wtr {
w.write_record(&record_data).ok();
}
logins_stats_tb.add_row(record_data);
}
println!("{logins_stats_tb}");
println!();