<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: tracking-threat-actor-infrastructure
description: >-
  Track and map threat actor infrastructure using passive DNS, WHOIS history,
  certificate transparency, JARM fingerprinting, and ASN analysis.
domain: cybersecurity
subdomain: threat-intelligence
tags:
  - infrastructure-tracking
  - passive-dns
  - whois
  - certificate-transparency
  - jarm
  - shodan
  - censys
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1583", "T1584"]
---
# Tracking Threat Actor Infrastructure

## Overview

Infrastructure tracking maps the domains, IPs, certificates, and hosting used by
threat actors for C2, staging, phishing, and exfiltration. This skill covers passive
DNS pivoting, WHOIS registrant correlation, certificate transparency monitoring,
JARM/JA3 TLS fingerprinting, and ASN clustering to identify and track adversary
infrastructure across campaigns.

## Prerequisites

| Requirement | Details |
|---|---|
| Python 3.10+ with `requests`, `shodan`, `dns.resolver` | Required |
| API access | Shodan, Censys, SecurityTrails, VirusTotal |
| Certificate transparency tools | crt.sh, Certstream |
| JARM scanner | `jarm` Python package |
| Passive DNS | DNSDB (Farsight), SecurityTrails, or RiskIQ |

## Key Concepts

### Infrastructure Types

- **Type I (Attacker-owned):** Purchased/registered by threat actor. Strongest attribution signal.
- **Type II (Compromised):** Legitimate infrastructure hijacked for operations. Common for webshells, watering holes.
- **Type III (Ephemeral):** Cloud functions, serverless, dynamic DNS. Hardest to track.

### Pivoting Hierarchy

```
Domain → WHOIS registrant → other domains by same registrant
Domain → Passive DNS → historical IPs → other domains on same IP
IP → Shodan/Censys → services/certs → JARM fingerprint → other IPs with same JARM
Domain → Certificate Transparency → other SANs on same cert
IP → ASN → other IPs in same allocation → common hosting patterns
```

### JARM Fingerprinting

JARM creates a TLS server fingerprint by sending 10 TLS Client Hello packets with
varying parameters and hashing the Server Hello responses. Same C2 framework =
same JARM hash regardless of domain/IP.

```bash
# Generate JARM fingerprint
python -m jarm evil-c2.com

# Search Shodan for matching JARM
shodan search "ssl.jarm:2ad2ad0002ad2ad00042d42d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb"
```

## Workflow

### Step 1: Passive DNS Pivoting

```python
import requests

def query_passive_dns(domain: str, api_key: str) -> list[dict]:
    """Query SecurityTrails for passive DNS history."""
    resp = requests.get(
        f"https://api.securitytrails.com/v1/domain/{domain}/dns/a",
        headers={"apikey": api_key},
    )
    if resp.status_code == 200:
        records = resp.json().get("records", [])
        return [{"ip": r["values"][0]["ip"], "first_seen": r["first_seen"],
                 "last_seen": r["last_seen"]} for r in records]
    return []

def reverse_dns(ip: str, api_key: str) -> list[str]:
    """Find all domains that resolved to an IP."""
    resp = requests.get(
        f"https://api.securitytrails.com/v1/domains/list?include_ips=false&page=1",
        headers={"apikey": api_key},
        params={"filter": json.dumps({"ipv4": ip})},
    )
    if resp.status_code == 200:
        return [r["hostname"] for r in resp.json().get("records", [])]
    return []
```

### Step 2: WHOIS Registrant Correlation

```python
def whois_lookup(domain: str, api_key: str) -> dict:
    """Get WHOIS data for domain."""
    resp = requests.get(
        f"https://api.securitytrails.com/v1/domain/{domain}/whois",
        headers={"apikey": api_key},
    )
    if resp.status_code == 200:
        data = resp.json()
        contacts = data.get("result", {}).get("contacts", [])
        return {
            "registrar": data.get("result", {}).get("registrar_name", ""),
            "created": data.get("result", {}).get("created_date", ""),
            "contacts": contacts,
            "nameservers": data.get("result", {}).get("nameservers", []),
        }
    return {}

def find_domains_by_registrant(email: str, api_key: str) -> list[str]:
    """Find other domains registered by same email."""
    resp = requests.get(
        f"https://api.securitytrails.com/v1/domains/whois/{email}",
        headers={"apikey": api_key},
    )
    if resp.status_code == 200:
        return resp.json().get("result", {}).get("items", [])
    return []
```

### Step 3: Certificate Transparency Monitoring

```python
def cert_transparency_search(domain: str) -> list[dict]:
    """Search crt.sh for certificates matching domain."""
    resp = requests.get(f"https://crt.sh/?q=%.{domain}&output=json", timeout=30)
    if resp.status_code == 200:
        certs = resp.json()
        return [
            {
                "id": c["id"],
                "common_name": c["common_name"],
                "name_value": c["name_value"],
                "issuer": c["issuer_name"],
                "not_before": c["not_before"],
                "not_after": c["not_after"],
            }
            for c in certs[:50]
        ]
    return []
```

### Step 4: Shodan Infrastructure Scanning

```python
import shodan

def scan_infrastructure(ip: str, api_key: str) -> dict:
    """Get detailed host information from Shodan."""
    api = shodan.Shodan(api_key)
    try:
        host = api.host(ip)
        return {
            "ip": ip,
            "org": host.get("org", ""),
            "asn": host.get("asn", ""),
            "ports": host.get("ports", []),
            "hostnames": host.get("hostnames", []),
            "os": host.get("os"),
            "vulns": host.get("vulns", []),
            "ssl": host.get("ssl", {}),
        }
    except shodan.APIError as e:
        return {"ip": ip, "error": str(e)}
```

### Step 5: JARM Fingerprint Clustering

```python
import subprocess
import json

def get_jarm(target: str) -> str:
    """Get JARM fingerprint for a target."""
    result = subprocess.run(
        ["python", "-m", "jarm", target], capture_output=True, text=True
    )
    return result.stdout.strip().split(",")[-1] if result.returncode == 0 else ""

def cluster_by_jarm(targets: list[str]) -> dict[str, list[str]]:
    """Cluster targets by JARM fingerprint."""
    clusters: dict[str, list[str]] = {}
    for t in targets:
        jarm = get_jarm(t)
        if jarm:
            clusters.setdefault(jarm, []).append(t)
    return clusters
```

## Detection

```yaml
title: Tracking Threat Actor Infrastructure Detection
id: 6e51e727-340a-476f-81fa-744081c7d3ea
status: experimental
description: Detects suspicious activity related to tracking threat actor infrastructure techniques in threat intelligence context
logsource:
  category: network_connection
  product: firewall
detection:
  selection:
    DestinationPort: 443
    Initiated: "true"
  condition: selection
level: medium
tags:
  - attack.t1583
  - attack.t1584
  - attack.reconnaissance
falsepositives:
  - Threat intelligence platform aggregating feeds from approved sources
```


**Detection Opportunities**

| Indicator | Source | Detection Logic |
|---|---|---|
| Tracking Threat Actor Infrastructure Detection | firewall/network_connection | Sigma rule (medium) |
| ATT&CK Coverage | MITRE ATT&CK | T1583, T1584 |

## Verification

- [ ] Passive DNS reveals historical IP-to-domain mappings
- [ ] WHOIS correlation identifies related domains by registrant
- [ ] Certificate transparency finds all SANs and related certificates
- [ ] Shodan/Censys scans enumerate services and vulnerabilities
- [ ] JARM clustering groups C2 servers by TLS fingerprint
- [ ] Infrastructure map exported with pivoting documentation

## References

- [Shodan API](https://developer.shodan.io/api)
- [SecurityTrails API](https://securitytrails.com/corp/api)
- [Censys Search](https://search.censys.io/)
- [crt.sh Certificate Transparency](https://crt.sh/)
- [JARM Fingerprinting](https://github.com/salesforce/jarm)
- [Farsight DNSDB](https://www.farsightsecurity.com/solutions/dnsdb/)
