"""VirusShare feed for AYN Antivirus. Downloads MD5 hash lists from VirusShare.com — one of the largest free malware hash databases. Each list contains 65,536 MD5 hashes of known malware samples (.exe, .dll, .rar, .doc, .pdf, .app, etc). https://virusshare.com/hashes """ from __future__ import annotations import logging import os from pathlib import Path from typing import Any, Dict, List, Optional import requests from ayn_antivirus.signatures.feeds.base_feed import BaseFeed logger = logging.getLogger(__name__) _BASE_URL = "https://virusshare.com/hashfiles/VirusShare_{:05d}.md5" _TIMEOUT = 30 _STATE_FILE = "/var/lib/ayn-antivirus/.virusshare_last" class VirusShareFeed(BaseFeed): """Fetch malware MD5 hashes from VirusShare. Tracks the last downloaded list number so incremental updates only fetch new lists. """ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self._last_list = self._load_state() def get_name(self) -> str: return "virusshare" def fetch(self) -> List[Dict[str, Any]]: """Fetch new hash lists since last update.""" return self.fetch_new_lists(max_lists=3) def fetch_new_lists(self, max_lists: int = 3) -> List[Dict[str, Any]]: """Download up to max_lists new VirusShare hash files.""" results: List[Dict[str, Any]] = [] start = self._last_list + 1 fetched = 0 for i in range(start, start + max_lists): self._rate_limit_wait() url = _BASE_URL.format(i) self._log("Fetching VirusShare_%05d", i) try: resp = requests.get(url, timeout=_TIMEOUT) if resp.status_code == 404: self._log("VirusShare_%05d not found — at latest", i) break resp.raise_for_status() except requests.RequestException as exc: self._error("Failed to fetch list %d: %s", i, exc) break hashes = [ line.strip() for line in resp.text.splitlines() if line.strip() and not line.startswith("#") and len(line.strip()) == 32 ] for h in hashes: results.append({ "hash": h.lower(), "threat_name": "Malware.VirusShare", "threat_type": "MALWARE", "severity": "HIGH", "source": "virusshare", "details": f"md5,list={i:05d}", }) self._last_list = i self._save_state(i) fetched += 1 self._log("VirusShare_%05d: %d hashes", i, len(hashes)) self._log("Fetched %d list(s), %d total hashes", fetched, len(results)) if results: self._mark_updated() return results def fetch_initial(self, start_list: int = 470, count: int = 11) -> List[Dict[str, Any]]: """Bulk download for initial setup.""" old = self._last_list self._last_list = start_list - 1 results = self.fetch_new_lists(max_lists=count) if not results: self._last_list = old return results @staticmethod def _load_state() -> int: try: return int(Path(_STATE_FILE).read_text().strip()) except Exception: return 480 # Default: start after list 480 @staticmethod def _save_state(n: int) -> None: try: Path(_STATE_FILE).write_text(str(n)) except Exception: pass