| 1 | from __future__ import annotations |
| 2 | |
| 3 | from cti import mitre |
| 4 | from cti.models import Indicator, Technique |
| 5 | |
| 6 | |
| 7 | def extract_techniques(indicators: list[Indicator]) -> list[Technique]: |
| 8 | table: dict[str, Technique] = {} |
| 9 | for indicator in indicators: |
| 10 | sources = indicator.source.split(",") |
| 11 | for technique_id in indicator.techniques: |
| 12 | technique = table.get(technique_id) |
| 13 | if technique is None: |
| 14 | technique = Technique( |
| 15 | technique_id=technique_id, |
| 16 | name=mitre.name_for(technique_id), |
| 17 | tactic=mitre.tactic_for(technique_id), |
| 18 | ) |
| 19 | table[technique_id] = technique |
| 20 | technique.sources.update(sources) |
| 21 | technique.indicator_count += 1 |
| 22 | return sorted(table.values(), key=lambda t: (-t.indicator_count, t.technique_id)) |
| 23 | |
| 24 | |
| 25 | def coverage_report(techniques: list[Technique]) -> str: |
| 26 | lines = [ |
| 27 | "# TTP coverage", |
| 28 | "", |
| 29 | "| Technique | Tactic | Name | Indicators | Sources |", |
| 30 | "|---|---|---|---|---|", |
| 31 | ] |
| 32 | for technique in techniques: |
| 33 | lines.append( |
| 34 | f"| {technique.technique_id} | {technique.tactic} | {technique.name} " |
| 35 | f"| {technique.indicator_count} | {', '.join(sorted(technique.sources))} |" |
| 36 | ) |
| 37 | return "\n".join(lines) + "\n" |