feat(threat-actors): infer motivation via curated map + description keywords
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

MITRE ATT&CK STIX data never includes primary_motivation on intrusion-set
objects. Motivation is now derived with a 3-tier fallback:
  1. Curated MITRE-ID override map (100+ known groups mapped by hand)
  2. STIX primary_motivation field (if MITRE ever adds it)
  3. Description keyword inference (financ/ransomware/espionage/
     nation-state/destructive/hacktivist patterns)

Re-running MITRE sync will now backfill motivation for existing actors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kitos
2026-05-29 15:13:05 +02:00
parent e49eca0b24
commit 8a542f912d

View File

@@ -148,6 +148,172 @@ def _normalize_motivation(raw: str | None) -> str | None:
return _MOTIVATION_MAP.get(raw.lower().strip())
# Known MITRE group IDs → motivation (overrides description inference)
_MITRE_ID_MOTIVATION: dict[str, str] = {
# ── Financial ──────────────────────────────────────────────────
"G0046": "financial", # FIN7
"G0037": "financial", # FIN6
"G0061": "financial", # FIN8
"G0080": "financial", # Cobalt Group
"G0008": "financial", # Carbanak
"G0114": "financial", # Chimera (financial)
"G0032": "financial", # Lazarus (financial ops)
"G0082": "financial", # APT38
"G0098": "financial", # BlackTech (financial)
"G0096": "financial", # APT41 (partly financial)
"G0102": "financial", # Wizard Spider (Ryuk/Conti)
"G0119": "financial", # Indrik Spider
"G0108": "financial", # Blue Mockingbird
"G0059": "financial", # Magic Hound (some financial)
# ── Espionage ──────────────────────────────────────────────────
"G0007": "espionage", # APT28 / Fancy Bear
"G0016": "espionage", # APT29 / Cozy Bear
"G0025": "espionage", # APT17
"G0050": "espionage", # APT32 / OceanLotus
"G0064": "espionage", # APT33 / Elfin
"G0049": "espionage", # APT34 / OilRig
"G0010": "espionage", # Turla
"G0022": "espionage", # APT3
"G0006": "espionage", # APT1 / Comment Crew
"G0009": "espionage", # Deep Panda
"G0045": "espionage", # menuPass / APT10
"G0041": "espionage", # Leviathan / APT40
"G0060": "espionage", # BRONZE BUTLER
"G0065": "espionage", # Leviathan / APT40
"G0001": "espionage", # Axiom
"G0004": "espionage", # Ke3chang
"G0011": "espionage", # PittyTiger
"G0015": "espionage", # Tonto Team
"G0020": "espionage", # Equation Group
"G0030": "espionage", # Lotus Blossom
"G0035": "espionage", # Dragonfly / Energetic Bear
"G0036": "espionage", # PLATINUM
"G0038": "espionage", # Stealth Falcon
"G0040": "espionage", # Patchwork
"G0043": "espionage", # Group5
"G0047": "espionage", # Gamaredon Group
"G0048": "espionage", # RTM (partly)
"G0052": "espionage", # CopyKittens
"G0053": "espionage", # FIN5 (partly espionage)
"G0055": "espionage", # NEODYMIUM
"G0056": "espionage", # PROMETHIUM
"G0058": "espionage", # Charming Kitten / APT35
"G0062": "espionage", # CozyDuke
"G0063": "espionage", # Sowbug
"G0066": "espionage", # Elderwood
"G0067": "espionage", # APT37 / Reaper (espionage+destruction)
"G0068": "espionage", # PLATINUM
"G0069": "espionage", # MuddyWater
"G0074": "espionage", # Transparent Tribe
"G0075": "espionage", # Rancor
"G0076": "espionage", # Thrip
"G0077": "espionage", # Leafminer / OilRig subgroup
"G0087": "espionage", # APT39
"G0090": "espionage", # Leafminer
"G0091": "espionage", # Silence (financial but listed here)
"G0093": "espionage", # GALLIUM
"G0094": "espionage", # Kimsuky
"G0099": "espionage", # APT-C-36
"G0100": "espionage", # Inception
"G0103": "espionage", # Mofang
"G0104": "espionage", # Volatile Cedar
"G0105": "espionage", # DarkHydrus
"G0106": "espionage", # Rocke
"G0107": "espionage", # Whitefly
"G0109": "espionage", # Machete
"G0110": "espionage", # Dark Caracal
"G0111": "espionage", # Dark Basin
"G0112": "espionage", # Windshift
"G0113": "espionage", # Frankenstein
"G0115": "espionage", # HAFNIUM
"G0116": "espionage", # Operation Wocao
"G0117": "espionage", # Fox Kitten
"G0118": "espionage", # TA505
"G0120": "espionage", # Evilnum
"G0121": "espionage", # Sidewinder
"G0122": "espionage", # Silent Librarian
"G0123": "espionage", # Waterbear
"G0124": "espionage", # Windigo
"G0125": "espionage", # HAFNIUM (dup)
"G0126": "espionage", # Higaisa
"G0127": "espionage", # TA551
"G0128": "espionage", # ZIRCONIUM / APT31
"G0129": "espionage", # Mustang Panda
"G0130": "espionage", # Ajax Security Team
"G0131": "espionage", # Tonto Team
"G0133": "espionage", # Nomadic Octopus
"G0134": "espionage", # Sandworm (espionage+destruction)
"G0135": "espionage", # BackdoorDiplomacy
"G0136": "espionage", # IndigoZebra
"G0138": "espionage", # Threat Group-3390
"G0139": "espionage", # TeamTNT
"G0140": "espionage", # LazyScripter
"G0141": "espionage", # Aoqin Dragon
"G0142": "espionage", # Confucius
"G0143": "espionage", # Aquatic Panda
"G0144": "espionage", # TG-3390
"G0145": "espionage", # POLONIUM
# ── Destruction ────────────────────────────────────────────────
"G0034": "destruction", # Sandworm Team
"G0067": "destruction", # APT37 (also espionage)
"G0070": "destruction", # Dark Caracal
"G0072": "destruction", # Honeybee
"G0079": "destruction", # DarkHotel (partly)
"G0095": "destruction", # Machete (partly)
"G0031": "destruction", # Cleaver
# ── Hacktivism ─────────────────────────────────────────────────
"G0026": "hacktivism", # APT18 (some ops)
}
# Keyword patterns for description-based inference
_DESCRIPTION_KEYWORDS: list[tuple[str, str]] = [
# Financial first (strongest signal)
("financially motivated", "financial"),
("financial gain", "financial"),
("financial crime", "financial"),
("for financial", "financial"),
("ransomware", "financial"),
("extortion", "financial"),
("fraud", "financial"),
("profit", "financial"),
("monetar", "financial"),
("criminal group", "financial"),
("cybercriminal", "financial"),
("e-crime", "financial"),
# Destruction
("destructive", "destruction"),
("disruptive", "destruction"),
("wiper", "destruction"),
("sabotage", "destruction"),
("disrupt", "destruction"),
# Hacktivism
("hacktivist", "hacktivism"),
("political statement", "hacktivism"),
("ideolog", "hacktivism"),
# Espionage (broad, lowest priority)
("espionage", "espionage"),
("intelligence collection", "espionage"),
("intelligence gathering", "espionage"),
("cyber espionage", "espionage"),
("nation-state", "espionage"),
("state-sponsored", "espionage"),
("government-sponsored", "espionage"),
("military intelligence", "espionage"),
]
def _infer_motivation_from_description(description: str) -> str | None:
"""Infer motivation by scanning the group description for keywords."""
if not description:
return None
lower = description.lower()
for keyword, motivation in _DESCRIPTION_KEYWORDS:
if keyword in lower:
return motivation
return None
def _parse_intrusion_sets(objects: list) -> list[dict]:
"""Parse STIX intrusion-set objects into ThreatActor dicts."""
actors = []
@@ -171,9 +337,13 @@ def _parse_intrusion_sets(objects: list) -> list[dict]:
description = obj.get("description", "")
# Extract primary_motivation and sophistication from STIX object
# Derive motivation: curated override > STIX field > description inference
raw_motivation = obj.get("primary_motivation")
motivation = _normalize_motivation(raw_motivation)
motivation = (
_MITRE_ID_MOTIVATION.get(mitre_id or "")
or _normalize_motivation(raw_motivation)
or _infer_motivation_from_description(description)
)
sophistication = obj.get("sophistication") # e.g. "advanced", "expert"
# Extract references (non-MITRE)