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')