feat(threat-actors): infer motivation via curated map + description keywords
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user