Running locally

This guide is for two audiences: you, running the tracker on your own machine day to day, and anyone else who wants to stand up their own copy and populate it with their own samples.

These files live in docs/ as Markdown today. They become the published docs section of the Hugo site once it ships, so they are written to work both rendered on the site and read raw on GitHub.

What the pipeline does

samples/  --ingest-->  data/c2db.json  --enrich-->  whois cache + DNS
                                                        |
                                          Hugo site --> GitHub Pages
  1. ingest walks samples/, identifies the malware family, extracts the C2 configuration, and records one row per (family, indicator, sample) in data/c2db.json with first/last-seen dates.
  2. enrich queries RDAP (the JSON successor to WHOIS) and DNS for every unique indicator, caching results under data/whois/ so repeated runs are instant and registry rate limits are respected.
  3. refresh is the daily maintenance pass: re-resolve every domain and re-query Whois for entries older than 7 days. This is what catches “domain changed registrar”, “C2 went dark”, “IP reassigned to another network” โ€” the changes that are themselves intelligence.
  4. The Hugo site (separate step) renders data/c2db.json + the whois cache into the public tracker.

Setup

Requirements: Python 3.10+ and Git. Windows, macOS and Linux all work.

git clone https://github.com/<your-user>/C2-Tracker.git
cd C2-Tracker
pip install -r requirements.txt

No other services, databases or API keys are required for the core loop. RDAP and DNS are queried directly; everything else is local files.

First seen dates come from MalwareBazaar (when the sample was first seen in the wild) or from manual backfill, never from the day you ran the ingest. The Bazaar lookup needs a free abuse.ch API key; you can also set dates yourself from any source you trust (python -m tracker backfill <sha256> --first-seen YYYY-MM-DD):

  1. Create an account at https://bazaar.abuse.ch/login/ and copy your API key from https://bazaar.abuse.ch/api/.

  2. Set it as an environment variable before ingesting:

    # PowerShell (persistent, per-user)
    [Environment]::SetEnvironmentVariable("BAZAAR_AUTH_KEY", "your-key", "User")
    # Git Bash (current session)
    export BAZAAR_AUTH_KEY=your-key
    

Without the key the tracker still works โ€” dates fall back to the ingest day, and you can fix them later with python -m tracker backfill-seen.

Do not commit samples. The samples/ directory is git-ignored and must stay that way โ€” pushing malware to a public repository gets it flagged and banned. Only extracted data ever leaves your machine.

Daily workflow

# 1. drop new samples into samples/ (APK today; PE/ELF decoders land in phase 2)
python -m tracker ingest

# 2. fetch whois + DNS for anything new or stale
python -m tracker enrich

# 3. look at what you have
python -m tracker stats

ingest is safe to re-run: already-known (family, indicator, sample) combinations are recognised and merged, not duplicated.

The daily refresh (automatic)

python -m tracker refresh is designed to be scheduled:

Data layout

Path Contents Committed?
data/c2db.json one record per family/indicator/sample, with enrichment yes
data/whois/*.json cached RDAP per unique indicator yes
samples/ your malware samples never

Committing the whois cache is deliberate: the published site (and any CI run) renders entirely from committed files and never needs network access to registries.

Adding a decoder

Decoders live in tracker/decoders/<platform>/. A decoder is a class with two methods:

class MyFamilyDecoder(AndroidDecoder):
    family = "MyFamily"

    def match(self, apk) -> bool:
        # cheap family identification (package name, marker strings)

    def extract(self, apk) -> ExtractedConfig:
        # return ExtractedConfig(family=..., c2=["host:port"], ports=[...], extras={...})

Register it with one line in the platform’s __init__.py (ANDROID_DECODERS.append(MyFamilyDecoder())). Windows (RATDecoders-style) and IoT (ELF/Mirai-family) slots exist as stubs with the same interface.

Troubleshooting