CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-mitreattack-python

MITRE ATT&CK python library for accessing, querying, and manipulating ATT&CK threat intelligence data.

Pending
Overview
Eval results
Files

version-comparison.mddocs/

Version Comparison and Diff Analysis

Compare different versions of ATT&CK data and generate detailed changelog reports showing additions, modifications, and removals between releases. This module provides comprehensive tools for analyzing changes in the ATT&CK framework over time, tracking the evolution of techniques, groups, software, and relationships.

Capabilities

Main Diff Classes

Core classes for comparing ATT&CK versions and generating change reports.

class DiffStix:
    def __init__(self, old_data: dict, new_data: dict):
        """
        Initialize ATT&CK version comparison.
        
        Args:
            old_data (dict): Earlier version STIX data bundle
            new_data (dict): Later version STIX data bundle
        """
    
    def generate_changelog(self) -> dict:
        """
        Generate comprehensive changelog between versions.
        
        Returns:
            dict: Structured changelog with additions, modifications, and removals
        """
    
    def export_changelog(self, output_file: str, format: str = "json") -> None:
        """
        Export changelog to file.
        
        Args:
            output_file (str): Path to output file
            format (str): Output format ("json", "markdown", "html")
        """
    
    def get_added_objects(self, object_type: str = None) -> List[dict]:
        """
        Get objects added in the new version.
        
        Args:
            object_type (str, optional): Filter by STIX object type
            
        Returns:
            List[dict]: List of added objects
        """
    
    def get_removed_objects(self, object_type: str = None) -> List[dict]:
        """
        Get objects removed in the new version.
        
        Args:
            object_type (str, optional): Filter by STIX object type
            
        Returns:
            List[dict]: List of removed objects
        """
    
    def get_modified_objects(self, object_type: str = None) -> List[dict]:
        """
        Get objects modified between versions.
        
        Args:
            object_type (str, optional): Filter by STIX object type
            
        Returns:
            List[dict]: List of modified objects with change details
        """
    
    def compare_relationships(self) -> dict:
        """
        Compare relationships between versions.
        
        Returns:
            dict: Changes in relationships (added, removed, modified)
        """
    
    def generate_summary_statistics(self) -> dict:
        """
        Generate summary statistics of changes.
        
        Returns:
            dict: Summary counts of additions, modifications, removals by type
        """

Version Tracking Classes

Support classes for tracking object versions and changes.

class AttackObjectVersion:
    def __init__(self, stix_object: dict, version_info: dict):
        """
        Track version information for an ATT&CK object.
        
        Args:
            stix_object (dict): STIX object data
            version_info (dict): Version metadata
        """
    
    def get_version_string(self) -> str:
        """
        Get version string for the object.
        
        Returns:
            str: Version identifier
        """
    
    def compare_with(self, other: 'AttackObjectVersion') -> dict:
        """
        Compare this version with another version of the same object.
        
        Args:
            other (AttackObjectVersion): Other version to compare with
            
        Returns:
            dict: Detailed comparison results
        """
    
    def get_field_changes(self, other: 'AttackObjectVersion') -> Dict[str, dict]:
        """
        Get field-level changes between versions.
        
        Args:
            other (AttackObjectVersion): Other version to compare with
            
        Returns:
            Dict[str, dict]: Field changes with old and new values
        """

JSON Encoding for Changes

Specialized JSON encoder for serializing change data.

class AttackChangesEncoder(json.JSONEncoder):
    def default(self, obj):
        """
        Custom JSON encoder for ATT&CK change objects.
        
        Args:
            obj: Object to encode
            
        Returns:
            Serializable representation of the object
        """

CLI Function

Command-line interface for version comparison workflows.

def main(args: List[str] = None) -> None:
    """
    CLI entry point for diff_stix command.
    
    Args:
        args (List[str], optional): Command-line arguments
            --old: Path to older version STIX file
            --new: Path to newer version STIX file  
            --output: Output file path for changelog
            --format: Output format (json, markdown, html)
            --filter: Filter by object type
            --summary-only: Generate summary statistics only
    """

Usage Examples

Basic Version Comparison

import json
from mitreattack.diffStix import DiffStix

# Load two versions of ATT&CK data
with open("enterprise-attack-v14.1.json", "r") as f:
    old_data = json.load(f)

with open("enterprise-attack-v15.0.json", "r") as f:
    new_data = json.load(f)

# Initialize comparison
diff_analyzer = DiffStix(old_data, new_data)

# Generate changelog
changelog = diff_analyzer.generate_changelog()

# Export to file
diff_analyzer.export_changelog("v14.1_to_v15.0_changelog.json", format="json")
diff_analyzer.export_changelog("v14.1_to_v15.0_changelog.md", format="markdown")

print("Changelog generated successfully")

Analyzing Specific Changes

from mitreattack.diffStix import DiffStix
import json

# Load version data
with open("old_version.json", "r") as f:
    old_data = json.load(f)

with open("new_version.json", "r") as f:
    new_data = json.load(f)

diff_analyzer = DiffStix(old_data, new_data)

# Get added techniques
added_techniques = diff_analyzer.get_added_objects("attack-pattern")
print(f"Added techniques: {len(added_techniques)}")

for technique in added_techniques[:5]:  # Show first 5
    print(f"  - {technique.get('name', 'Unknown')} ({technique.get('external_references', [{}])[0].get('external_id', 'No ID')})")

# Get removed groups
removed_groups = diff_analyzer.get_removed_objects("intrusion-set")
print(f"\\nRemoved groups: {len(removed_groups)}")

# Get modified software
modified_software = diff_analyzer.get_modified_objects("malware")
modified_tools = diff_analyzer.get_modified_objects("tool")
print(f"\\nModified software: {len(modified_software + modified_tools)}")

# Relationship changes
relationship_changes = diff_analyzer.compare_relationships()
print(f"\\nRelationship changes:")
print(f"  Added: {len(relationship_changes.get('added', []))}")
print(f"  Removed: {len(relationship_changes.get('removed', []))}")
print(f"  Modified: {len(relationship_changes.get('modified', []))}")

Summary Statistics and Reporting

from mitreattack.diffStix import DiffStix
import json

# Perform comparison
diff_analyzer = DiffStix(old_data, new_data)

# Generate summary statistics
summary = diff_analyzer.generate_summary_statistics()

print("ATT&CK Version Comparison Summary")
print("=" * 40)

for object_type, changes in summary.items():
    if isinstance(changes, dict):
        print(f"{object_type.upper()}:")
        print(f"  Added: {changes.get('added', 0)}")
        print(f"  Removed: {changes.get('removed', 0)}")
        print(f"  Modified: {changes.get('modified', 0)}")
        print()

# Export summary as JSON
with open("version_comparison_summary.json", "w") as f:
    json.dump(summary, f, indent=2, cls=AttackChangesEncoder)

Detailed Object Change Analysis

from mitreattack.diffStix import DiffStix, AttackObjectVersion
import json

diff_analyzer = DiffStix(old_data, new_data)

# Get modified techniques for detailed analysis
modified_techniques = diff_analyzer.get_modified_objects("attack-pattern")

print(f"Analyzing {len(modified_techniques)} modified techniques...")

for modified_technique in modified_techniques[:3]:  # Analyze first 3
    technique_id = modified_technique.get('external_references', [{}])[0].get('external_id', 'Unknown')
    technique_name = modified_technique.get('name', 'Unknown')
    
    print(f"\\nTechnique: {technique_name} ({technique_id})")
    
    # Get detailed field changes
    if 'changes' in modified_technique:
        changes = modified_technique['changes']
        
        for field, change_info in changes.items():
            if isinstance(change_info, dict) and 'old' in change_info and 'new' in change_info:
                old_value = change_info['old']
                new_value = change_info['new']
                
                # Truncate long values for display
                if len(str(old_value)) > 100:
                    old_value = str(old_value)[:97] + "..."
                if len(str(new_value)) > 100:
                    new_value = str(new_value)[:97] + "..."
                    
                print(f"  {field}:")
                print(f"    Old: {old_value}")
                print(f"    New: {new_value}")

CLI Usage for Version Comparison

# Compare two specific versions
diff_stix --old enterprise-attack-v14.1.json --new enterprise-attack-v15.0.json --output changelog.json

# Generate markdown changelog
diff_stix --old old_version.json --new new_version.json --output changelog.md --format markdown

# Filter by specific object type
diff_stix --old old_version.json --new new_version.json --filter attack-pattern --output technique_changes.json

# Generate summary only
diff_stix --old old_version.json --new new_version.json --summary-only --output summary.json

# Compare with HTML output
diff_stix --old old_version.json --new new_version.json --format html --output changes_report.html

Automated Version Tracking Workflow

import json
from pathlib import Path
from mitreattack.diffStix import DiffStix
from mitreattack.download_stix import download_stix, get_available_versions
from mitreattack.release_info import STIX20

def track_attack_evolution(versions: List[str], domain: str = "enterprise-attack"):
    """
    Track ATT&CK evolution across multiple versions.
    """
    download_dir = Path("./version_tracking/")
    download_dir.mkdir(exist_ok=True)
    
    # Download all requested versions
    version_files = {}
    for version in versions:
        file_path = download_stix(
            stix_version="2.0",
            domain=domain,
            download_dir=str(download_dir),
            release=version,
            known_hash=STIX20[version]
        )
        version_files[version] = file_path
    
    # Compare consecutive versions
    evolution_report = {"comparisons": []}
    
    for i in range(len(versions) - 1):
        old_version = versions[i]
        new_version = versions[i + 1]
        
        print(f"Comparing {old_version} -> {new_version}")
        
        # Load data
        with open(version_files[old_version], "r") as f:
            old_data = json.load(f)
        with open(version_files[new_version], "r") as f:
            new_data = json.load(f)
        
        # Perform comparison
        diff_analyzer = DiffStix(old_data, new_data)
        summary = diff_analyzer.generate_summary_statistics()
        
        comparison_result = {
            "from_version": old_version,
            "to_version": new_version,
            "summary": summary
        }
        evolution_report["comparisons"].append(comparison_result)
        
        # Export detailed changelog
        changelog_file = download_dir / f"{old_version}_to_{new_version}_changelog.json"
        diff_analyzer.export_changelog(str(changelog_file), format="json")
    
    # Save evolution report
    with open(download_dir / "evolution_report.json", "w") as f:
        json.dump(evolution_report, f, indent=2)
    
    print(f"Evolution tracking complete. Reports saved to {download_dir}")
    return evolution_report

# Track evolution across recent versions
recent_versions = ["13.1", "14.0", "14.1", "15.0", "15.1"]
evolution_data = track_attack_evolution(recent_versions)

# Print summary
for comparison in evolution_data["comparisons"]:
    from_ver = comparison["from_version"]
    to_ver = comparison["to_version"]
    summary = comparison["summary"]
    
    print(f"\\n{from_ver} -> {to_ver}:")
    
    total_added = sum(stats.get("added", 0) for stats in summary.values() if isinstance(stats, dict))
    total_modified = sum(stats.get("modified", 0) for stats in summary.values() if isinstance(stats, dict))
    total_removed = sum(stats.get("removed", 0) for stats in summary.values() if isinstance(stats, dict))
    
    print(f"  Total changes: +{total_added} / ~{total_modified} / -{total_removed}")

Custom Change Analysis

from mitreattack.diffStix import DiffStix
import json

def analyze_technique_changes(old_data: dict, new_data: dict):
    """
    Custom analysis focusing on technique changes.
    """
    diff_analyzer = DiffStix(old_data, new_data)
    
    # Get technique changes
    added_techniques = diff_analyzer.get_added_objects("attack-pattern")
    modified_techniques = diff_analyzer.get_modified_objects("attack-pattern")
    removed_techniques = diff_analyzer.get_removed_objects("attack-pattern")
    
    analysis_results = {
        "new_techniques": [],
        "updated_techniques": [],
        "deprecated_techniques": [],
        "platform_changes": {},
        "tactic_changes": {}
    }
    
    # Analyze added techniques
    for technique in added_techniques:
        tech_id = technique.get('external_references', [{}])[0].get('external_id', 'Unknown')
        tech_name = technique.get('name', 'Unknown')
        platforms = technique.get('x_mitre_platforms', [])
        
        analysis_results["new_techniques"].append({
            "id": tech_id,
            "name": tech_name,
            "platforms": platforms
        })
    
    # Analyze modified techniques for platform changes
    for technique in modified_techniques:
        if 'changes' in technique and 'x_mitre_platforms' in technique['changes']:
            tech_id = technique.get('external_references', [{}])[0].get('external_id', 'Unknown')
            old_platforms = technique['changes']['x_mitre_platforms'].get('old', [])
            new_platforms = technique['changes']['x_mitre_platforms'].get('new', [])
            
            added_platforms = set(new_platforms) - set(old_platforms)
            removed_platforms = set(old_platforms) - set(new_platforms)
            
            if added_platforms or removed_platforms:
                analysis_results["platform_changes"][tech_id] = {
                    "added_platforms": list(added_platforms),
                    "removed_platforms": list(removed_platforms)
                }
    
    return analysis_results

# Perform custom analysis
with open("old_attack.json", "r") as f:
    old_data = json.load(f)
with open("new_attack.json", "r") as f:
    new_data = json.load(f)

custom_analysis = analyze_technique_changes(old_data, new_data)

print(f"New techniques: {len(custom_analysis['new_techniques'])}")
print(f"Platform changes: {len(custom_analysis['platform_changes'])}")

# Show platform changes
for tech_id, changes in custom_analysis["platform_changes"].items():
    if changes["added_platforms"]:
        print(f"{tech_id}: Added platforms: {', '.join(changes['added_platforms'])}")
    if changes["removed_platforms"]:
        print(f"{tech_id}: Removed platforms: {', '.join(changes['removed_platforms'])}")

Install with Tessl CLI

npx tessl i tessl/pypi-mitreattack-python

docs

cli-tools.md

collections.md

excel-export.md

index.md

navigation-layers.md

stix20-data-access.md

version-comparison.md

version-management.md

tile.json