Outils¶
Outils de dev dans le crate tools, chacun avec sa propre fonction. Ils restent hors du
binaire principal sigmacatch pour garder son arbre de dépendances léger.
check_evtx¶
Fichier : tools/src/check_evtx.rs
Usage : cargo run --release --bin check_evtx [--json]
Fonction : Batch validation du moteur de détection Sigma contre les données de régression SigmaHQ.
Pipeline¶
- Charge toutes les règles Sigma depuis
./sigma, filtre sur Windows - Construit le
DetectionEngineune seule fois - Charge les entrées de régression depuis
./sigma/regression_data - Pour chaque entrée
info.yml: charge le.evtxbrut, le parse → events - Évalue les events contre la règle
- Valide : la règle DOIT matcher (test de détection positive)
- Vérification de conformité JSON : quand un
<rule_id>.jsoncommitté existe, vérifie queparse_winevt_xml_rawle reproduit à l'identique (compatibilité du format SigmaHQ) — un écart est rapporté séparément et ne fait pas échouer le test de détection - Rapport pass/fail par règle + résumé (exit 1 en cas d'échec de détection)
Sortie¶
Found 3777 total rules
→ 2872 windows rules after filtering
Found 202 regression entry(ies)
Engine ready — 2872 rule(s) loaded.
Running validation...
[ 1/202 ] win_security_explicit_credential_local_logon ... [JSON OK] parse_winevt_xml_raw reproduces committed JSON
[PASS] 1 alert(s), rule matched
[ 2/202 ] win_security_susp_scheduled_task_delete_or_disable ... [JSON MISMATCH] no EVTX record reproduces committed JSON first diff: Event.EventData.TaskContent ...
[PASS] 1 alert(s), rule matched
...
[ 165/202 ] registry_event_add_local_hidden_user ... [JSON OK] parse_winevt_xml_raw reproduces committed JSON
[FAIL] RULE NOT MATCHED — expected '460479f3-80b7-42da-9c43-2cc1d54dbccd' (0 alert(s), matched: )
--- explain_rule trace ---
...
[ 201/202 ] win_defender_exploit_redsun_tiering_engine_detected_as_eicar ... [JSON MISMATCH] ...
[PASS] 1 alert(s), rule matched
[ 202/202 ] image_load_win_werfaultsecure_dbgcore_dbghelp_load ... [JSON OK] parse_winevt_xml_raw reproduces committed JSON
[PASS] 1 alert(s), rule matched
============================================================
VALIDATION SUMMARY
============================================================
Total entries: 202
Passed: 201
Skipped: 0
Failed: 1
Pass rate: 99.5%
JSON FORMAT CHECKS (parse_winevt_xml_raw vs committed JSON):
Checked: 189
Matched: 186
Mismatch: 3
============================================================
Failed rules:
FAIL registry_event_add_local_hidden_user — RULE NOT MATCHED — expected '460479f3-...'
JSON format mismatches:
MISMATCH win_security_susp_scheduled_task_delete_or_disable — no EVTX record reproduces committed JSON first diff: Event.EventData.TaskContent ... (CRLF vs LF dans le XML embarqué)
MISMATCH proc_creation_win_susp_right_to_left_override — no EVTX record reproduces committed JSON first diff: Event.System.TimeCreated.#attributes.SystemTime ... (précision des fractions de seconde)
MISMATCH win_defender_exploit_redsun_tiering_engine_detected_as_eicar — no EVTX record reproduces committed JSON first diff: Event.EventData.Threat ID (nombre vs string)
Le run check_evtx ci-dessus correspond à l'état actuel de sigma/regression_data
(202 entrées, 201 PASS / 1 FAIL — l'échec registry_event_add_local_hidden_user est le problème
registry connu en attente d'une maj rsigma ; 3 écarts de format JSON cosmétiques restent).
Exemple¶
check_filter¶
Fichier : tools/src/check_filter.rs
Usage : cargo run --release --bin check_filter [--json]
Fonction : Valide SigmaFilterConfig (product / status / level / author) contre le vrai jeu
de règles Sigma. Pas d'args CLI — exécute toutes les combinaisons de filtres automatiquement.
Pipeline¶
- Charge toutes les règles depuis
./sigmaune seule fois (SigmahqRules::new()) - Pour chaque combinaison de filtres : applique le filtre et lit
LoadStats - Recalcule indépendamment les comptages ground-truth par dimension (
count_ground_truth) - Compare chaque bucket :
loaded,product,status,level,author,total - Rapport pass/fail par test + résumé (exit 1 si écart)
Ce n'est pas circulaire : les stats viennent de filter(), le ground truth est compté
directement depuis les règles brutes — donc un stats() auto-cohérent mais faux échouerait quand même.
Sortie¶
Loaded 3777 total rules from ./sigma
============================================================
TEST: empty filter (no filtering)
============================================================
product=windows status=None level=None author=None → 2872 loaded / 3777 total
GT: loaded=2872 prod=905 stat=0 lvl=0 auth=0 total=3777 sum=3777
filter: loaded=2872 prod=905 stat=0 lvl=0 auth=0 total=3777 sum=3777
✅ all dimensions match ground truth
✅ PASS
============================================================
TEST: product filter
============================================================
product=linux status=None level=None author=None → 248 loaded / 3777 total
GT: loaded=248 prod=3529 stat=0 lvl=0 auth=0 total=3777 sum=3777
filter: loaded=248 prod=3529 stat=0 lvl=0 auth=0 total=3777 sum=3777
✅ all dimensions match ground truth
product=macos status=None level=None author=None → 75 loaded / 3777 total
GT: loaded=75 prod=3702 stat=0 lvl=0 auth=0 total=3777 sum=3777
filter: loaded=75 prod=3702 stat=0 lvl=0 auth=0 total=3777 sum=3777
✅ all dimensions match ground truth
✅ PASS
============================================================
TEST: author filter
============================================================
product=windows status=None level=None author=Some("FRACK113") → 461 loaded / 3777 total
GT: loaded=461 prod=905 stat=0 lvl=0 auth=2411 total=3777 sum=3777
filter: loaded=461 prod=905 stat=0 lvl=0 auth=2411 total=3777 sum=3777
✅ all dimensions match ground truth
✅ PASS
============================================================
TEST: combined: with author
============================================================
product=windows status=None level=None author=Some("Elastic") → 5 loaded / 3777 total
GT: loaded=5 prod=905 stat=0 lvl=0 auth=2867 total=3777 sum=3777
filter: loaded=5 prod=905 stat=0 lvl=0 auth=2867 total=3777 sum=3777
✅ all dimensions match ground truth
✅ PASS
============================================================
SUMMARY
============================================================
Passed: 7
Failed: 0
============================================================
(7 tests : empty filter, product, status, level, author, combined product+status+level, combined avec author — tous passent au moment de la rédaction, 3777 règles totales.)
Exemple¶
check_dry_run¶
Fichier : tools/src/check_dry_run.rs
Usage : cargo run --release --bin check_dry_run [--json]
Fonction : diagnostics git de l'ancien flag --dry-run de sigmacatch (déplacé ici pour
simplifier le binaire principal). Réutilise Config::load_with_cli + dry_run_git de
sigmacatch-config. Accepte les mêmes flags que le binaire principal (--author, -o/--offline,
-c/--contrib, --help).
Pipeline¶
parse_args()+Config::load_with_cli("config.yaml", cli)dry_run_git(&config)→ résolution du token (config + env), détection du fork (HTTP HEAD), vérification API/user, endpoint git smart HTTP info/refs, état du repo localsigma/- Rapport détaillé de chaque étape → identifier le point de défaillance
check_channels¶
Fichier : tools/src/check_channels.rs
Usage : cargo run --release --bin check_channels [--json]
Fonction : résout et liste les channels Windows que le moteur collecterait (ancien
--channels-only de sigmacatch, déplacé ici).
Pipeline¶
Config::load("config.yaml")(section filter)- Charge les règles Sigma depuis
./sigma+ filtre config DetectionEngine::new(&rules)→resolve_channels(&custom_map)(incl. custom_channels.yaml)- Affiche la liste des channels (exit 1 si aucun)
list_rules¶
Fichier : tools/src/list_rules.rs
Usage : cargo run --release --bin list_rules [--json]
Fonction : liste les règles chargées avec leur chemin (ancien --list-rules de sigmacatch,
déplacé ici).
Pipeline¶
Config::load("config.yaml")(section filter)- Charge les règles Sigma depuis
./sigma+ filtre config - Pour chaque règle : id, titre, status, niveau, techniques (tags
attack.*), chemin, lien ART (première sous-technique)
get_atomic¶
Fichier : tools/src/get_atomic.rs
Usage : cargo run --release --bin get_atomic [--output run_atomic.ps] [--getprereqs] [--json]
Fonction : génère un script run_atomic.ps1 qui chaîne les commandes
Invoke-AtomicTest T1xxx.xxx pour les techniques ATT&CK des règles sans
regression data selon le filtre config. Le script est copié sur la VM Windows
et exécuté manuellement ; sigmacatch (boucle continue) capte les events
générés et produit la regression data.
Pipeline¶
Config::load("config.yaml")(section filter +git.sigma_repo_path)- Charge les règles Sigma depuis
./sigma+ filtre config - Skip set = règles avec regression data déjà valide (local
regression_data/) ∪ ids des branches remotesigmacatch/*en attente de merge - Pour chaque règle restante :
rule.attack_techniques()(extension traitSigmaRuleExtdesigmacatch-rule) - Dédupe + tri des techniques (BTreeSet) — une
Invoke-AtomicTestpar technique - Écrit
run_atomic.ps1(ou--output <path>) + rapport
Script généré¶
$ErrorActionPreference = "Continue"
Import-Module Invoke-AtomicRedTeam
# 12 rule(s) without regression data — 7 technique(s)
Start-Sleep -Seconds 5
Invoke-AtomicTest T1055.001 -TimeoutSeconds 120
Start-Sleep -Seconds 30
Invoke-AtomicTest T1547.001 -TimeoutSeconds 120
...
Start-Sleep 30entre les tests → laisse sigmacatch collecter les events-TimeoutSeconds 120→ évite qu'un test bloquant fige la chaîne- Les règles sans tag
attack.*sont comptées et listées dans le rapport (pas deInvoke-AtomicTestgénéré pour elles)
Limitations¶
Pas de garantie de couverture : une règle avec une condition spécifique peut ne pas matcher l'event produit par le test ART. Les règles restées sans data sont re-listées au prochain run (le skip set n'exclut que ce qui est déjà généré).
coverage¶
Fichier : tools/src/coverage.rs
Usage : cargo run --release --bin coverage [--json]
Fonction : statistiques de couverture globale pour la config filtre actuelle. Sortie JSON : règles totales, rules avec régression locale, rules en attente sur branches remote, pourcentage de couverture.
Pipeline¶
Config::load("config.yaml")(section filter)- Charge toutes les règles Sigma depuis
./sigma+ filtre config - Scan local
regression_data/→ skip set SigmaRepo::pending_regression_rule_ids()→ skip set branches remote- Calcul % couverture → JSON
Comment ajouter un outil¶
- Créer
tools/src/<name>.rsavec un docstring en tête - Ajouter l'entrée dans
tools/Cargo.toml:
- Ajouter seulement les dépendances nécessaires à l'outil dans
tools/Cargo.toml - Documenter ici avec usage et pipeline