From 5c0c851a375ae60d50d6eeda635b3237d19a6f54 Mon Sep 17 00:00:00 2001 From: fukusuket <41001169+fukusuket@users.noreply.github.com> Date: Fri, 14 Mar 2025 17:49:38 +0900 Subject: [PATCH] update --- WELA.ps1 | 23 ----------- wela-extractor/src/main.rs | 85 +++++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 56 deletions(-) diff --git a/WELA.ps1 b/WELA.ps1 index dee68684..6bde6912 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -60,13 +60,6 @@ $usableCounts = $usableRules | Group-Object -Property level | ForEach-Object { } } -$unusableCounts = $unusableRules | Group-Object -Property level | ForEach-Object { - [PSCustomObject]@{ - Level = $_.Name - Count = $_.Count - } -} - # Step 5: Calculate the percentages $usablePercentages = $usableCounts | ForEach-Object { $total = ($totalCounts | Where-Object Level -match $PSItem.Level | Select-Object -ExpandProperty Count)[0] @@ -78,16 +71,6 @@ $usablePercentages = $usableCounts | ForEach-Object { } } -$unusablePercentages = $unusableCounts | ForEach-Object { - $total = ($totalCounts | Where-Object Level -match $PSItem.Level | Select-Object -ExpandProperty Count)[0] - [PSCustomObject]@{ - Level = $PSItem.Level - UnusableCount = $PSItem.Count - TotalCount = $total - Percentage = "{0:N2}" -f ($PSItem.Count / $total * 100) - } -} - # Step 6: Generate the required outputtotal $customOrder = @("critical", "high", "medium", "low", "informational") Write-Output "Detection rules that can be used on this system versus total possible rules:" @@ -96,12 +79,6 @@ $usablePercentages | ForEach-Object { Write-Output "$($_.Level) rules: $($_.UsableCount) / $($_.TotalCount) ($($_.Percentage)%)" } Write-Output "" -Write-Output "Detection rules that cannot be used on this system:" -$unusablePercentages = $unusablePercentages | Sort-Object { $customOrder.IndexOf($_.Level) } -$unusablePercentages | ForEach-Object { - Write-Output "$($_.Level) rules: $($_.UnusableCount) / $($_.TotalCount) ($($_.Percentage)%)" -} -Write-Output "" Write-Output "Usable detection rules list saved to: UsableRules.csv" Write-Output "Unusable detection rules list saved to: UnusableRules.csv" Write-Output "" diff --git a/wela-extractor/src/main.rs b/wela-extractor/src/main.rs index d12d569c..a6267314 100644 --- a/wela-extractor/src/main.rs +++ b/wela-extractor/src/main.rs @@ -2,11 +2,26 @@ use csv::ReaderBuilder; use serde_json::{Value, json}; use std::collections::HashSet; use std::error::Error; +use std::fmt::{Display, Formatter}; use std::fs::write; use std::{env, fs}; use walkdir::WalkDir; use yaml_rust2::{Yaml, YamlLoader}; +enum Channel { + Security, + PowerShell, +} + +impl Display for Channel { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Channel::Security => write!(f, "sec"), + Channel::PowerShell => write!(f, "pwsh"), + } + } +} + fn list_yml_files(dir: &str) -> Vec { let mut yml_files = Vec::new(); for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) { @@ -58,58 +73,62 @@ fn extract_event_ids(yaml: &Yaml, event_ids: &mut HashSet) { } } -fn contains_channel_security(yaml: &Yaml) -> bool { +fn contains_channel_security(yaml: &Yaml) -> Option { match yaml { Yaml::Hash(hash) => { for (key, value) in hash { - if key.as_str() == Some("Channel") && value.as_str() == Some("Security") { - return true; - } - if contains_channel_security(value) { - return true; + if key.as_str() == Some("Channel") { + match value.as_str() { + Some("Security") => return Some(Channel::Security), + Some("Microsoft-Windows-PowerShell/Operational") + | Some("PowerShellCore/Operational") + | Some("Windows PowerShell") => return Some(Channel::PowerShell), + _ => None::, + }; + } else if let Some(channel) = contains_channel_security(value) { + return Some(channel); } } } Yaml::Array(array) => { for item in array { - if contains_channel_security(item) { - return true; + if let Some(channel) = contains_channel_security(item) { + return Some(channel); } } } _ => {} } - false + None } fn parse_yaml(doc: Yaml, eid_subcategory_pair: &Vec<(String, String)>) -> Option { - if !contains_channel_security(&doc["detection"]) { - return None; - } - let uuid = doc["id"].as_str().unwrap_or(""); - let title = doc["title"].as_str().unwrap_or(""); - let desc = doc["description"].as_str().unwrap_or(""); - let level = doc["level"].as_str().unwrap_or(""); - let mut event_ids = HashSet::new(); - let mut subcategories = HashSet::new(); - extract_event_ids(&doc, &mut event_ids); - for event_id in &event_ids { - for (eid, subcategory) in eid_subcategory_pair { - if eid == event_id { - subcategories.insert(subcategory.clone()); + if let Some(ch) = contains_channel_security(&doc["detection"]) { + let uuid = doc["id"].as_str().unwrap_or(""); + let title = doc["title"].as_str().unwrap_or(""); + let level = doc["level"].as_str().unwrap_or(""); + let mut event_ids = HashSet::new(); + let mut subcategories = HashSet::new(); + extract_event_ids(&doc, &mut event_ids); + for event_id in &event_ids { + for (eid, subcategory) in eid_subcategory_pair { + if eid == event_id { + subcategories.insert(subcategory.clone()); + } } } + let event_ids: Vec = event_ids.into_iter().collect(); + let subcategories: Vec = subcategories.into_iter().collect(); + return Some(json!({ + "id": uuid, + "title": title, + "channel": ch.to_string(), + "level": level, + "event_ids": event_ids, + "subcategory_guids": subcategories + })); } - let event_ids: Vec = event_ids.into_iter().collect(); - let subcategories: Vec = subcategories.into_iter().collect(); - Some(json!({ - "id": uuid, - "title": title, - "description": desc, - "level": level, - "event_ids": event_ids, - "subcategory_guids": subcategories - })) + None } fn load_event_id_guid_pairs(file_path: &str) -> Result, Box> {