threat intelligence

Analyzing APT Group with MITRE ATT&CK Navigator

Analyze advanced persistent threat (APT) group techniques using MITRE ATT&CK Navigator to create layered heatmaps of adversary TTPs for detection gap analysis and threat-informed defense.

aptdetection-gapheatmapmitre-attacknavigatorthreat-actorthreat-intelligencettp-analysis
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

MITRE ATT&CK Navigator is a web-based tool for annotating and exploring ATT&CK matrices, enabling analysts to visualize threat actor technique coverage, compare multiple APT groups, identify detection gaps, and build threat-informed defense strategies. This skill covers querying ATT&CK data programmatically, mapping APT group TTPs to Navigator layers, creating multi-layer overlays for gap analysis, and generating actionable intelligence reports for detection engineering teams.

When to Use

  • When investigating security incidents that require analyzing apt group with mitre navigator
  • When building detection rules or threat hunting queries for this domain
  • When SOC analysts need structured procedures for this analysis type
  • When validating security monitoring coverage for related attack techniques

Prerequisites

  • Python 3.9+ with attackcti, mitreattack-python, stix2, requests libraries
  • ATT&CK Navigator (https://mitre-attack.github.io/attack-navigator/) or local deployment
  • Understanding of ATT&CK Enterprise matrix: 14 Tactics, 200+ Techniques, Sub-techniques
  • Access to threat intelligence reports or MISP/OpenCTI for threat actor data
  • Familiarity with STIX 2.1 Intrusion Set and Attack Pattern objects

Key Concepts

ATT&CK Navigator Layers

Navigator layers are JSON files that annotate ATT&CK techniques with scores, colors, comments, and metadata. Each layer can represent a single APT group's technique usage, a detection capability map, or a combined overlay. Layer version 4.5 supports enterprise-attack, mobile-attack, and ics-attack domains with filtering by platform (Windows, Linux, macOS, Cloud, Azure AD, Office 365, SaaS).

APT Group Profiles in ATT&CK

ATT&CK catalogs over 140 threat groups with documented technique usage. Each group profile includes aliases, targeted sectors, associated campaigns, software used, and technique mappings with procedure-level detail. Groups are identified by G-codes (e.g., G0016 for APT29, G0007 for APT28, G0032 for Lazarus Group).

Multi-Layer Analysis

The Navigator supports loading multiple layers simultaneously, allowing analysts to overlay threat actor TTPs against detection coverage to identify gaps, compare multiple APT groups to find common techniques worth prioritizing, and track technique coverage changes over time.

Workflow

Step 1: Query ATT&CK Data for APT Group

from attackcti import attack_client
import json
 
lift = attack_client()
 
# Get all threat groups
groups = lift.get_groups()
print(f"Total ATT&CK groups: {len(groups)}")
 
# Find APT29 (Cozy Bear / Midnight Blizzard)
apt29 = next((g for g in groups if g.get('name') == 'APT29'), None)
if apt29:
    print(f"Group: {apt29['name']}")
    print(f"Aliases: {apt29.get('aliases', [])}")
    print(f"Description: {apt29.get('description', '')[:300]}")
 
# Get techniques used by APT29 (G0016)
techniques = lift.get_techniques_used_by_group("G0016")
print(f"APT29 uses {len(techniques)} techniques")
 
technique_map = {}
for tech in techniques:
    tech_id = ""
    for ref in tech.get("external_references", []):
        if ref.get("source_name") == "mitre-attack":
            tech_id = ref.get("external_id", "")
            break
    if tech_id:
        tactics = [p.get("phase_name", "") for p in tech.get("kill_chain_phases", [])]
        technique_map[tech_id] = {
            "name": tech.get("name", ""),
            "tactics": tactics,
            "description": tech.get("description", "")[:500],
            "platforms": tech.get("x_mitre_platforms", []),
            "data_sources": tech.get("x_mitre_data_sources", []),
        }

Step 2: Generate Navigator Layer JSON

def create_navigator_layer(group_name, technique_map, color="#ff6666"):
    techniques_list = []
    for tech_id, info in technique_map.items():
        for tactic in info["tactics"]:
            techniques_list.append({
                "techniqueID": tech_id,
                "tactic": tactic,
                "color": color,
                "comment": info["name"],
                "enabled": True,
                "score": 100,
                "metadata": [
                    {"name": "group", "value": group_name},
                    {"name": "platforms", "value": ", ".join(info["platforms"])},
                ],
            })
 
    layer = {
        "name": f"{group_name} TTP Coverage",
        "versions": {"attack": "16.1", "navigator": "5.1.0", "layer": "4.5"},
        "domain": "enterprise-attack",
        "description": f"Techniques attributed to {group_name}",
        "filters": {
            "platforms": ["Linux", "macOS", "Windows", "Cloud",
                          "Azure AD", "Office 365", "SaaS", "Google Workspace"]
        },
        "sorting": 0,
        "layout": {
            "layout": "side", "aggregateFunction": "average",
            "showID": True, "showName": True,
            "showAggregateScores": False, "countUnscored": False,
        },
        "hideDisabled": False,
        "techniques": techniques_list,
        "gradient": {"colors": ["#ffffff", color], "minValue": 0, "maxValue": 100},
        "legendItems": [
            {"label": f"Used by {group_name}", "color": color},
            {"label": "Not observed", "color": "#ffffff"},
        ],
        "showTacticRowBackground": True,
        "tacticRowBackground": "#dddddd",
        "selectTechniquesAcrossTactics": True,
        "selectSubtechniquesWithParent": False,
        "selectVisibleTechniques": False,
    }
    return layer
 
layer = create_navigator_layer("APT29", technique_map)
with open("apt29_layer.json", "w") as f:
    json.dump(layer, f, indent=2)
print("[+] Layer saved: apt29_layer.json")

Step 3: Compare Multiple APT Groups

groups_to_compare = {"G0016": "APT29", "G0007": "APT28", "G0032": "Lazarus Group"}
group_techniques = {}
 
for gid, gname in groups_to_compare.items():
    techs = lift.get_techniques_used_by_group(gid)
    tech_ids = set()
    for t in techs:
        for ref in t.get("external_references", []):
            if ref.get("source_name") == "mitre-attack":
                tech_ids.add(ref.get("external_id", ""))
    group_techniques[gname] = tech_ids
 
common_to_all = set.intersection(*group_techniques.values())
print(f"Techniques common to all groups: {len(common_to_all)}")
for tid in sorted(common_to_all):
    print(f"  {tid}")
 
for gname, techs in group_techniques.items():
    others = set.union(*[t for n, t in group_techniques.items() if n != gname])
    unique = techs - others
    print(f"\nUnique to {gname}: {len(unique)} techniques")

Step 4: Detection Gap Analysis with Layer Overlay

# Define your current detection capabilities
detected_techniques = {
    "T1059", "T1059.001", "T1071", "T1071.001", "T1566", "T1566.001",
    "T1547", "T1547.001", "T1053", "T1053.005", "T1078", "T1027",
}
 
actor_techniques = set(technique_map.keys())
covered = actor_techniques.intersection(detected_techniques)
gaps = actor_techniques - detected_techniques
 
print(f"=== Detection Gap Analysis for APT29 ===")
print(f"Actor techniques: {len(actor_techniques)}")
print(f"Detected: {len(covered)} ({len(covered)/len(actor_techniques)*100:.0f}%)")
print(f"Gaps: {len(gaps)} ({len(gaps)/len(actor_techniques)*100:.0f}%)")
 
# Create gap layer (red = undetected, green = detected)
gap_techniques = []
for tech_id in actor_techniques:
    info = technique_map.get(tech_id, {})
    for tactic in info.get("tactics", [""]):
        color = "#66ff66" if tech_id in detected_techniques else "#ff3333"
        gap_techniques.append({
            "techniqueID": tech_id,
            "tactic": tactic,
            "color": color,
            "comment": f"{'DETECTED' if tech_id in detected_techniques else 'GAP'}: {info.get('name', '')}",
            "enabled": True,
            "score": 100 if tech_id in detected_techniques else 0,
        })
 
gap_layer = {
    "name": "APT29 Detection Gap Analysis",
    "versions": {"attack": "16.1", "navigator": "5.1.0", "layer": "4.5"},
    "domain": "enterprise-attack",
    "description": "Green = detected, Red = gap",
    "techniques": gap_techniques,
    "gradient": {"colors": ["#ff3333", "#66ff66"], "minValue": 0, "maxValue": 100},
    "legendItems": [
        {"label": "Detected", "color": "#66ff66"},
        {"label": "Detection Gap", "color": "#ff3333"},
    ],
}
with open("apt29_gap_layer.json", "w") as f:
    json.dump(gap_layer, f, indent=2)

Step 5: Tactic Breakdown Analysis

from collections import defaultdict
 
tactic_breakdown = defaultdict(list)
for tech_id, info in technique_map.items():
    for tactic in info["tactics"]:
        tactic_breakdown[tactic].append({"id": tech_id, "name": info["name"]})
 
tactic_order = [
    "reconnaissance", "resource-development", "initial-access",
    "execution", "persistence", "privilege-escalation",
    "defense-evasion", "credential-access", "discovery",
    "lateral-movement", "collection", "command-and-control",
    "exfiltration", "impact",
]
 
print("\n=== APT29 Tactic Breakdown ===")
for tactic in tactic_order:
    techs = tactic_breakdown.get(tactic, [])
    if techs:
        print(f"\n{tactic.upper()} ({len(techs)} techniques):")
        for t in techs:
            print(f"  {t['id']}: {t['name']}")

Validation Criteria

  • ATT&CK data queried successfully via TAXII server
  • APT group mapped to all documented techniques with procedure examples
  • Navigator layer JSON validates and renders correctly in ATT&CK Navigator
  • Multi-layer overlay shows threat actor vs. detection coverage
  • Detection gap analysis identifies unmonitored techniques with data source recommendations
  • Cross-group comparison reveals shared and unique TTPs
  • Output is actionable for detection engineering prioritization

References

Source materials

References and resources

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

References 1

api-reference.md2.5 KB

API Reference: MITRE ATT&CK Navigator APT Analysis

ATT&CK Navigator Layer Format

Layer JSON Structure

{
  "name": "APT29 - TTPs",
  "versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"},
  "domain": "enterprise-attack",
  "techniques": [
    {
      "techniqueID": "T1566.001",
      "tactic": "initial-access",
      "color": "#ff6666",
      "score": 100,
      "comment": "Used by APT29",
      "enabled": true
    }
  ],
  "gradient": {"colors": ["#ffffff", "#ff6666"], "minValue": 0, "maxValue": 100}
}

ATT&CK STIX Data Access

Download Enterprise ATT&CK Bundle

curl -o enterprise-attack.json \
  https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json

STIX Object Types

Type Description
intrusion-set APT groups / threat actors
attack-pattern Techniques and sub-techniques
relationship Links groups to techniques (uses)
malware Malware families
tool Legitimate tools used by adversaries

mitreattack-python Library

Installation

pip install mitreattack-python

Query Group Techniques

from mitreattack.stix20 import MitreAttackData
 
attack = MitreAttackData("enterprise-attack.json")
groups = attack.get_groups()
for g in groups:
    techs = attack.get_techniques_used_by_group(g)
    print(f"{g.name}: {len(techs)} techniques")

Get Technique Details

technique = attack.get_object_by_attack_id("T1566.001", "attack-pattern")
print(technique.name)          # Spearphishing Attachment
print(technique.x_mitre_platforms)  # ['Windows', 'macOS', 'Linux']

Navigator CLI (attack-navigator)

Export Layer to SVG

npx attack-navigator-export \
  --layer layer.json \
  --output output.svg \
  --theme dark

ATT&CK API (TAXII)

from stix2 import TAXIICollectionSource, Filter
from taxii2client.v20 import Collection
 
collection = Collection(
    "https://cti-taxii.mitre.org/stix/collections/95ecc380-afe9-11e4-9b6c-751b66dd541e/"
)
tc_source = TAXIICollectionSource(collection)
groups = tc_source.query([Filter("type", "=", "intrusion-set")])

Key APT Groups Reference

ID Name Known Aliases
G0016 APT29 Cozy Bear, The Dukes, NOBELIUM
G0007 APT28 Fancy Bear, Sofacy, Strontium
G0022 APT3 Gothic Panda, UPS
G0032 Lazarus Group HIDDEN COBRA, Zinc
G0074 Dragonfly 2.0 Energetic Bear, Berserk Bear
G0010 Turla Waterbug, Venomous Bear

Scripts 1

agent.py8.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""APT group analysis agent using MITRE ATT&CK Navigator layers.

Queries ATT&CK data, maps APT techniques to Navigator layers,
performs detection gap analysis, and generates threat-informed reports.
"""

import json
import os
import sys
from collections import Counter

try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    HAS_REQUESTS = False

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

NAVIGATOR_LAYER_TEMPLATE = {
    "name": "",
    "versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"},
    "domain": "enterprise-attack",
    "description": "",
    "filters": {"platforms": ["Windows", "Linux", "macOS", "Cloud"]},
    "sorting": 0,
    "layout": {"layout": "side", "aggregateFunction": "average", "showID": False,
                "showName": True, "showAggregateScores": False, "countUnscored": False},
    "hideDisabled": False,
    "techniques": [],
    "gradient": {"colors": ["#ffffff", "#ff6666"], "minValue": 0, "maxValue": 100},
    "legendItems": [],
    "metadata": [],
    "links": [],
    "showTacticRowBackground": False,
    "tacticRowBackground": "#dddddd",
    "selectTechniquesAcrossTactics": True,
    "selectSubtechniquesWithParent": False,
    "selectVisibleTechniques": False,
}


def load_attack_data(filepath=None):
    """Load ATT&CK STIX bundle from file or download."""
    if filepath and os.path.exists(filepath):
        with open(filepath, "r", encoding="utf-8") as f:
            return json.load(f)
    if HAS_REQUESTS:
        print("[*] Downloading ATT&CK Enterprise data...")
        resp = requests.get(ATTACK_ENTERPRISE_URL, timeout=60)
        resp.raise_for_status()
        return resp.json()
    return None


def extract_groups(bundle):
    """Extract intrusion-set (APT group) objects from STIX bundle."""
    groups = {}
    for obj in bundle.get("objects", []):
        if obj.get("type") == "intrusion-set":
            name = obj.get("name", "Unknown")
            aliases = obj.get("aliases", [])
            ext_refs = obj.get("external_references", [])
            attack_id = ""
            for ref in ext_refs:
                if ref.get("source_name") == "mitre-attack":
                    attack_id = ref.get("external_id", "")
                    break
            groups[obj["id"]] = {
                "name": name, "id": attack_id, "aliases": aliases,
                "description": obj.get("description", "")[:200],
            }
    return groups


def extract_techniques(bundle):
    """Extract attack-pattern (technique) objects from STIX bundle."""
    techniques = {}
    for obj in bundle.get("objects", []):
        if obj.get("type") == "attack-pattern" and not obj.get("revoked", False):
            ext_refs = obj.get("external_references", [])
            attack_id = ""
            for ref in ext_refs:
                if ref.get("source_name") == "mitre-attack":
                    attack_id = ref.get("external_id", "")
                    break
            if attack_id:
                tactics = [p["phase_name"] for p in obj.get("kill_chain_phases", [])]
                techniques[obj["id"]] = {
                    "id": attack_id, "name": obj.get("name", ""),
                    "tactics": tactics, "platforms": obj.get("x_mitre_platforms", []),
                }
    return techniques


def map_group_techniques(bundle, group_stix_id, techniques):
    """Map techniques used by a specific group via relationship objects."""
    group_techniques = []
    for obj in bundle.get("objects", []):
        if (obj.get("type") == "relationship" and
                obj.get("relationship_type") == "uses" and
                obj.get("source_ref") == group_stix_id and
                obj.get("target_ref", "").startswith("attack-pattern--")):
            tech_id = obj["target_ref"]
            if tech_id in techniques:
                group_techniques.append(techniques[tech_id])
    return group_techniques


def build_navigator_layer(group_name, group_techniques, color="#ff6666", score=100):
    """Build ATT&CK Navigator JSON layer for a group's techniques."""
    layer = json.loads(json.dumps(NAVIGATOR_LAYER_TEMPLATE))
    layer["name"] = f"{group_name} - TTPs"
    layer["description"] = f"ATT&CK techniques attributed to {group_name}"
    for tech in group_techniques:
        entry = {
            "techniqueID": tech["id"],
            "tactic": tech["tactics"][0] if tech["tactics"] else "",
            "color": color,
            "comment": f"Used by {group_name}",
            "enabled": True,
            "metadata": [],
            "links": [],
            "showSubtechniques": False,
            "score": score,
        }
        layer["techniques"].append(entry)
    return layer


def detection_gap_analysis(group_techniques, detection_rules):
    """Compare group TTPs against existing detection rules to find gaps."""
    covered = set()
    for rule in detection_rules:
        tech_id = rule.get("technique_id", "")
        if tech_id:
            covered.add(tech_id)
    gaps = []
    for tech in group_techniques:
        if tech["id"] not in covered:
            gaps.append({
                "technique_id": tech["id"],
                "technique_name": tech["name"],
                "tactics": tech["tactics"],
                "status": "NO DETECTION",
            })
    coverage_pct = (len(covered & {t["id"] for t in group_techniques}) /
                    len(group_techniques) * 100) if group_techniques else 0
    return gaps, round(coverage_pct, 1)


def tactic_heatmap(group_techniques):
    """Generate tactic-level heatmap showing technique distribution."""
    tactic_counts = Counter()
    for tech in group_techniques:
        for tactic in tech["tactics"]:
            tactic_counts[tactic] += 1
    return dict(tactic_counts.most_common())


def compare_groups(group_a_techs, group_b_techs):
    """Compare two groups' technique sets for overlap analysis."""
    set_a = {t["id"] for t in group_a_techs}
    set_b = {t["id"] for t in group_b_techs}
    overlap = set_a & set_b
    only_a = set_a - set_b
    only_b = set_b - set_a
    jaccard = len(overlap) / len(set_a | set_b) if (set_a | set_b) else 0
    return {
        "overlap_count": len(overlap), "overlap_ids": sorted(overlap),
        "only_group_a": len(only_a), "only_group_b": len(only_b),
        "jaccard_similarity": round(jaccard, 4),
    }


def save_layer(layer, output_path):
    """Save Navigator layer to JSON file."""
    with open(output_path, "w", encoding="utf-8") as f:
        json.dump(layer, f, indent=2)
    print(f"[+] Layer saved: {output_path}")


if __name__ == "__main__":
    print("=" * 60)
    print("APT Group Analysis Agent - MITRE ATT&CK Navigator")
    print("TTP mapping, detection gap analysis, group comparison")
    print("=" * 60)

    group_name = sys.argv[1] if len(sys.argv) > 1 else None
    attack_file = sys.argv[2] if len(sys.argv) > 2 else None

    bundle = load_attack_data(attack_file)
    if not bundle:
        print("\n[!] Cannot load ATT&CK data. Provide STIX bundle path or install requests.")
        print("[DEMO] Usage:")
        print("  python agent.py APT29 enterprise-attack.json")
        print("  python agent.py APT28   # downloads from GitHub")
        sys.exit(1)

    groups = extract_groups(bundle)
    techniques = extract_techniques(bundle)
    print(f"[*] Loaded {len(groups)} groups, {len(techniques)} techniques")

    if not group_name:
        print("\n--- Available APT Groups (sample) ---")
        for gid, g in list(groups.items())[:20]:
            print(f"  {g['id']:8s} {g['name']:30s} aliases={g['aliases'][:3]}")
        sys.exit(0)

    target_group = None
    for gid, g in groups.items():
        if (g["name"].lower() == group_name.lower() or
                g["id"].lower() == group_name.lower() or
                group_name.lower() in [a.lower() for a in g["aliases"]]):
            target_group = (gid, g)
            break

    if not target_group:
        print(f"[!] Group '{group_name}' not found")
        sys.exit(1)

    gid, ginfo = target_group
    print(f"\n[*] Group: {ginfo['name']} ({ginfo['id']})")
    print(f"    Aliases: {', '.join(ginfo['aliases'][:5])}")

    group_techs = map_group_techniques(bundle, gid, techniques)
    print(f"    Techniques: {len(group_techs)}")

    heatmap = tactic_heatmap(group_techs)
    print("\n--- Tactic Heatmap ---")
    for tactic, count in heatmap.items():
        bar = "#" * count
        print(f"  {tactic:35s} {count:3d} {bar}")

    layer = build_navigator_layer(ginfo["name"], group_techs)
    out_file = f"{ginfo['name'].replace(' ', '_')}_layer.json"
    save_layer(layer, out_file)

    sample_rules = [{"technique_id": t["id"]} for t in group_techs[:len(group_techs)//2]]
    gaps, coverage = detection_gap_analysis(group_techs, sample_rules)
    print(f"\n--- Detection Gap Analysis (demo: {coverage}% coverage) ---")
    for gap in gaps[:10]:
        print(f"  [GAP] {gap['technique_id']:12s} {gap['technique_name']}")
Keep exploring