diff --git a/lib/cuckoo/common/cents/cents_azorult.py b/lib/cuckoo/common/cents/cents_azorult.py index c92da0f7342..c090282c9c0 100644 --- a/lib/cuckoo/common/cents/cents_azorult.py +++ b/lib/cuckoo/common/cents/cents_azorult.py @@ -3,7 +3,7 @@ log = logging.getLogger(__name__) -def cents_azorult(config_dict, sid_counter, md5): +def cents_azorult(config_dict, sid_counter, md5, date): """Creates Suricata rules from extracted Azorult malware configuration. :param config_dict: Dictionary with the extracted Azorult configuration. @@ -15,9 +15,12 @@ def cents_azorult(config_dict, sid_counter, md5): :param md5: MD5 hash of the source sample. :type md5: `int` + :param date: Timestamp of the analysis run of the source sample. + :type date: `str` + :return List of Suricata rules (`str`) or empty list if no rule has been created. """ - if not config_dict or not sid_counter or not md5: + if not config_dict or not sid_counter or not md5 or not date: return [] next_sid = sid_counter @@ -26,7 +29,8 @@ def cents_azorult(config_dict, sid_counter, md5): for address in address_list: rule = f"alert http $HOME_NET any -> $EXTERNAL_NET any (msg:\"ET MALWARE Azorult Beacon " \ f"C2 Communication - CAPE sandbox config extraction\"; flow:established,to_server; " \ - f"http.host; content:\"{address}\"; fast_pattern; reference:md5,{md5}; sid:{next_sid}; rev:1;)" + f"http.host; content:\"{address}\"; fast_pattern; reference:md5,{md5}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" next_sid += 1 rule_list.append(rule) diff --git a/lib/cuckoo/common/cents/cents_cobaltstrikebeacon.py b/lib/cuckoo/common/cents/cents_cobaltstrikebeacon.py index 849303790a7..10e1f459873 100644 --- a/lib/cuckoo/common/cents/cents_cobaltstrikebeacon.py +++ b/lib/cuckoo/common/cents/cents_cobaltstrikebeacon.py @@ -3,7 +3,7 @@ log = logging.getLogger(__name__) -def cents_cobaltstrikebeacon(config_dict, sid_counter, md5): +def cents_cobaltstrikebeacon(config_dict, sid_counter, md5, date): """Creates Suricata rules from extracted CobaltStrikeBeacon malware configuration. :param config_dict: Dictionary with the extracted CobaltStrikeBeacon configuration. @@ -15,9 +15,12 @@ def cents_cobaltstrikebeacon(config_dict, sid_counter, md5): :param md5: MD5 hash of the source sample. :type md5: `int` + :param date: Timestamp of the analysis run of the source sample. + :type date: `str` + :return List of Suricata rules (`str`) or empty list if no rule has been created. """ - if not config_dict or not sid_counter or not md5: + if not config_dict or not sid_counter or not md5 or not date: return [] next_sid = sid_counter @@ -33,7 +36,8 @@ def cents_cobaltstrikebeacon(config_dict, sid_counter, md5): # TODO make this better and differentiate between HTTP and HTTPS beacon types rule = f"alert http $HOME_NET any -> $EXTERNAL_NET {port} (msg:\"ET MALWARE CobaltStrike Beacon " \ f"C2 Communication - CAPE sandbox config extraction\"; flow:established,to_server; " \ - f"content:\"{c2}\"; fast_pattern; reference:md5,{md5}; sid:{next_sid}; rev:1;)" + f"content:\"{c2}\"; fast_pattern; reference:md5,{md5}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" next_sid += 1 rule_list.append(rule) diff --git a/lib/cuckoo/common/cents/cents_remcos.py b/lib/cuckoo/common/cents/cents_remcos.py index 84faa9ecc4a..f35c1ba4f1b 100644 --- a/lib/cuckoo/common/cents/cents_remcos.py +++ b/lib/cuckoo/common/cents/cents_remcos.py @@ -78,7 +78,7 @@ def _parse_ratdecoders(remcos_config): return remcos_config_list -def cents_remcos(config_dict, sid_counter, md5): +def cents_remcos(config_dict, sid_counter, md5, date): """Creates Suricata rules from extracted Remcos malware configuration. :param config_dict: Dictionary with the extracted Remcos configuration. @@ -90,9 +90,12 @@ def cents_remcos(config_dict, sid_counter, md5): :param md5: MD5 hash of the source sample. :type md5: `int` + :param date: Timestamp of the analysis run of the source sample. + :type date: `str` + :return List of Suricata rules (`str`) or empty list if no rule has been created. """ - if not config_dict or not sid_counter or not md5: + if not config_dict or not sid_counter or not md5 or not date: return [] next_sid = sid_counter @@ -152,7 +155,8 @@ def cents_remcos(config_dict, sid_counter, md5): for ip_group in _chunk_stuff(list(ip_list)): rule = f"alert tcp $HOME_NET any -> {ip_group} any (msg:\"ET CENTS Remcos RAT (C2 IP Address) " \ f"C2 Communication - CAPE sandbox config extraction\"; flow:established,to_server; " \ - f"reference:md5,{md5}; sid:{next_sid}; rev:1;)" + f"reference:md5,{md5}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" rule_list.append(rule) next_sid += 1 @@ -161,7 +165,8 @@ def cents_remcos(config_dict, sid_counter, md5): rule = f"alert dns $HOME_NET any -> any any (msg:\"ET CENTS Remcos RAT (C2 Domain) " \ f"C2 Communication - CAPE sandbox config extraction\"; flow:established,to_server; " \ f"dns.query; content:\"{c2_domain}\"; " \ - f"reference:md5,{md5}; sid:{next_sid}; rev:1;)" + f"reference:md5,{md5}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" rule_list.append(rule) next_sid += 1 @@ -174,7 +179,8 @@ def cents_remcos(config_dict, sid_counter, md5): f"(passphrase {parsed_config.get('Password')}) " \ f"C2 Communication - CAPE sandbox config extraction\"; flow:established,to_server; " \ f"content:\"|{first}|\"; startswith; fast_pattern; content:\"|{second}|\"; distance:2; within:2; " \ - f"reference:md5,{md5}; sid:{next_sid}; rev:1;)" + f"reference:md5,{md5}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" rule_list.append(rule) next_sid += 1 diff --git a/lib/cuckoo/common/cents/cents_squirrelwaffle.py b/lib/cuckoo/common/cents/cents_squirrelwaffle.py index 4db053c6989..f50861306a2 100644 --- a/lib/cuckoo/common/cents/cents_squirrelwaffle.py +++ b/lib/cuckoo/common/cents/cents_squirrelwaffle.py @@ -4,7 +4,7 @@ log = logging.getLogger(__name__) -def cents_squirrelwaffle(config_dict, sid_counter, md5): +def cents_squirrelwaffle(config_dict, sid_counter, md5, date): """Creates Suricata rules from extracted SquirrelWaffle malware configuration. :param config_dict: Dictionary with the extracted SquirrelWaffle configuration. @@ -16,9 +16,12 @@ def cents_squirrelwaffle(config_dict, sid_counter, md5): :param md5: MD5 hash of the source sample. :type md5: `int` + :param date: Timestamp of the analysis run of the source sample. + :type date: `str` + :return List of Suricata rules (`str`) or empty list if no rule has been created. """ - if not config_dict or not sid_counter or not md5: + if not config_dict or not sid_counter or not md5 or not date: return [] next_sid = sid_counter @@ -38,14 +41,16 @@ def cents_squirrelwaffle(config_dict, sid_counter, md5): http_rule = f"alert http $HOME_NET any -> $EXTERNAL_NET any (msg:\"ET CENTS SquirrelWaffle CnC " \ f"Activity\"; flow:established,to_server; http.method; content:\"POST\"; http.host; " \ f"content:\"{c2.hostname}\"; fast_pattern; reference:md5,{md5}; http.uri; " \ - f"content:\"{c2.path}\"; bsize:{len(c2.path)}; sid:{next_sid}; rev:1;)" + f"content:\"{c2.path}\"; bsize:{len(c2.path)}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" rule_list.append(http_rule) next_sid += 1 dns_rule = f"alert dns $HOME_NET any -> any any (msg:\"ET CENTS SquirrelWaffle CnC Domain in DNS Query\"; " \ f"dns.query; content:\"{c2.hostname}\"; fast_pattern; reference:md5,{md5}; sid:{next_sid}; rev" \ - f":1;)" + f":1; " \ + f"metadata:created_at {date};)" rule_list.append(dns_rule) next_sid += 1 diff --git a/lib/cuckoo/common/cents/cents_trickbot.py b/lib/cuckoo/common/cents/cents_trickbot.py index a358dea3141..394f5ddfaad 100644 --- a/lib/cuckoo/common/cents/cents_trickbot.py +++ b/lib/cuckoo/common/cents/cents_trickbot.py @@ -3,7 +3,7 @@ log = logging.getLogger(__name__) -def cents_trickbot(config_dict, sid_counter, md5): +def cents_trickbot(config_dict, sid_counter, md5, date): """Creates Suricata rules from extracted TrickBot malware configuration. :param config_dict: Dictionary with the extracted TrickBot configuration. @@ -15,9 +15,12 @@ def cents_trickbot(config_dict, sid_counter, md5): :param md5: MD5 hash of the source sample. :type md5: `int` + :param date: Timestamp of the analysis run of the source sample. + :type date: `str` + :return List of Suricata rules (`str`) or empty list if no rule has been created. """ - if not config_dict or not sid_counter or not md5: + if not config_dict or not sid_counter or not md5 or not date: return [] next_sid = sid_counter @@ -30,7 +33,8 @@ def cents_trickbot(config_dict, sid_counter, md5): port = s.split(":", 1)[1] rule = f"alert ip $HOME_NET any -> {ip} {port} (msg:\"ET MALWARE TrickBot Beacon (gtag {gtag}, version {ver})" \ f" C2 Communication - CAPE sandbox config extraction\"; flow:established,to_server; " \ - f"reference:md5,{md5}; sid:{next_sid}; rev:1;)" + f"reference:md5,{md5}; sid:{next_sid}; rev:1; " \ + f"metadata:created_at {date};)" next_sid += 1 rule_list.append(rule) diff --git a/modules/reporting/cents.py b/modules/reporting/cents.py index a3fed26a718..aa60ac48a60 100644 --- a/modules/reporting/cents.py +++ b/modules/reporting/cents.py @@ -4,6 +4,7 @@ from lib.cuckoo.common.config import Config from lib.cuckoo.common.abstracts import Report from lib.cuckoo.common.exceptions import CuckooReportError +from lib.cuckoo.common.utils import datetime_to_iso from lib.cuckoo.common.cents.cents_azorult import cents_azorult from lib.cuckoo.common.cents.cents_cobaltstrikebeacon import cents_cobaltstrikebeacon @@ -38,7 +39,8 @@ def run(self, results): :raise CuckooReportError: if fails to write rules file. """ rule_list = [] - md5 = results.get("target", {}).get("file", {}).get("md5", "") + md5 = results.get("target", {}).get("file", {}).get("md5", "") # md5 of the sample + date = datetime_to_iso(results.get("info", {}).get("started", "")).split("T", 1)[0].replace("-", "_") # timestamp of the sample run configs = results.get("CAPE", {}).get("configs", []) results["info"]["has_cents_rules"] = False if not configs: @@ -58,15 +60,15 @@ def run(self, results): for config_name, config_dict in config.items(): rules = None if config_name == "Azorult": - rules = cents_azorult(config_dict, self.sid_counter, md5) + rules = cents_azorult(config_dict, self.sid_counter, md5, date) elif config_name == "CobaltStrikeBeacon": - rules = cents_cobaltstrikebeacon(config_dict, self.sid_counter, md5) + rules = cents_cobaltstrikebeacon(config_dict, self.sid_counter, md5, date) elif config_name == "Remcos": - rules = cents_remcos(config_dict, self.sid_counter, md5) + rules = cents_remcos(config_dict, self.sid_counter, md5, date) elif config_name == "SquirrelWaffle": - rules = cents_squirrelwaffle(config_dict, self.sid_counter, md5) + rules = cents_squirrelwaffle(config_dict, self.sid_counter, md5, date) elif config_name == "TrickBot": - rules = cents_trickbot(config_dict, self.sid_counter, md5) + rules = cents_trickbot(config_dict, self.sid_counter, md5, date) else: # config for this family not implemented yet log.debug(f"[CENTS] Config for family {config_name} not implemented yet") @@ -80,7 +82,7 @@ def run(self, results): log.warning(f"[CENTS] Found config for {config_name}, but couldn't create rules") if not rule_list: - # no rules ahve been created + # no rules have been created return try: