diff --git a/content_classifier.py b/content_classifier.py index 03e1c42..6cb568e 100644 --- a/content_classifier.py +++ b/content_classifier.py @@ -84,9 +84,31 @@ class ContentClassifier: 'apt1', 'apt28', 'apt29', 'apt32', 'apt33', 'apt34', 'apt35', 'apt37', 'apt38', 'apt39', 'apt40', 'apt41', 'lazarus', 'kimsuky', 'andariel', 'fancy bear', 'cozy bear', 'sandworm', 'turla', 'equation group', 'carbanak', 'fin7', 'fin6', 'fin8', - 'conti', 'lockbit', 'blackcat', 'alphv', 'cl0p', 'clop', 'revil', 'darkside', 'nobelium', 'hafnium', 'phosphorus', 'holmium', 'strontium', - 'volt typhoon', 'flax typhoon', 'mustang panda', 'winnti' + 'volt typhoon', 'flax typhoon', 'mustang panda', 'winnti', + 'scattered spider', 'lapsus', 'lapsus$', 'unc2452', 'unc3944', + 'ta505', 'ta577', 'ta558', 'gold southfield', 'gold dupont', + } + + # Stored as display names; matched case-insensitively + MALWARE_FAMILIES = { + # Ransomware + 'LockBit', 'REvil', 'BlackCat', 'ALPHV', 'Cl0p', 'Conti', 'DarkSide', + 'Ryuk', 'BlackMatter', 'Akira', 'Black Basta', 'RansomHub', 'Rhysida', + 'Medusa', 'Cactus', 'Play', 'Royal', 'Hive', 'Maze', '8Base', + 'Hunters International', 'Inc Ransom', 'Monti', 'Nokoyawa', + # C2 frameworks / RATs + 'Cobalt Strike', 'Mimikatz', 'Sliver', 'Brute Ratel', 'Havoc', + 'AsyncRAT', 'Remcos', 'njRAT', 'NanoCore', 'XWorm', 'QuasarRAT', + 'DarkComet', 'NetWire', 'Metasploit', + # Loaders / droppers + 'Emotet', 'TrickBot', 'QakBot', 'IcedID', 'BazarLoader', 'Dridex', + 'GootLoader', 'BumbleBee', 'PikaBot', 'DarkGate', + # Stealers + 'AgentTesla', 'FormBook', 'RedLine', 'Vidar', 'Raccoon', 'Lumma', + 'Rhadamanthys', 'StealC', 'Meduza', 'Aurora', + # APT tooling + 'PlugX', 'ShadowPad', 'Gh0stRAT', 'PoisonIvy', } # MITRE ATT&CK technique pattern @@ -125,6 +147,15 @@ class ContentClassifier: found_actors.append(actor.upper()) return list(set(found_actors)) + def extract_malware_families(self, text: str) -> List[str]: + """Extract known malware family / tool names from text""" + text_lower = text.lower() + found = [] + for family in self.MALWARE_FAMILIES: + if family.lower() in text_lower: + found.append(family) + return list(set(found)) + def extract_iocs(self, text: str) -> Dict[str, List[str]]: """Extract Indicators of Compromise from text""" iocs = { @@ -285,6 +316,7 @@ class ContentClassifier: cves = self.extract_cves(combined_text) mitre_techniques = self.extract_mitre_techniques(combined_text) threat_actors = self.extract_threat_actors(combined_text) + malware_families = self.extract_malware_families(combined_text) iocs = self.extract_iocs(combined_text) # Classify content type @@ -306,13 +338,14 @@ class ContentClassifier: article['cves'] = cves article['mitre_techniques'] = mitre_techniques article['threat_actors'] = threat_actors + article['malware_families'] = malware_families article['iocs'] = iocs # Log classification logger.info( f"Classified: {article['title'][:50]}... | " f"Score: {quality_score} | Severity: {severity} | " - f"Types: {', '.join(classifications)} | CVEs: {len(cves)}" + f"CVEs: {len(cves)} | Actors: {len(threat_actors)} | Malware: {len(malware_families)}" ) return article diff --git a/rss_manager.py b/rss_manager.py index a28bb5f..37207ab 100644 --- a/rss_manager.py +++ b/rss_manager.py @@ -449,7 +449,6 @@ class RSSFeedManager: @staticmethod def format_telegram_message(article: Dict) -> Tuple[str, Optional[str]]: """Format article for Telegram message""" - # Category emoji mapping category_emojis = { 'news': '๐Ÿ“ฐ', 'malware': '๐Ÿฆ ', @@ -459,48 +458,32 @@ class RSSFeedManager: } emoji = category_emojis.get(article.get('category', ''), '๐Ÿ“ฐ') - category_display = article.get('category', '').replace('_', ' ').title() - title = escape(article.get('title', 'No Title')) description = escape(article.get('description', '')) source = escape(article.get('source', 'Unknown Source')) published_human = escape(article.get('published_human', 'Unknown')) - category_safe = escape(category_display) url = escape(article.get('url', ''), quote=True) - severity = article.get('severity', 'unknown').lower() - quality_score = article.get('quality_score') - cves = article.get('cves', [])[:3] - classifications = article.get('classifications', [])[:3] - - severity_emoji = { - 'critical': '๐Ÿšจ', - 'high': '๐Ÿ”ด', - 'medium': '๐ŸŸ ', - 'low': '๐ŸŸก', - }.get(severity, 'โšช') - severity_safe = escape(severity.upper()) + cves = article.get('cves', [])[:5] + threat_actors = article.get('threat_actors', [])[:3] + malware_families = article.get('malware_families', [])[:3] message = f"{emoji} {title}\n\n" if description: - message += f"๐Ÿ“‹ {description}\n\n" + message += f"{description}\n\n" + + message += f"๐Ÿ“ก {source} ยท {published_human}\n" - message += f"๐Ÿท๏ธ Category: {category_safe}\n" - message += f"๐Ÿ“ก Source: {source}\n" - message += f"{severity_emoji} Severity: {severity_safe}\n" - if quality_score is not None: - message += f"โญ Quality: {int(quality_score)}/100\n" - if classifications: - class_text = ", ".join(escape(c) for c in classifications) - message += f"๐Ÿง  Type: {class_text}\n" if cves: - cve_text = ", ".join(escape(cve) for cve in cves) - message += f"๐Ÿ†” CVEs: {cve_text}\n" - why_this_matters = RSSFeedManager.build_why_this_matters(article) - if why_this_matters: - message += f"๐ŸŽฏ Why This Matters: {escape(why_this_matters)}\n" - message += f"โฐ Published: {published_human}\n" - message += f"๐Ÿ”— Read Full Article" + message += f"๐Ÿ†” {', '.join(escape(c) for c in cves)}\n" + + if threat_actors: + message += f"๐Ÿ‘ค {', '.join(escape(a.title()) for a in threat_actors)}\n" + + if malware_families: + message += f"๐Ÿฆ  {', '.join(escape(f) for f in malware_families)}\n" + + message += f"\n๐Ÿ”— Read Full Article" return message, article.get('thumbnail')