"""Malware C2 / URLhaus feed (abuse.ch, Osiris port).""" from __future__ import annotations import logging from datetime import datetime, timezone from typing import Any from services.fetchers._store import _data_lock, _mark_fresh, is_any_active, latest_data from services.network_utils import fetch_with_curl logger = logging.getLogger(__name__) COUNTRY_CENTROIDS: dict[str, tuple[float, float]] = { "AF": (65, 33), "AL": (20, 41), "DZ": (3, 28), "AR": (-64, -34), "AU": (134, -25), "AT": (14, 47.5), "BE": (4, 50.8), "BR": (-51, -10), "CA": (-96, 62), "CN": (105, 35), "DE": (10, 51), "FR": (2, 46), "GB": (-2, 54), "IN": (79, 22), "IR": (53, 32), "IT": (12.5, 42.8), "JP": (138, 36), "KR": (128, 36), "MX": (-102, 23.5), "NL": (5.5, 52.5), "PL": (19.5, 52), "RU": (100, 60), "SG": (103.8, 1.35), "TW": (121, 23.7), "UA": (32, 49), "US": (-97, 38), "VN": (106, 16), } def fetch_malware_threats() -> list[dict[str, Any]]: if not is_any_active("malware_c2"): return latest_data.get("malware_threats") or [] threats: list[dict[str, Any]] = [] threat_id = 0 try: resp = fetch_with_curl( "https://feodotracker.abuse.ch/downloads/ipblocklist.json", timeout=10, headers={"User-Agent": "Shadowbroker/1.0", "Accept": "application/json"}, ) if resp.status_code == 200: entries = resp.json() if not isinstance(entries, list): entries = [] for entry in entries[:200]: cc = entry.get("country") if not cc or cc not in COUNTRY_CENTROIDS: continue lng, lat = COUNTRY_CENTROIDS[cc] j_lng = ((threat_id * 173.7) % 200 - 100) / 100 * 4 j_lat = ((threat_id * 293.1) % 200 - 100) / 100 * 4 threats.append( { "id": f"feodo-{threat_id}", "lat": lat + j_lat, "lng": lng + j_lng, "ip": entry.get("ip_address") or "unknown", "port": entry.get("dst_port") or 0, "malware": entry.get("malware") or "unknown", "status": entry.get("status") or "active", "first_seen": entry.get("first_seen"), "last_online": entry.get("last_online"), "country": cc, "threat_type": "botnet_c2", } ) threat_id += 1 except Exception as exc: logger.warning("Feodo fetch failed: %s", exc) try: resp = fetch_with_curl( "https://urlhaus-api.abuse.ch/v1/urls/recent/limit/100/", timeout=8, ) if resp.status_code == 200: urls = (resp.json() or {}).get("urls") or [] for u in urls: cc = u.get("country") if not cc or cc not in COUNTRY_CENTROIDS: cc = next(iter(COUNTRY_CENTROIDS)) lng, lat = COUNTRY_CENTROIDS[cc] j_lng = ((threat_id * 137.3) % 200 - 100) / 100 * 5 j_lat = ((threat_id * 211.7) % 200 - 100) / 100 * 5 threats.append( { "id": f"urlhaus-{threat_id}", "lat": lat + j_lat, "lng": lng + j_lng, "ip": u.get("host") or "unknown", "port": 0, "malware": ", ".join(u.get("tags") or []) or u.get("threat") or "malware", "status": u.get("url_status") or "online", "first_seen": u.get("dateadded"), "country": cc, "threat_type": "malware_url", } ) threat_id += 1 except Exception as exc: logger.debug("URLhaus supplement failed: %s", exc) payload = { "threats": threats, "total": len(threats), "timestamp": datetime.now(timezone.utc).isoformat(), "source": "abuse.ch Feodo Tracker + URLhaus", } with _data_lock: latest_data["malware_threats"] = payload _mark_fresh("malware_threats") return threats