diff --git a/.gitignore b/.gitignore index cb604ce6..882e7f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ /.vscode/ .DS_Store test_* -.env \ No newline at end of file +.env diff --git a/README-Japanese.md b/README-Japanese.md index 9e13daae..2eaf5665 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -35,7 +35,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/) - [Timeline Explorerでの解析:](#timeline-explorerでの解析) - [Criticalアラートのフィルタリングとコンピュータごとのグルーピング:](#criticalアラートのフィルタリングとコンピュータごとのグルーピング) - [タイムラインのサンプル結果](#タイムラインのサンプル結果) -- [特徴](#特徴) +- [特徴&機能](#特徴機能) - [予定されている機能](#予定されている機能) - [ダウンロード](#ダウンロード) - [ソースコードからのコンパイル(任意)](#ソースコードからのコンパイル任意) @@ -52,6 +52,7 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/) - [使用方法](#使用方法) - [コマンドラインオプション](#コマンドラインオプション) - [使用例](#使用例) + - [ピボットキーワードの作成](#ピボットキーワードの作成) - [サンプルevtxファイルでHayabusaをテストする](#サンプルevtxファイルでhayabusaをテストする) - [Hayabusaの出力](#hayabusaの出力) - [プログレスバー](#プログレスバー) @@ -61,6 +62,8 @@ Hayabusaは、日本の[Yamato Security](https://yamatosecurity.connpass.com/) - [検知ルールのチューニング](#検知ルールのチューニング) - [イベントIDフィルタリング](#イベントidフィルタリング) - [その他のWindowsイベントログ解析ツールおよび関連プロジェクト](#その他のwindowsイベントログ解析ツールおよび関連プロジェクト) +- [Windowsイベントログ設定のススメ](#windowsイベントログ設定のススメ) +- [Sysmon関係のプロジェクト](#sysmon関係のプロジェクト) - [Sigmaをサポートする他の類似ツールとの比較](#sigmaをサポートする他の類似ツールとの比較) - [コミュニティによるドキュメンテーション](#コミュニティによるドキュメンテーション) - [英語](#英語) @@ -115,7 +118,7 @@ CSVのタイムライン結果のサンプルは[こちら](https://github.com/Y CSVのタイムラインをExcelやTimeline Explorerで分析する方法は[こちら](doc/CSV-AnalysisWithExcelAndTimelineExplorer-Japanese.pdf)で紹介しています。 -# 特徴 +# 特徴&機能 * クロスプラットフォーム対応: Windows, Linux, macOS。 * Rustで開発され、メモリセーフでハヤブサよりも高速です! @@ -127,6 +130,7 @@ CSVのタイムラインをExcelやTimeline Explorerで分析する方法は[こ * イベントログの統計。(どのような種類のイベントがあるのかを把握し、ログ設定のチューニングに有効です。) * 不良ルールやノイズの多いルールを除外するルールチューニング設定が可能です。 * MITRE ATT&CKとのマッピング (CSVの出力ファイルのみ)。 +* イベントログから不審なユーザやファイルを素早く特定するのに有用な、ピボットキーワードの一覧を作成することが可能です。 # 予定されている機能 @@ -311,6 +315,7 @@ USAGE: -s --statistics 'イベント ID の統計情報を表示する。' -q --quiet 'Quietモード。起動バナーを表示しない。' -Q --quiet-errors 'Quiet errorsモード。エラーログを保存しない。' + -p --pivot-keywords-list 'ピボットキーワードの一覧作成。' --contributors 'コントリビュータの一覧表示。' ``` @@ -376,6 +381,12 @@ hayabusa.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default\events\Securi hayabusa.exe -l -m low ``` +* criticalレベルのアラートからピボットキーワードの一覧を作成します(結果は結果毎に`keywords-Ip Address.txt`や`keyworss-Users.txt`等に出力されます): + +```bash +hayabusa.exe -l -m critical -p -o keywords +``` + * イベントIDの統計情報を取得します: ```bash @@ -403,10 +414,28 @@ Checking target evtx FilePath: "./hayabusa-sample-evtx/YamatoSecurity/T1218.004_ 5 / 509 [=>------------------------------------------------------------------------------------------------------------------------------------------] 0.98 % 1s ``` -* Quiet error mode: +* エラーログの出力をさせないようにする: デフォルトでは、Hayabusaはエラーメッセージをエラーログに保存します。 エラーメッセージを保存したくない場合は、`-Q`を追加してください。 +## ピボットキーワードの作成 + +`-p`もしくは`--pivot-keywords-list`オプションを使うことで不審なユーザやホスト名、プロセスなどを一覧で出力することができ、イベントログから素早く特定することができます。 +ピボットキーワードのカスタマイズは`config/pivot_keywords.txt`を変更することで行うことができます。以下はデフォルトの設定になります。: + +``` +Users.SubjectUserName +Users.TargetUserName +Users.User +Logon IDs.SubjectLogonId +Logon IDs.TargetLogonId +Workstation Names.WorkstationName +Ip Addresses.IpAddress +Processes.Image +``` + +形式は`KeywordName.FieldName`となっています。例えばデフォルトの設定では、`Users`というリストは検知したイベントから`SubjectUserName`、 `TargetUserName` 、 `User`のフィールドの値が一覧として出力されます。hayabusaのデフォルトでは検知したすべてのイベントから結果を出力するため、`--pivot-keyword-list`オプションを使うときには `-m` もしくは `--min-level` オプションを併せて使って検知するイベントのレベルを指定することをおすすめします。まず`-m critical`を指定して、最も高い`critical`レベルのアラートのみを対象として、レベルを必要に応じて下げていくとよいでしょう。結果に正常なイベントにもある共通のキーワードが入っている可能性が高いため、手動で結果を確認してから、不審なイベントにありそうなキーワードリストを1つのファイルに保存し、`grep -f keywords.txt timeline.csv`等のコマンドで不審なアクティビティに絞ったタイムラインを作成することができます。 + # サンプルevtxファイルでHayabusaをテストする Hayabusaをテストしたり、新しいルールを作成したりするためのサンプルevtxファイルをいくつか提供しています: [https://github.com/Yamato-Security/Hayabusa-sample-evtx](https://github.com/Yamato-Security/Hayabusa-sample-evtx) @@ -532,6 +561,20 @@ Sigmaルールは、最初にHayabusaルール形式に変換する必要があ * [WELA (Windows Event Log Analyzer)](https://github.com/Yamato-Security/WELA/) - [Yamato Security](https://github.com/Yamato-Security/)によるWindowsイベントログ解析のマルチツール。 * [Zircolite](https://github.com/wagga40/Zircolite) - Pythonで書かれたSigmaベースの攻撃検知ツール。 +# Windowsイベントログ設定のススメ + +Windows機での悪性な活動を検知する為には、デフォルトのログ設定を改善することが必要です。 +以下のサイトを閲覧することをおすすめします。: +* [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) + +# Sysmon関係のプロジェクト + +フォレンジックに有用な証拠を作り、高い精度で検知をさせるためには、sysmonをインストールする必要があります。以下のサイトを参考に設定することをおすすめします。: +* [Sysmon Modular](https://github.com/olafhartong/sysmon-modular) +* [TrustedSec Sysmon Community Guide](https://github.com/trustedsec/SysmonCommunityGuide) + ## Sigmaをサポートする他の類似ツールとの比較 対象となるサンプルデータ、コマンドラインオプション、ルールのチューニング等によって結果が異なるため、完全な比較はできませんが、ご了承ください。 diff --git a/README.md b/README.md index 17b57bc4..aa4cdd6e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre - [Usage](#usage) - [Command Line Options](#command-line-options) - [Usage Examples](#usage-examples) + - [Pivot Keyword Generator](#pivot-keyword-generator) - [Testing Hayabusa on Sample Evtx Files](#testing-hayabusa-on-sample-evtx-files) - [Hayabusa Output](#hayabusa-output) - [Progress Bar](#progress-bar) @@ -61,6 +62,8 @@ Hayabusa is a **Windows event log fast forensics timeline generator** and **thre - [Detection Rule Tuning](#detection-rule-tuning) - [Event ID Filtering](#event-id-filtering) - [Other Windows Event Log Analyzers and Related Projects](#other-windows-event-log-analyzers-and-related-projects) +- [Windows Logging Recommendations](#windows-logging-recommendations) +- [Sysmon Related Projects](#sysmon-related-projects) - [Comparison To Other Similar Tools](#comparison-to-other-similar-tools) - [Community Documentation](#community-documentation) - [English](#english) @@ -125,6 +128,7 @@ You can learn how to analyze CSV timelines in Excel and Timeline Explorer [here] * Event log statistics. (Useful for getting a picture of what types of events there are and for tuning your log settings.) * Rule tuning configuration by excluding unneeded or noisy rules. * MITRE ATT&CK mapping of tactics (only in saved CSV files). +* Create a list of unique pivot keywords to quickly identify abnormal users, hostnames, processes, etc... as well as correlate events. # Planned Features @@ -305,6 +309,7 @@ USAGE: -s --statistics 'Prints statistics of event IDs.' -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.' ``` @@ -370,7 +375,13 @@ hayabusa.exe -d .\hayabusa-sample-evtx -r .\rules\hayabusa\default\events\Securi hayabusa.exe -l -m low ``` -* Get event ID statistics: +* 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.exe -l -m critical -p -o keywords +``` + +* Print Event ID statistics: ```bash hayabusa.exe -f Security.evtx -s @@ -401,6 +412,24 @@ Checking target evtx FilePath: "./hayabusa-sample-evtx/YamatoSecurity/T1218.004_ By default, hayabusa will save error messages to error log files. If you do not want to save error messages, please add `-Q`. +## Pivot Keyword Generator + +You can use the `-p` or `--pivot-keywords-list` option to create a list of unique pivot keywords to quickly identify abnormal users, hostnames, processes, etc... as well as correlate events. You can customize what keywords you want to search for by editing `config/pivot_keywords.txt`. +This is the default setting: + +``` +Users.SubjectUserName +Users.TargetUserName +Users.User +Logon IDs.SubjectLogonId +Logon IDs.TargetLogonId +Workstation Names.WorkstationName +Ip Addresses.IpAddress +Processes.Image +``` + +The format is `KeywordName.FieldName`. For example, when creating the list of `Users`, hayabusa will list up all the values in the `SubjectUserName`, `TargetUserName` and `User` fields. By default, hayabusa will return results from all events (informational and higher) so we highly recommend combining the `--pivot-keyword-list` option with the `-m` or `--min-level` option. For example, start off with only creating keywords from `critical` alerts with `-m critical` and then continue with `-m high`, `-m medium`, etc... There will most likely be common keywords in your results that will match on many normal events, so after manually checking the results and creating a list of unique keywords in a single file, you can then create a narrowed down timeline of suspicious activity with a command like `grep -f keywords.txt timeline.csv`. + # Testing Hayabusa on Sample Evtx Files We have provided some sample evtx files for you to test hayabusa and/or create new rules at [https://github.com/Yamato-Security/hayabusa-sample-evtx](https://github.com/Yamato-Security/hayabusa-sample-evtx) @@ -524,6 +553,19 @@ There is no "one tool to rule them all" and we have found that each has its own * [WELA (Windows Event Log Analyzer)](https://github.com/Yamato-Security/WELA) - The swiff-army knife for Windows event logs by [Yamato Security](https://github.com/Yamato-Security/) * [Zircolite](https://github.com/wagga40/Zircolite) - Sigma-based attack detection tool written in Python. +# 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: +* [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) + +# Sysmon Related Projects + +To create the most forensic evidence and detect with the highest accuracy, you need to install sysmon. We recommend the following sites: +* [Sysmon Modular](https://github.com/olafhartong/sysmon-modular) +* [TrustedSec Sysmon Community Guide](https://github.com/trustedsec/SysmonCommunityGuide) + ## Comparison To Other Similar Tools Please understand that it is not possible to do a perfect comparison as results will differ based on the target sample data, command-line options, rule tuning, etc... diff --git a/config/pivot_keywords.txt b/config/pivot_keywords.txt new file mode 100644 index 00000000..7e39ecd4 --- /dev/null +++ b/config/pivot_keywords.txt @@ -0,0 +1,8 @@ +Users.SubjectUserName +Users.TargetUserName +Users.User +Logon IDs.SubjectLogonId +Logon IDs.TargetLogonId +Workstation Names.WorkstationName +Ip Addresses.IpAddress +Processes.Image \ No newline at end of file diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 9ad2185a..5bca7bb9 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -1,3 +1,5 @@ +use crate::detections::pivot::PivotKeyword; +use crate::detections::pivot::PIVOT_KEYWORD; use crate::detections::print::AlertMessage; use crate::detections::utils; use chrono::{DateTime, Utc}; @@ -67,7 +69,7 @@ fn build_app<'a>() -> ArgMatches<'a> { let usages = "-d --directory=[DIRECTORY] 'Directory of multiple .evtx files.' -f --filepath=[FILEPATH] 'File path to one .evtx file.' - -r --rules=[RULEFILE/RULEDIRECTORY] 'Rule file or directory. (Default: ./rules)' + -r --rules=[RULEDIRECTORY/RULEFILE] 'Rule file or directory (default: ./rules)' -c --color 'Output with color. (Terminal needs to support True Color.)' -C --config=[RULECONFIGDIRECTORY] 'Rule config folder. (Default: ./rules/config)' -o --output=[CSV_TIMELINE] 'Save the timeline in CSV format. (Example: results.csv)' @@ -86,6 +88,7 @@ fn build_app<'a>() -> ArgMatches<'a> { -s --statistics 'Prints statistics of event IDs.' -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!") @@ -268,6 +271,7 @@ impl Default for EventKeyAliasConfig { fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig { let mut config = EventKeyAliasConfig::new(); + // eventkey_aliasが読み込めなかったらエラーで終了とする。 let read_result = utils::read_csv(path); if read_result.is_err() { AlertMessage::alert( @@ -277,7 +281,7 @@ fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig { .ok(); return config; } - // eventkey_aliasが読み込めなかったらエラーで終了とする。 + read_result.unwrap().into_iter().for_each(|line| { if line.len() != 2 { return; @@ -302,6 +306,40 @@ fn load_eventkey_alias(path: &str) -> EventKeyAliasConfig { config } +///設定ファイルを読み込み、keyとfieldsのマップをPIVOT_KEYWORD大域変数にロードする。 +pub fn load_pivot_keywords(path: &str) { + let read_result = utils::read_txt(path); + if read_result.is_err() { + AlertMessage::alert( + &mut BufWriter::new(std::io::stderr().lock()), + read_result.as_ref().unwrap_err(), + ) + .ok(); + } + + read_result.unwrap().into_iter().for_each(|line| { + let map: Vec<&str> = line.split('.').collect(); + if map.len() != 2 { + return; + } + + //存在しなければ、keyを作成 + PIVOT_KEYWORD + .write() + .unwrap() + .entry(map[0].to_string()) + .or_insert(PivotKeyword::new()); + + PIVOT_KEYWORD + .write() + .unwrap() + .get_mut(&map[0].to_string()) + .unwrap() + .fields + .insert(map[1].to_string()); + }); +} + #[derive(Debug, Clone)] pub struct EventInfo { pub evttitle: String, diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 8ab1e406..4e92cdc4 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1,10 +1,12 @@ extern crate csv; use crate::detections::configs; +use crate::detections::pivot::insert_pivot_keyword; 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::rule; @@ -177,6 +179,12 @@ impl Detection { if !result { continue; } + + if *PIVOT_KEYWORD_LIST_FLAG { + insert_pivot_keyword(&record_info.record); + continue; + } + // aggregation conditionが存在しない場合はそのまま出力対応を行う if !agg_condition { Detection::insert_message(&rule, record_info); diff --git a/src/detections/mod.rs b/src/detections/mod.rs index e4ee98be..5a081dff 100644 --- a/src/detections/mod.rs +++ b/src/detections/mod.rs @@ -1,5 +1,6 @@ pub mod configs; pub mod detection; +pub mod pivot; pub mod print; pub mod rule; pub mod utils; diff --git a/src/detections/pivot.rs b/src/detections/pivot.rs new file mode 100644 index 00000000..f8be1801 --- /dev/null +++ b/src/detections/pivot.rs @@ -0,0 +1,270 @@ +use hashbrown::HashMap; +use hashbrown::HashSet; +use lazy_static::lazy_static; +use serde_json::Value; +use std::sync::RwLock; + +use crate::detections::configs; +use crate::detections::utils::get_serde_number_to_string; + +#[derive(Debug)] +pub struct PivotKeyword { + pub keywords: HashSet, + pub fields: HashSet, +} + +lazy_static! { + pub static ref PIVOT_KEYWORD: RwLock> = + RwLock::new(HashMap::new()); +} + +impl Default for PivotKeyword { + fn default() -> Self { + Self::new() + } +} + +impl PivotKeyword { + pub fn new() -> PivotKeyword { + PivotKeyword { + keywords: HashSet::new(), + fields: HashSet::new(), + } + } +} + +///levelがlowより大きいレコードの場合、keywordがrecord内にみつかれば、 +///それをPIVOT_KEYWORD.keywordsに入れる。 +pub fn insert_pivot_keyword(event_record: &Value) { + //levelがlow異常なら続ける + let mut is_exist_event_key = false; + let mut tmp_event_record: &Value = event_record; + for s in ["Event", "System", "Level"] { + if let Some(record) = tmp_event_record.get(s) { + is_exist_event_key = true; + tmp_event_record = record; + } + } + if is_exist_event_key { + let hash_value = get_serde_number_to_string(tmp_event_record); + + if hash_value.is_some() && hash_value.as_ref().unwrap() == "infomational" + || hash_value.as_ref().unwrap() == "undefined" + || hash_value.as_ref().unwrap() == "-" + { + return; + } + } else { + return; + } + + for (_, pivot) in PIVOT_KEYWORD.write().unwrap().iter_mut() { + for field in &pivot.fields { + if let Some(array_str) = configs::EVENTKEY_ALIAS.get_event_key(&String::from(field)) { + let split: Vec<&str> = array_str.split('.').collect(); + let mut is_exist_event_key = false; + let mut tmp_event_record: &Value = event_record; + for s in split { + if let Some(record) = tmp_event_record.get(s) { + is_exist_event_key = true; + tmp_event_record = record; + } + } + if is_exist_event_key { + let hash_value = get_serde_number_to_string(tmp_event_record); + + if let Some(value) = hash_value { + if value == "-" || value == "127.0.0.1" || value == "::1" { + continue; + } + pivot.keywords.insert(value); + }; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::detections::configs::load_pivot_keywords; + use crate::detections::pivot::insert_pivot_keyword; + use crate::detections::pivot::PIVOT_KEYWORD; + use serde_json; + + //PIVOT_KEYWORDはグローバルなので、他の関数の影響も考慮する必要がある。 + #[test] + fn insert_pivot_keyword_local_ip4() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "high" + }, + "EventData": { + "IpAddress": "127.0.0.1" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(!PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("127.0.0.1")); + } + + #[test] + fn insert_pivot_keyword_ip4() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "high" + }, + "EventData": { + "IpAddress": "10.0.0.1" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("10.0.0.1")); + } + + #[test] + fn insert_pivot_keyword_ip_empty() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "high" + }, + "EventData": { + "IpAddress": "-" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(!PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("-")); + } + + #[test] + fn insert_pivot_keyword_local_ip6() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "high" + }, + "EventData": { + "IpAddress": "::1" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(!PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("::1")); + } + + #[test] + fn insert_pivot_keyword_level_infomational() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "infomational" + }, + "EventData": { + "IpAddress": "10.0.0.2" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(!PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("10.0.0.2")); + } + + #[test] + fn insert_pivot_keyword_level_low() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "low" + }, + "EventData": { + "IpAddress": "10.0.0.1" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("10.0.0.1")); + } + + #[test] + fn insert_pivot_keyword_level_none() { + load_pivot_keywords("test_files/config/pivot_keywords.txt"); + let record_json_str = r#" + { + "Event": { + "System": { + "Level": "-" + }, + "EventData": { + "IpAddress": "10.0.0.3" + } + } + }"#; + insert_pivot_keyword(&serde_json::from_str(record_json_str).unwrap()); + + assert!(!PIVOT_KEYWORD + .write() + .unwrap() + .get_mut("Ip Addresses") + .unwrap() + .keywords + .contains("10.0.0.3")); + } +} diff --git a/src/detections/print.rs b/src/detections/print.rs index dff39245..a530c9d9 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -53,6 +53,11 @@ lazy_static! { .unwrap() .args .is_present("statistics"); + pub static ref PIVOT_KEYWORD_LIST_FLAG: bool = configs::CONFIG + .read() + .unwrap() + .args + .is_present("pivot-keywords-list"); } impl Default for Message { diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 8dc39e5c..b8e60a13 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -87,7 +87,7 @@ pub fn read_csv(filename: &str) -> Result>, String> { return Result::Err(e.to_string()); } - let mut rdr = csv::Reader::from_reader(contents.as_bytes()); + let mut rdr = csv::ReaderBuilder::new().from_reader(contents.as_bytes()); rdr.records().for_each(|r| { if r.is_err() { return; diff --git a/src/filter.rs b/src/filter.rs index 92293a62..d65f6296 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -65,7 +65,7 @@ impl RuleExclude { ERROR_LOG_STACK .lock() .unwrap() - .push(format!("[WARN] {} does not exist", filename)); + .push(format!("{} does not exist", filename)); } return; } diff --git a/src/main.rs b/src/main.rs index 3461b1d9..d462f1f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,12 @@ use chrono::{DateTime, Datelike, Local, TimeZone}; use evtx::{EvtxParser, ParserSettings}; use git2::Repository; use hashbrown::{HashMap, HashSet}; +use hayabusa::detections::configs::load_pivot_keywords; use hayabusa::detections::detection::{self, EvtxRecordInfo}; +use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::print::{ - AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, QUIET_ERRORS_FLAG, STATISTICS_FLAG, + AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, + STATISTICS_FLAG, }; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::filter; @@ -26,7 +29,7 @@ use std::cmp::Ordering; use std::ffi::OsStr; use std::fmt::Display; use std::fs::create_dir; -use std::io::BufWriter; +use std::io::{BufWriter, Write}; use std::path::Path; use std::sync::Arc; use std::time::SystemTime; @@ -71,6 +74,10 @@ impl App { } fn exec(&mut self) { + if *PIVOT_KEYWORD_LIST_FLAG { + load_pivot_keywords("config/pivot_keywords.txt"); + } + let analysis_start_time: DateTime = Local::now(); if !configs::CONFIG.read().unwrap().args.is_present("quiet") { self.output_logo(); @@ -118,6 +125,20 @@ impl App { return; } if let Some(csv_path) = configs::CONFIG.read().unwrap().args.value_of("output") { + for (key, _) in PIVOT_KEYWORD.read().unwrap().iter() { + let keywords_file_name = csv_path.to_owned() + "-" + key + ".txt"; + if Path::new(&keywords_file_name).exists() { + AlertMessage::alert( + &mut BufWriter::new(std::io::stderr().lock()), + &format!( + " The file {} already exists. Please specify a different filename.", + &keywords_file_name + ), + ) + .ok(); + return; + } + } if Path::new(csv_path).exists() { AlertMessage::alert( &mut BufWriter::new(std::io::stderr().lock()), @@ -130,6 +151,7 @@ impl App { return; } } + if *STATISTICS_FLAG { println!("Generating Event ID Statistics"); println!(); @@ -193,6 +215,60 @@ impl App { if ERROR_LOG_STACK.lock().unwrap().len() > 0 { AlertMessage::create_error_log(ERROR_LOG_PATH.to_string()); } + + if *PIVOT_KEYWORD_LIST_FLAG { + //ファイル出力の場合 + if let Some(pivot_file) = configs::CONFIG.read().unwrap().args.value_of("output") { + for (key, pivot_keyword) in PIVOT_KEYWORD.read().unwrap().iter() { + let mut f = BufWriter::new( + fs::File::create(pivot_file.to_owned() + "-" + key + ".txt").unwrap(), + ); + let mut output = "".to_string(); + output += &format!("{}: ", key).to_string(); + + output += "( "; + for i in pivot_keyword.fields.iter() { + output += &format!("%{}% ", i).to_string(); + } + output += "):"; + output += "\n"; + + for i in pivot_keyword.keywords.iter() { + output += &format!("{}\n", i).to_string(); + } + + f.write_all(output.as_bytes()).unwrap(); + } + + //output to stdout + let mut output = + "Pivot keyword results saved to the following files:\n".to_string(); + for (key, _) in PIVOT_KEYWORD.read().unwrap().iter() { + output += &(pivot_file.to_owned() + "-" + key + ".txt" + "\n"); + } + println!("{}", output); + } else { + //標準出力の場合 + let mut output = "The following pivot keywords were found:\n".to_string(); + for (key, pivot_keyword) in PIVOT_KEYWORD.read().unwrap().iter() { + output += &format!("{}: ", key).to_string(); + + output += "( "; + for i in pivot_keyword.fields.iter() { + output += &format!("%{}% ", i).to_string(); + } + output += "):"; + output += "\n"; + + for i in pivot_keyword.keywords.iter() { + output += &format!("{}\n", i).to_string(); + } + + output += "\n"; + } + print!("{}", output); + } + } } #[cfg(not(target_os = "windows"))] @@ -327,7 +403,7 @@ impl App { pb.inc(); } detection.add_aggcondition_msges(&self.rt); - if !*STATISTICS_FLAG { + if !*STATISTICS_FLAG && !*PIVOT_KEYWORD_LIST_FLAG { after_fact(); } } diff --git a/test_files/config/pivot_keywords.txt b/test_files/config/pivot_keywords.txt new file mode 100644 index 00000000..7e39ecd4 --- /dev/null +++ b/test_files/config/pivot_keywords.txt @@ -0,0 +1,8 @@ +Users.SubjectUserName +Users.TargetUserName +Users.User +Logon IDs.SubjectLogonId +Logon IDs.TargetLogonId +Workstation Names.WorkstationName +Ip Addresses.IpAddress +Processes.Image \ No newline at end of file