threat intelligence

Profiling Threat Actor Groups

Develops comprehensive threat actor profiles for APT groups, criminal organizations, and hacktivist collectives by aggregating TTP documentation, historical campaign data, tooling fingerprints, and attribution indicators from multiple intelligence sources. Use when briefing executives on sector-specific threats, updating threat model assumptions, or prioritizing defensive controls against specific adversaries. Activates for requests involving MITRE ATT&CK Groups, Mandiant APT profiles, CrowdStrike adversary naming, or sector-specific threat briefings.

aptattributioncrowdstrikekill-chainmandiantmitre-att&cknist-csfthreat-actor
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

Use this skill when:

  • Updating the organization's threat model with profiles of adversary groups recently observed targeting your sector
  • Preparing an executive briefing on APT groups that align with geopolitical events affecting your business
  • Enabling SOC analysts to understand attacker objectives and TTPs to improve detection tuning

Do not use this skill for real-time incident attribution — attribution during active incidents should be deprioritized in favor of containment. Profile refinement occurs post-incident.

Prerequisites

  • Access to MITRE ATT&CK Groups database (https://attack.mitre.org/groups/)
  • Commercial threat intelligence subscription (Mandiant Advantage, CrowdStrike Falcon Intelligence, or Recorded Future)
  • Sector-specific ISAC membership for targeted intelligence (FS-ISAC, H-ISAC, E-ISAC)
  • Structured profile template (see workflow below)

Workflow

Step 1: Identify Relevant Threat Actors

Cross-reference your organization's sector, geography, and technology stack against known adversary targeting patterns. Sources:

  • MITRE ATT&CK Groups: 130+ documented nation-state and criminal groups with TTP mappings
  • CrowdStrike Annual Threat Report: adversary naming by nation-state (BEAR=Russia, PANDA=China, KITTEN=Iran, CHOLLIMA=North Korea)
  • Mandiant M-Trends: annual report with sector-specific targeting statistics
  • CISA Known Exploited Vulnerabilities (KEV) catalog: identifies vulnerabilities actively exploited by specific threat actors

Shortlist 5–10 groups most likely to target your organization based on sector alignment and recent activity.

Step 2: Collect Profile Data

For each adversary, document across standard dimensions:

Identity: ATT&CK Group ID (e.g., G0016 for APT29), aliases (Cozy Bear, The Dukes, Midnight Blizzard), suspected nation-state sponsor

Motivations: Espionage, financial gain, disruption, intellectual property theft

Targeting: Sectors, geographies, organization sizes, technology targets (OT/IT, cloud, supply chain)

Capabilities: Custom malware (e.g., APT29's SUNBURST, MiniDuke), exploitation of 0-days vs. known CVEs, supply chain attack capability

Campaign History: Notable operations with dates (SolarWinds 2020, Exchange Server 2021, etc.)

TTPs by ATT&CK Phase: Document top 5 techniques per tactic phase

Step 3: Map TTPs to ATT&CK

Using mitreattack-python:

from mitreattack.stix20 import MitreAttackData
 
mitre = MitreAttackData("enterprise-attack.json")
apt29 = mitre.get_object_by_attack_id("G0016", "groups")
techniques = mitre.get_techniques_used_by_group(apt29)
 
profile = {}
for item in techniques:
    tech = item["object"]
    tid = tech["external_references"][0]["external_id"]
    tactic = [p["phase_name"] for p in tech.get("kill_chain_phases", [])]
    profile[tid] = {"name": tech["name"], "tactics": tactic}

Step 4: Assess Detection Coverage Against Profile

Compare the adversary's technique list against your detection coverage matrix (from ATT&CK Navigator layer). Identify:

  • Techniques used by this group where you have no detection (critical gaps)
  • Techniques where you have partial coverage (logging but no alerting)
  • Compensating controls where detection is not feasible (network segmentation as mitigation for lateral movement)

Step 5: Package Profile for Distribution

Structure the final profile for different audiences:

  • Executive summary (1 page): Who, motivation, recent campaigns, top risk to our organization, recommended priority actions
  • SOC analyst brief (3–5 pages): Full TTP list with detection status, IOC list, hunt hypotheses
  • Technical appendix: YARA rules, Sigma detections, STIX JSON object for TIP import

Classify TLP:AMBER for internal distribution; seek ISAC approval before external sharing.

Key Concepts

Term Definition
APT Advanced Persistent Threat — well-resourced, sophisticated adversary (typically nation-state or sophisticated criminal) conducting long-term targeted operations
TTPs Tactics, Techniques, Procedures — behavioral fingerprint of an adversary group, more durable than IOCs which change frequently
Aliases Threat actors receive different names from different vendors (APT29 = Cozy Bear = The Dukes = Midnight Blizzard = YTTRIUM)
Attribution Process of associating an attack with a specific threat actor; requires multiple independent corroborating data points and carries inherent uncertainty
Cluster A group of related intrusion activity that may or may not be attributable to a single actor; used when attribution is uncertain
Intrusion Set STIX SDO type representing a grouped set of adversarial behaviors with common objectives, even if actor identity is unknown

Tools & Systems

  • MITRE ATT&CK Groups: Free, community-maintained database of 130+ documented adversary groups with referenced campaign reports
  • Mandiant Advantage Threat Intelligence: Commercial platform with detailed APT profiles, malware families, and campaign analysis
  • CrowdStrike Falcon Intelligence: Commercial feed with adversary-centric profiles and real-time attribution updates
  • Recorded Future Threat Intelligence: Combines OSINT, dark web, and technical intelligence for adversary profiling
  • OpenCTI: Graph-based visualization of threat actor relationships, tooling, and campaign linkages

Common Pitfalls

  • IOC-centric profiles: Building profiles around IP addresses and domains rather than TTPs means the profile becomes stale within weeks as infrastructure rotates.
  • Vendor alias confusion: Conflating two different threat actor groups due to shared malware or infrastructure leads to incorrect threat model assumptions.
  • Binary attribution: Treating attribution as certain when it is probabilistic. Always qualify attribution confidence level (Low/Medium/High).
  • Neglecting insider and criminal groups: Overemphasis on nation-state APTs while ignoring ransomware groups (Cl0p, LockBit, ALPHV) which represent higher probability threats for most organizations.
  • Profile staleness: Adversary TTPs evolve. Profiles not updated quarterly may miss technique changes, new malware, or targeting shifts.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.2 KB

API Reference: Threat Actor Profiling Agent

Overview

Builds threat actor profiles from MITRE ATT&CK STIX data using the stix2 MemoryStore. Queries intrusion-set objects for TTPs, software, and relationships, enabling group comparison and tactic mapping.

Dependencies

Package Version Purpose
stix2 >= 3.0 STIX 2.1 object store and filtering
requests >= 2.28 ATT&CK STIX data download

Data Source

MITRE ATT&CK Enterprise STIX bundle from https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json. Cached locally at /tmp/enterprise-attack.json.

Core Functions

load_attack_data(cache_path)

Downloads and caches ATT&CK STIX data into a stix2 MemoryStore.

  • Returns: MemoryStore instance

list_threat_groups(src)

Lists all intrusion-set objects with name, aliases, and description.

  • Returns: list[dict] sorted by name

get_group_profile(src, group_name)

Full profile: description, aliases, techniques with ATT&CK IDs, software (malware/tools), external references.

  • Search: Exact match on name, then fuzzy match on name and aliases
  • Returns: dict with techniques, software, references

get_group_techniques_by_tactic(src, group_name)

Organizes a group's techniques by ATT&CK tactic (kill chain phase).

  • Returns: dict with tactics mapped to technique lists

compare_groups(src, group_names)

Compares multiple groups: shared techniques, technique counts, software counts.

  • Returns: dict with shared_techniques and per-group statistics

STIX Object Types Queried

Type ATT&CK Concept
intrusion-set Threat actor group
attack-pattern ATT&CK technique
malware Malware family
tool Legitimate tool used by attacker
relationship Links between groups, techniques, software

Usage

python agent.py APT29
python agent.py "Lazarus Group"

Example Output Fields

{
  "name": "APT29",
  "aliases": ["NOBELIUM", "Cozy Bear", "The Dukes"],
  "techniques": [{"name": "Phishing", "technique_id": "T1566"}],
  "software": [{"name": "Cobalt Strike", "type": "tool"}]
}

Scripts 1

agent.py6.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Threat actor profiling agent using MITRE ATT&CK STIX data and STIX2 library."""

import json
import sys
import os

try:
    from stix2 import MemoryStore, Filter
    import requests
except ImportError:
    print("Install: pip install stix2 requests")
    sys.exit(1)

ATTACK_STIX_URL = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"


def load_attack_data(cache_path="/tmp/enterprise-attack.json"):
    """Load MITRE ATT&CK STIX data from cache or download."""
    if os.path.exists(cache_path):
        with open(cache_path, "r") as f:
            data = json.load(f)
    else:
        resp = requests.get(ATTACK_STIX_URL, timeout=60)
        resp.raise_for_status()
        data = resp.json()
        with open(cache_path, "w") as f:
            json.dump(data, f)
    return MemoryStore(stix_data=data["objects"])


def list_threat_groups(src):
    """List all threat actor groups in ATT&CK."""
    groups = src.query([Filter("type", "=", "intrusion-set")])
    result = []
    for g in groups:
        aliases = getattr(g, "aliases", [])
        result.append({
            "id": g.id,
            "name": g.name,
            "aliases": aliases if aliases else [],
            "description": (g.description[:200] + "...") if hasattr(g, "description") and g.description else "",
            "created": str(g.created),
            "modified": str(g.modified),
        })
    return sorted(result, key=lambda x: x["name"])


def get_group_profile(src, group_name):
    """Build a comprehensive profile for a specific threat actor group."""
    groups = src.query([
        Filter("type", "=", "intrusion-set"),
        Filter("name", "=", group_name),
    ])
    if not groups:
        groups = src.query([Filter("type", "=", "intrusion-set")])
        groups = [g for g in groups if group_name.lower() in g.name.lower()
                  or any(group_name.lower() in a.lower() for a in getattr(g, "aliases", []))]
    if not groups:
        return {"error": f"Group '{group_name}' not found"}
    group = groups[0]
    profile = {
        "name": group.name,
        "id": group.id,
        "aliases": getattr(group, "aliases", []),
        "description": getattr(group, "description", ""),
        "created": str(group.created),
        "modified": str(group.modified),
        "external_references": [],
    }
    for ref in getattr(group, "external_references", []):
        if hasattr(ref, "source_name"):
            profile["external_references"].append({
                "source": ref.source_name,
                "url": getattr(ref, "url", ""),
                "external_id": getattr(ref, "external_id", ""),
            })
    relationships = src.query([
        Filter("type", "=", "relationship"),
        Filter("source_ref", "=", group.id),
    ])
    profile["techniques"] = []
    profile["software"] = []
    for rel in relationships:
        target = src.get(rel.target_ref)
        if target:
            if target.type == "attack-pattern":
                technique = {
                    "name": target.name,
                    "technique_id": "",
                    "description": rel.description[:200] if hasattr(rel, "description") and rel.description else "",
                }
                for ref in getattr(target, "external_references", []):
                    if hasattr(ref, "external_id") and ref.external_id.startswith("T"):
                        technique["technique_id"] = ref.external_id
                profile["techniques"].append(technique)
            elif target.type in ("malware", "tool"):
                profile["software"].append({
                    "name": target.name,
                    "type": target.type,
                    "description": (target.description[:200] + "...") if hasattr(target, "description") and target.description else "",
                })
    return profile


def get_group_techniques_by_tactic(src, group_name):
    """Map a group's techniques organized by ATT&CK tactic."""
    profile = get_group_profile(src, group_name)
    if "error" in profile:
        return profile
    tactic_map = {}
    techniques = src.query([Filter("type", "=", "attack-pattern")])
    tech_lookup = {}
    for t in techniques:
        for ref in getattr(t, "external_references", []):
            if hasattr(ref, "external_id") and ref.external_id.startswith("T"):
                tech_lookup[t.name] = {
                    "id": ref.external_id,
                    "tactics": [p["phase_name"] for p in getattr(t, "kill_chain_phases", [])],
                }
    for tech in profile["techniques"]:
        info = tech_lookup.get(tech["name"], {})
        for tactic in info.get("tactics", ["unknown"]):
            tactic_map.setdefault(tactic, []).append({
                "technique": tech["name"],
                "id": info.get("id", tech.get("technique_id", "")),
            })
    return {"group": group_name, "tactics": tactic_map}


def compare_groups(src, group_names):
    """Compare techniques and tools across multiple threat actor groups."""
    profiles = {}
    for name in group_names:
        p = get_group_profile(src, name)
        if "error" not in p:
            profiles[name] = p
    all_techniques = {}
    for name, profile in profiles.items():
        for tech in profile["techniques"]:
            tech_name = tech["name"]
            all_techniques.setdefault(tech_name, set()).add(name)
    shared = {t: list(g) for t, g in all_techniques.items() if len(g) > 1}
    return {
        "groups": list(profiles.keys()),
        "shared_techniques": shared,
        "technique_counts": {n: len(p["techniques"]) for n, p in profiles.items()},
        "software_counts": {n: len(p["software"]) for n, p in profiles.items()},
    }


def print_profile(profile):
    print(f"Threat Actor Profile: {profile['name']}")
    print("=" * 50)
    if profile.get("aliases"):
        print(f"Aliases: {', '.join(profile['aliases'])}")
    print(f"\nDescription:\n{profile.get('description', '')[:500]}")
    print(f"\nTechniques ({len(profile.get('techniques', []))}):")
    for t in profile.get("techniques", [])[:20]:
        print(f"  [{t.get('technique_id', 'N/A'):8s}] {t['name']}")
    print(f"\nSoftware ({len(profile.get('software', []))}):")
    for s in profile.get("software", []):
        print(f"  [{s['type']:7s}] {s['name']}")
    print(f"\nReferences:")
    for r in profile.get("external_references", [])[:5]:
        print(f"  {r['source']}: {r.get('url', r.get('external_id', ''))}")


if __name__ == "__main__":
    group_name = sys.argv[1] if len(sys.argv) > 1 else "APT29"
    print("Loading MITRE ATT&CK data...")
    src = load_attack_data()
    profile = get_group_profile(src, group_name)
    if "error" in profile:
        print(profile["error"])
        print("\nAvailable groups:")
        for g in list_threat_groups(src)[:20]:
            print(f"  {g['name']}: {', '.join(g['aliases'][:3])}")
    else:
        print_profile(profile)
Keep exploring