diff --git a/README.md b/README.md index 43532d177..63fb0b7e9 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,27 @@ sigma2misp @misp.conf --same-event --info "Test Event" -r sigma_rules/ [Evt2Sigma](https://github.com/Neo23x0/evt2sigma) helps you with the rule creation. It generates a Sigma rule from a log entry. +## Sigma2attack + +Generates a [MITRE ATT&CK Navigator](https://github.com/mitre/attack-navigator/) heatmap from a directory containing sigma rules. + +Requirements: +- Sigma rules tagged with a `attack.tXXXX` tag (e.g.: `attack.t1086`) + +Usage samples: + +``` +# Use the default "rules" folder +./tools/sigma2attack + +# ... or specify your own +./tools/sigma2attack --rules-directory ~/hunting/rules +``` + +Result once imported in the MITRE ATT&CK Navigator ([online version](https://mitre-attack.github.io/attack-navigator/enterprise/)): + +![Sigma2attack result](./images/sigma2attack.png) + ## Contributed Scripts The directory `contrib` contains scripts that were contributed by the community: diff --git a/images/sigma2attack.png b/images/sigma2attack.png new file mode 100644 index 000000000..c8f6a3f5b Binary files /dev/null and b/images/sigma2attack.png differ diff --git a/tools/sigma2attack b/tools/sigma2attack new file mode 100755 index 000000000..5a5ea20ad --- /dev/null +++ b/tools/sigma2attack @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import argparse +import glob +import json +import os +import sys + +import yaml + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument("--rules-directory", "-d", dest="rules_dir", default="rules", help="Directory to read rules from") +parser.add_argument("--out-file", "-o", dest="out_file", default="heatmap.json", help="File to write the JSON layer to") +parser.add_argument("--no-comment", dest="no_comment", action="store_true", help="Don't store rule names in comments") +args = parser.parse_args() + +rule_files = glob.glob(os.path.join(args.rules_dir, "**/*.yml"), recursive=True) +techniques_to_rules = {} +curr_max_technique_count = 0 +num_rules_used = 0 +for rule_file in rule_files: + try: + rule = yaml.safe_load(open(rule_file).read()) + except yaml.YAMLError: + sys.stderr.write("Ignoring rule " + rule_file + " (parsing failed)\n") + continue + if "tags" not in rule: + sys.stderr.write("Ignoring rule " + rule_file + " (no tags)\n") + continue + tags = rule["tags"] + for tag in tags: + if tag.lower().startswith("attack.t"): + technique_id = tag[len("attack."):].upper() + num_rules_used += 1 + if technique_id not in techniques_to_rules: + techniques_to_rules[technique_id] = [] + techniques_to_rules[technique_id].append(os.path.basename(rule_file)) + curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id])) + + +scores = [] +for technique in techniques_to_rules: + entry = { + "techniqueID": technique, + "score": len(techniques_to_rules[technique]), + } + if not args.no_comment: + entry["comment"] = "\n".join(techniques_to_rules[technique]) + + scores.append(entry) + +output = { + "domain": "mitre-enterprise", + "name": "Sigma rules heatmap", + "gradient": { + "colors": [ + "#ffffff", + "#ff6666" + ], + "maxValue": curr_max_technique_count, + "minValue": 0 + }, + "version": "2.2", + "techniques": scores, +} + +with open(args.out_file, "w") as f: + f.write(json.dumps(output)) + print("[*] Layer file written in " + args.out_file + " (" + str(num_rules_used) + " rules)") \ No newline at end of file