From bd6bd96d69c489666d0de03ee748225023a57ec0 Mon Sep 17 00:00:00 2001 From: cccs-rs <62077998+cccs-rs@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:02:53 -0400 Subject: [PATCH] Create MACO extractors that wrap the CAPE extractor (#2373) * Create a AgentTesla MACO extractor that wraps the CAPE extractor * Add MACO extractors with tests * Create MACO extractors that wrap the CAPE extractor * Add maco dependency * Patch yara rule name * Update poetry lockfile * Run Ruff --- README.md | 4 +- modules/processing/parsers/MACO/AgentTesla.py | 64 ++++++ modules/processing/parsers/MACO/AsyncRAT.py | 51 +++++ .../processing/parsers/MACO/AuroraStealer.py | 26 +++ modules/processing/parsers/MACO/Azorult.py | 20 ++ .../processing/parsers/MACO/BackOffLoader.py | 31 +++ modules/processing/parsers/MACO/BackOffPOS.py | 31 +++ modules/processing/parsers/MACO/BitPaymer.py | 27 +++ .../processing/parsers/MACO/BlackDropper.py | 28 +++ modules/processing/parsers/MACO/BlackNix.py | 53 +++++ modules/processing/parsers/MACO/Blister.py | 32 +++ modules/processing/parsers/MACO/BruteRatel.py | 30 +++ modules/processing/parsers/MACO/BuerLoader.py | 24 +++ modules/processing/parsers/MACO/BumbleBee.py | 44 +++++ modules/processing/parsers/MACO/Carbanak.py | 44 +++++ modules/processing/parsers/MACO/ChChes.py | 25 +++ .../parsers/MACO/CobaltStrikeBeacon.py | 44 +++++ .../parsers/MACO/CobaltStrikeStager.py | 22 +++ modules/processing/parsers/MACO/DCRat.py | 24 +++ modules/processing/parsers/MACO/DarkGate.py | 49 +++++ .../processing/parsers/MACO/DoppelPaymer.py | 27 +++ .../processing/parsers/MACO/DridexLoader.py | 30 +++ modules/processing/parsers/MACO/Emotet.py | 29 +++ modules/processing/parsers/MACO/Enfal.py | 22 +++ modules/processing/parsers/MACO/EvilGrab.py | 37 ++++ modules/processing/parsers/MACO/Fareit.py | 25 +++ modules/processing/parsers/MACO/Formbook.py | 30 +++ modules/processing/parsers/MACO/Greame.py | 22 +++ modules/processing/parsers/MACO/GuLoader.py | 27 +++ modules/processing/parsers/MACO/Hancitor.py | 30 +++ .../processing/parsers/MACO/HttpBrowser.py | 34 ++++ modules/processing/parsers/MACO/IcedID.py | 23 +++ .../processing/parsers/MACO/IcedIDLoader.py | 31 +++ modules/processing/parsers/MACO/KoiLoader.py | 26 +++ .../processing/parsers/MACO/Latrodectus.py | 43 ++++ modules/processing/parsers/MACO/LokiBot.py | 27 +++ modules/processing/parsers/MACO/Lumma.py | 27 +++ modules/processing/parsers/MACO/NanoCore.py | 44 +++++ modules/processing/parsers/MACO/Nighthawk.py | 26 +++ modules/processing/parsers/MACO/Njrat.py | 34 ++++ modules/processing/parsers/MACO/Oyster.py | 33 ++++ modules/processing/parsers/MACO/Pandora.py | 49 +++++ .../parsers/MACO/PhemedroneStealer.py | 22 +++ modules/processing/parsers/MACO/PikaBot.py | 34 ++++ modules/processing/parsers/MACO/PlugX.py | 22 +++ modules/processing/parsers/MACO/PoisonIvy.py | 44 +++++ modules/processing/parsers/MACO/Punisher.py | 45 +++++ modules/processing/parsers/MACO/QakBot.py | 27 +++ modules/processing/parsers/MACO/QuasarRAT.py | 25 +++ modules/processing/parsers/MACO/Quickbind.py | 34 ++++ modules/processing/parsers/MACO/RCSession.py | 43 ++++ modules/processing/parsers/MACO/REvil.py | 22 +++ modules/processing/parsers/MACO/RedLeaf.py | 35 ++++ modules/processing/parsers/MACO/RedLine.py | 26 +++ modules/processing/parsers/MACO/Remcos.py | 25 +++ modules/processing/parsers/MACO/Retefe.py | 23 +++ .../processing/parsers/MACO/Rhadamanthys.py | 26 +++ modules/processing/parsers/MACO/Rozena.py | 26 +++ modules/processing/parsers/MACO/SmallNet.py | 26 +++ .../processing/parsers/MACO/SmokeLoader.py | 24 +++ .../processing/parsers/MACO/Socks5Systemz.py | 30 +++ modules/processing/parsers/MACO/SparkRAT.py | 31 +++ .../processing/parsers/MACO/SquirrelWaffle.py | 24 +++ modules/processing/parsers/MACO/Stealc.py | 24 +++ modules/processing/parsers/MACO/Strrat.py | 22 +++ modules/processing/parsers/MACO/TSCookie.py | 25 +++ modules/processing/parsers/MACO/TrickBot.py | 23 +++ modules/processing/parsers/MACO/UrsnifV3.py | 25 +++ modules/processing/parsers/MACO/VenomRat.py | 23 +++ modules/processing/parsers/MACO/WarzoneRAT.py | 27 +++ modules/processing/parsers/MACO/XWorm.py | 25 +++ modules/processing/parsers/MACO/XenoRAT.py | 25 +++ modules/processing/parsers/MACO/Zloader.py | 32 +++ modules/processing/parsers/MACO/__init__.py | 1 + modules/processing/parsers/MACO/test_maco.py | 9 + poetry.lock | 73 ++++++- pyproject.toml | 1 + tests_parsers/test_agenttesla.py | 41 ++++ tests_parsers/test_asyncrat.py | 36 ++++ tests_parsers/test_aurorastealer.py | 30 +++ tests_parsers/test_blackdropper.py | 13 ++ tests_parsers/test_bumblebee.py | 9 + tests_parsers/test_carbanak.py | 6 + tests_parsers/test_cobaltstrikebeacon.py | 62 +++++- tests_parsers/test_darkgate.py | 6 + tests_parsers/test_icedid.py | 7 + tests_parsers/test_koiloader.py | 9 + tests_parsers/test_latrodectus.py | 185 ++++++++++++++++++ tests_parsers/test_lumma.py | 28 +++ tests_parsers/test_nanocore.py | 57 ++++++ tests_parsers/test_njrat.py | 11 ++ tests_parsers/test_oyster.py | 12 ++ tests_parsers/test_pikabot.py | 83 ++++++++ tests_parsers/test_quickbind.py | 12 ++ tests_parsers/test_redline.py | 11 ++ tests_parsers/test_smokeloader.py | 6 + tests_parsers/test_sparkrat.py | 14 ++ tests_parsers/test_zloader.py | 12 ++ 98 files changed, 2943 insertions(+), 5 deletions(-) create mode 100644 modules/processing/parsers/MACO/AgentTesla.py create mode 100644 modules/processing/parsers/MACO/AsyncRAT.py create mode 100644 modules/processing/parsers/MACO/AuroraStealer.py create mode 100644 modules/processing/parsers/MACO/Azorult.py create mode 100644 modules/processing/parsers/MACO/BackOffLoader.py create mode 100644 modules/processing/parsers/MACO/BackOffPOS.py create mode 100644 modules/processing/parsers/MACO/BitPaymer.py create mode 100644 modules/processing/parsers/MACO/BlackDropper.py create mode 100644 modules/processing/parsers/MACO/BlackNix.py create mode 100644 modules/processing/parsers/MACO/Blister.py create mode 100644 modules/processing/parsers/MACO/BruteRatel.py create mode 100644 modules/processing/parsers/MACO/BuerLoader.py create mode 100644 modules/processing/parsers/MACO/BumbleBee.py create mode 100644 modules/processing/parsers/MACO/Carbanak.py create mode 100644 modules/processing/parsers/MACO/ChChes.py create mode 100644 modules/processing/parsers/MACO/CobaltStrikeBeacon.py create mode 100644 modules/processing/parsers/MACO/CobaltStrikeStager.py create mode 100644 modules/processing/parsers/MACO/DCRat.py create mode 100644 modules/processing/parsers/MACO/DarkGate.py create mode 100644 modules/processing/parsers/MACO/DoppelPaymer.py create mode 100644 modules/processing/parsers/MACO/DridexLoader.py create mode 100644 modules/processing/parsers/MACO/Emotet.py create mode 100644 modules/processing/parsers/MACO/Enfal.py create mode 100644 modules/processing/parsers/MACO/EvilGrab.py create mode 100644 modules/processing/parsers/MACO/Fareit.py create mode 100644 modules/processing/parsers/MACO/Formbook.py create mode 100644 modules/processing/parsers/MACO/Greame.py create mode 100644 modules/processing/parsers/MACO/GuLoader.py create mode 100644 modules/processing/parsers/MACO/Hancitor.py create mode 100644 modules/processing/parsers/MACO/HttpBrowser.py create mode 100644 modules/processing/parsers/MACO/IcedID.py create mode 100644 modules/processing/parsers/MACO/IcedIDLoader.py create mode 100644 modules/processing/parsers/MACO/KoiLoader.py create mode 100644 modules/processing/parsers/MACO/Latrodectus.py create mode 100644 modules/processing/parsers/MACO/LokiBot.py create mode 100644 modules/processing/parsers/MACO/Lumma.py create mode 100644 modules/processing/parsers/MACO/NanoCore.py create mode 100644 modules/processing/parsers/MACO/Nighthawk.py create mode 100644 modules/processing/parsers/MACO/Njrat.py create mode 100644 modules/processing/parsers/MACO/Oyster.py create mode 100644 modules/processing/parsers/MACO/Pandora.py create mode 100644 modules/processing/parsers/MACO/PhemedroneStealer.py create mode 100644 modules/processing/parsers/MACO/PikaBot.py create mode 100644 modules/processing/parsers/MACO/PlugX.py create mode 100644 modules/processing/parsers/MACO/PoisonIvy.py create mode 100644 modules/processing/parsers/MACO/Punisher.py create mode 100644 modules/processing/parsers/MACO/QakBot.py create mode 100644 modules/processing/parsers/MACO/QuasarRAT.py create mode 100644 modules/processing/parsers/MACO/Quickbind.py create mode 100644 modules/processing/parsers/MACO/RCSession.py create mode 100644 modules/processing/parsers/MACO/REvil.py create mode 100644 modules/processing/parsers/MACO/RedLeaf.py create mode 100644 modules/processing/parsers/MACO/RedLine.py create mode 100644 modules/processing/parsers/MACO/Remcos.py create mode 100644 modules/processing/parsers/MACO/Retefe.py create mode 100644 modules/processing/parsers/MACO/Rhadamanthys.py create mode 100644 modules/processing/parsers/MACO/Rozena.py create mode 100644 modules/processing/parsers/MACO/SmallNet.py create mode 100644 modules/processing/parsers/MACO/SmokeLoader.py create mode 100644 modules/processing/parsers/MACO/Socks5Systemz.py create mode 100644 modules/processing/parsers/MACO/SparkRAT.py create mode 100644 modules/processing/parsers/MACO/SquirrelWaffle.py create mode 100644 modules/processing/parsers/MACO/Stealc.py create mode 100644 modules/processing/parsers/MACO/Strrat.py create mode 100644 modules/processing/parsers/MACO/TSCookie.py create mode 100644 modules/processing/parsers/MACO/TrickBot.py create mode 100644 modules/processing/parsers/MACO/UrsnifV3.py create mode 100644 modules/processing/parsers/MACO/VenomRat.py create mode 100644 modules/processing/parsers/MACO/WarzoneRAT.py create mode 100644 modules/processing/parsers/MACO/XWorm.py create mode 100644 modules/processing/parsers/MACO/XenoRAT.py create mode 100644 modules/processing/parsers/MACO/Zloader.py create mode 100644 modules/processing/parsers/MACO/__init__.py create mode 100644 modules/processing/parsers/MACO/test_maco.py create mode 100644 tests_parsers/test_agenttesla.py create mode 100644 tests_parsers/test_asyncrat.py create mode 100644 tests_parsers/test_aurorastealer.py diff --git a/README.md b/README.md index 5bc6459548d..62909436d14 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Malware can be classified in CAPE via three mechanisms: ![image](https://github.com/kevoreilly/CAPEv2/assets/22219888/a44f2f8a-10df-47cc-9690-5ef08f04ea6b) -Parsing can be done using CAPE's own framework, alternatively the following frameworks are supported: [RATDecoders](https://github.com/kevthehermit/RATDecoders), [DC3-MWCP](https://github.com/Defense-Cyber-Crime-Center/DC3-MWCP) or [MalDuck](https://github.com/CERT-Polska/malduck/tree/master/malduck/) +Parsing can be done using CAPE's own framework, alternatively the following frameworks are supported: [RATDecoders](https://github.com/kevthehermit/RATDecoders), [DC3-MWCP](https://github.com/Defense-Cyber-Crime-Center/DC3-MWCP), [MalDuck](https://github.com/CERT-Polska/malduck/tree/master/malduck/), or [MaCo](https://github.com/CybercentreCanada/maco) #### Special note about config parsing frameworks: * Due to the nature of malware, since it changes constantly when any new version is released, something might become broken! @@ -228,5 +228,3 @@ If you use CAPEv2 in your work, please cite it as specified in the "Cite this re ### Docs * [ReadTheDocs](https://capev2.readthedocs.io/en/latest/#) - - diff --git a/modules/processing/parsers/MACO/AgentTesla.py b/modules/processing/parsers/MACO/AgentTesla.py new file mode 100644 index 00000000000..4bc6e7349ce --- /dev/null +++ b/modules/processing/parsers/MACO/AgentTesla.py @@ -0,0 +1,64 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.AgentTesla import extract_config + +def convert_to_MACO(raw_config: dict) -> MACOModel: + if not raw_config: + return + + protocol = raw_config.get('Protocol') + if not protocol: + return + + parsed_result = MACOModel(family="AgentTesla", other=raw_config) + if protocol == "Telegram": + parsed_result.http.append( + MACOModel.Http(uri=raw_config["C2"], + password=raw_config["Password"], + usage="c2") + ) + + elif protocol in ["HTTP(S)", "Discord"]: + parsed_result.http.append( + MACOModel.Http(uri=raw_config["C2"], + usage="c2") + ) + + elif protocol == "FTP": + parsed_result.ftp.append( + MACOModel.FTP(username=raw_config["Username"], + password=raw_config["Password"], + hostname=raw_config["C2"].replace('ftp://', ''), + usage="c2") + ) + + elif protocol == "SMTP": + smtp = dict(username=raw_config["Username"], + password=raw_config["Password"], + hostname=raw_config["C2"], + mail_to=[raw_config["EmailTo"]], + usage="c2") + if "Port" in raw_config: + smtp["port"] = raw_config["Port"] + parsed_result.smtp.append(MACOModel.SMTP(**smtp)) + + if "Persistence_Filename" in raw_config: + parsed_result.paths.append(MACOModel.Path(path=raw_config["Persistence_Filename"], usage="storage")) + + if "ExternalIPCheckServices" in raw_config: + for service in raw_config["ExternalIPCheckServices"]: + parsed_result.http.append(MACOModel.Http(uri=service, usage="other")) + + + return parsed_result + +class AgentTesla(Extractor): + author = "kevoreilly" + family = "AgentTesla" + last_modified = "2024-10-20" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/AsyncRAT.py b/modules/processing/parsers/MACO/AsyncRAT.py new file mode 100644 index 00000000000..a36cf01f13b --- /dev/null +++ b/modules/processing/parsers/MACO/AsyncRAT.py @@ -0,0 +1,51 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.AsyncRAT import extract_config + +def convert_to_MACO(raw_config: dict) -> MACOModel: + if not raw_config: + return + + parsed_result = MACOModel(family="AsyncRAT", other=raw_config) + + # Mutex + parsed_result.mutex.append(raw_config["Mutex"]) + + # Version + parsed_result.version = raw_config["Version"] + + # Was persistence enabled? + if raw_config['Install'] == 'true': + parsed_result.capability_enabled.append('persistence') + else: + parsed_result.capability_disabled.append('persistence') + + # Installation Path + if raw_config.get('Folder'): + parsed_result.paths.append(MACOModel.Path(path=os.path.join(raw_config['Folder'], raw_config['Filename']), + usage="install")) + + # C2s + for i in range(len(raw_config.get('C2s', []))): + parsed_result.http.append(MACOModel.Http(hostname=raw_config["C2s"][i], + port=int(raw_config["Ports"][i]), + usage="c2")) + # Pastebin + if raw_config.get("Pastebin") not in ["null", None]: + # TODO: Is it used to download the C2 information if not embedded? + # Ref: https://www.netskope.com/blog/asyncrat-using-fully-undetected-downloader + parsed_result.http.append(MACOModel.Http(uri=raw_config["Pastebin"], + usage="download")) + + return parsed_result + +class AsyncRAT(Extractor): + author = "kevoreilly" + family = "AsyncRAT" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/AuroraStealer.py b/modules/processing/parsers/MACO/AuroraStealer.py new file mode 100644 index 00000000000..7825c32441a --- /dev/null +++ b/modules/processing/parsers/MACO/AuroraStealer.py @@ -0,0 +1,26 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.AuroraStealer import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="AuroraStealer", other=raw_config) + if raw_config.get('C2'): + # IP related to C2 + parsed_result.http.append(MACOModel.Http(hostname=raw_config['C2'], + usage="c2")) + + return parsed_result + +class AuroraStealer(Extractor): + author = "kevoreilly" + family = "AuroraStealer" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Azorult.py b/modules/processing/parsers/MACO/Azorult.py new file mode 100644 index 00000000000..0fb3db5b07b --- /dev/null +++ b/modules/processing/parsers/MACO/Azorult.py @@ -0,0 +1,20 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Azorult import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + return MACOModel(family="Azorult", http=[MACOModel.Http(hostname=raw_config["address"])], other=raw_config) + + +class Azorult(Extractor): + author = "kevoreilly" + family = "Azorult" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BackOffLoader.py b/modules/processing/parsers/MACO/BackOffLoader.py new file mode 100644 index 00000000000..5266ff34501 --- /dev/null +++ b/modules/processing/parsers/MACO/BackOffLoader.py @@ -0,0 +1,31 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BackOffLoader import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BackOffLoader", other=raw_config) + + # Version + parsed_result.version = raw_config['Version'] + + # Encryption details + parsed_result.encryption.append(MACOModel.Encryption(algorithm="rc4", + key=raw_config['EncryptionKey'], + seed=raw_config['RC4Seed'])) + for url in raw_config['URLs']: + parsed_result.http.append(MACOModel.Http(url=url)) + + return parsed_result + + +class BackOffLoader(Extractor): + author = "kevoreilly" + family = "BackOffLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BackOffPOS.py b/modules/processing/parsers/MACO/BackOffPOS.py new file mode 100644 index 00000000000..21817e93676 --- /dev/null +++ b/modules/processing/parsers/MACO/BackOffPOS.py @@ -0,0 +1,31 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BackOffPOS import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BackOffPOS", other=raw_config) + + # Version + parsed_result.version = raw_config['Version'] + + # Encryption details + parsed_result.encryption.append(MACOModel.Encryption(algorithm="rc4", + key=raw_config['EncryptionKey'], + seed=raw_config['RC4Seed'])) + for url in raw_config['URLs']: + parsed_result.http.append(MACOModel.Http(url=url)) + + return parsed_result + + +class BackOffPOS(Extractor): + author = "kevoreilly" + family = "BackOffPOS" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BitPaymer.py b/modules/processing/parsers/MACO/BitPaymer.py new file mode 100644 index 00000000000..8786a57a6b0 --- /dev/null +++ b/modules/processing/parsers/MACO/BitPaymer.py @@ -0,0 +1,27 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BitPaymer import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BitPaymer", other=raw_config) + + # Extracted strings + parsed_result.decoded_strings = raw_config["strings"] + + # Encryption details + parsed_result.encryption.append(MACOModel.Encryption(algorithm="rsa", + public_key=raw_config['RSA public key'])) + return parsed_result + +class BitPaymer(Extractor): + author = "kevoreilly" + family = "BitPaymer" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BlackDropper.py b/modules/processing/parsers/MACO/BlackDropper.py new file mode 100644 index 00000000000..395992b1c28 --- /dev/null +++ b/modules/processing/parsers/MACO/BlackDropper.py @@ -0,0 +1,28 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BlackDropper import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BlackDropper", campaign_id=[raw_config["campaign"]], other=raw_config) + + for dir in raw_config.get("directories", []): + parsed_result.paths.append(MACOModel.Path(path=dir)) + + for url in raw_config.get("urls", []): + parsed_result.http.append(MACOModel.Http(uri=url)) + + return parsed_result + +class BlackDropper(Extractor): + author = "kevoreilly" + family = "BlackDropper" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BlackNix.py b/modules/processing/parsers/MACO/BlackNix.py new file mode 100644 index 00000000000..4750ec83cdf --- /dev/null +++ b/modules/processing/parsers/MACO/BlackNix.py @@ -0,0 +1,53 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BlackNix import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BlackNix", other=raw_config) + + # Mutex + parsed_result.mutex.append(raw_config['Mutex']) + + # Capabilities that are enabled/disabled + # TODO: Review if these are all capabilities set by a boolean flag + for capa in ["Anti Sandboxie", "Kernel Mode Unhooking", "User Mode Unhooking", + "Melt Server", "Offline Screen Capture", "Offline Keylogger", "Copy to ADS", + "Safe Mode Startup", "Inject winlogon.exe", "Active X Run", "Registry Run"]: + if raw_config[capa].lower() == "true": + parsed_result.capability_enabled.append(capa) + else: + parsed_result.capability_disabled.append(capa) + + # Delay Time + parsed_result.sleep_delay = raw_config["Delay Time"] + + # Password + parsed_result.password.append(raw_config["Password"]) + + # C2 Domain + parsed_result.http.append(MACOModel.Http(hostname=raw_config['Domain'], + usage="c2")) + # Registry + parsed_result.registry.append(MACOModel.Registry(key=raw_config["Registry Key"])) + + # Install Path + parsed_result.paths.append(MACOModel.Path(path=os.path.join(raw_config['Install Path'], raw_config["Install Name"]), + usage="install")) + + # Campaign Group/Name + parsed_result.campaign_id = [raw_config["Campaign Name"], raw_config["Campaign Group"]] + return parsed_result + + +class BlackNix(Extractor): + author = "kevoreilly" + family = "BlackNix" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Blister.py b/modules/processing/parsers/MACO/Blister.py new file mode 100644 index 00000000000..e865ab7cfc3 --- /dev/null +++ b/modules/processing/parsers/MACO/Blister.py @@ -0,0 +1,32 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Blister import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Blister", other=raw_config) + + for capa in ["Persistence", "Sleep after injection"]: + if raw_config[capa]: + parsed_result.capability_enabled.append(capa) + else: + parsed_result.capability_disabled.append(capa) + + # Rabbit encryption + parsed_result.encryption.append(MACOModel.Encryption(algorithm="rabbit", + key=raw_config["Rabbit key"], + iv=raw_config["Rabbit IV"])) + return parsed_result + +class Blister(Extractor): + author = "kevoreilly" + family = "Blister" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BruteRatel.py b/modules/processing/parsers/MACO/BruteRatel.py new file mode 100644 index 00000000000..4b8edcfbc23 --- /dev/null +++ b/modules/processing/parsers/MACO/BruteRatel.py @@ -0,0 +1,30 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BruteRatel import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BruteRatel", other=raw_config) + + for url in raw_config["C2"]: + for path in raw_config["URI"]: + parsed_result.http.append( + MACOModel.Http(uri=url, user_agent=raw_config["User Agent"], port=raw_config["Port"], path=path, usage="c2") + ) + + return parsed_result + + +class BruteRatel(Extractor): + author = "kevoreilly" + family = "BruteRatel" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BuerLoader.py b/modules/processing/parsers/MACO/BuerLoader.py new file mode 100644 index 00000000000..f3e9241af0a --- /dev/null +++ b/modules/processing/parsers/MACO/BuerLoader.py @@ -0,0 +1,24 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BuerLoader import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BuerLoader", other=raw_config) + + for c2 in raw_config["address"]: + parsed_result.http.append(MACOModel.Http(hostname=c2, usage="c2")) + return parsed_result + +class BuerLoader(Extractor): + author = "kevoreilly" + family = "BuerLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/BumbleBee.py b/modules/processing/parsers/MACO/BumbleBee.py new file mode 100644 index 00000000000..07f693c083f --- /dev/null +++ b/modules/processing/parsers/MACO/BumbleBee.py @@ -0,0 +1,44 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.BumbleBee import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="BumbleBee", other=raw_config) + + # Campaign ID + if raw_config.get("Campaign ID"): + parsed_result.campaign_id.append(raw_config["Campaign ID"]) + + # Botnet ID + if raw_config.get("Botnet ID"): + parsed_result.identifier.append(raw_config["Botnet ID"]) + + # C2s + for c2 in raw_config.get("C2s", []): + parsed_result.http.append(MACOModel.Http(hostname=c2, + usage="c2")) + + # Data + if raw_config.get("Data"): + parsed_result.binaries.append(MACOModel.Binary(data=raw_config["Data"])) + + # RC4 Key + if raw_config.get("RC4 Key"): + parsed_result.encryption.append(MACOModel.Encryption(algorithm="rc4", + key=raw_config["RC4 Key"])) + + return parsed_result + +class BumbleBee(Extractor): + author = "kevoreilly" + family = "BumbleBee" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Carbanak.py b/modules/processing/parsers/MACO/Carbanak.py new file mode 100644 index 00000000000..55ddff8fe5e --- /dev/null +++ b/modules/processing/parsers/MACO/Carbanak.py @@ -0,0 +1,44 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Carbanak import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Carbanak", other=raw_config) + + # Version + if raw_config.get("Version"): + parsed_result.version = raw_config["Version"] + + # Unknown strings + for i in [1, 2]: + if raw_config.get(f"Unknown {i}"): + parsed_result.decoded_strings.append(raw_config[f"Unknown {i}"]) + + # C2 + if raw_config.get("C2"): + if isinstance(raw_config["C2"], str): + parsed_result.http.append(MACOModel.Http(hostname=raw_config["C2"], + usage="c2")) + else: + for c2 in raw_config["C2"]: + parsed_result.http.append(MACOModel.Http(hostname=c2, + usage="c2")) + + # Campaign Id + if raw_config.get("Campaign Id"): + parsed_result.campaign_id.append(raw_config["Campaign Id"]) + + return parsed_result + +class Carbanak(Extractor): + author = "kevoreilly" + family = "Carbanak" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/ChChes.py b/modules/processing/parsers/MACO/ChChes.py new file mode 100644 index 00000000000..464b9f2faa2 --- /dev/null +++ b/modules/processing/parsers/MACO/ChChes.py @@ -0,0 +1,25 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.ChChes import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="ChChes", other=raw_config) + + # C2 URLs + for c2_url in raw_config.get("c2_url", []): + parsed_result.http.append(MACOModel.Http(uri=c2_url, usage="c2")) + + return parsed_result + +class ChChes(Extractor): + author = "kevoreilly" + family = "ChChes" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/CobaltStrikeBeacon.py b/modules/processing/parsers/MACO/CobaltStrikeBeacon.py new file mode 100644 index 00000000000..86aa4f29e92 --- /dev/null +++ b/modules/processing/parsers/MACO/CobaltStrikeBeacon.py @@ -0,0 +1,44 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.CobaltStrikeBeacon import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="CobaltStrikeBeacon", other=raw_config) + + clean_config = {k: v for k, v in raw_config.items() if v != "Not Found"} + capabilities = {k[1:]: clean_config.pop(k) for k in list(clean_config.keys()) if clean_config[k] in ["True", "False"]} + + for capability, enabled in capabilities.items(): + if enabled.lower() == "true": + parsed_result.capability_enabled.append(capability) + else: + parsed_result.capability_disabled.append(capability) + + if "C2Server" in clean_config: + host, get_path = clean_config.pop("C2Server").split(',') + port = clean_config.pop("Port") + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, method="GET", path=get_path, usage="c2")) + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, method="POST", path=clean_config.pop("HttpPostUri"), usage="c2")) + + parsed_result.sleep_delay = clean_config.pop("SleepTime") + parsed_result.sleep_delay_jitter = clean_config.pop("Jitter") + + for path_key in ["Spawnto_x86", "Spawnto_x64"]: + if path_key in clean_config: + parsed_result.paths.append(MACOModel.Path(path=clean_config.pop(path_key))) + + return parsed_result + +class CobaltStrikeBeacon(Extractor): + author = "kevoreilly" + family = "CobaltStrikeBeacon" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/CobaltStrikeStager.py b/modules/processing/parsers/MACO/CobaltStrikeStager.py new file mode 100644 index 00000000000..9f88e95f0f8 --- /dev/null +++ b/modules/processing/parsers/MACO/CobaltStrikeStager.py @@ -0,0 +1,22 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.CobaltStrikeStager import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="CobaltStrikeStager", other=raw_config) + + return parsed_result + +class CobaltStrikeStager(Extractor): + author = "kevoreilly" + family = "CobaltStrikeStager" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/DCRat.py b/modules/processing/parsers/MACO/DCRat.py new file mode 100644 index 00000000000..214b3cfb0de --- /dev/null +++ b/modules/processing/parsers/MACO/DCRat.py @@ -0,0 +1,24 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.DCRat import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + # TODO: Assign fields to MACO model + parsed_result = MACOModel(family="DCRat", other=raw_config) + + return parsed_result + +class DCRat(Extractor): + author = "kevoreilly" + family = "DCRat" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/DarkGate.py b/modules/processing/parsers/MACO/DarkGate.py new file mode 100644 index 00000000000..e4697b422da --- /dev/null +++ b/modules/processing/parsers/MACO/DarkGate.py @@ -0,0 +1,49 @@ +import os +from copy import deepcopy + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.DarkGate import extract_config + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="DarkGate", other=raw_config) + + # Create a copy of the raw configuration for parsing + config = deepcopy(raw_config) + + # Go through capabilities/settings that are boolean in nature + for k, v in list(config.items()): + if v not in ['Yes', 'No']: + continue + + if v == 'Yes': + parsed_result.capability_enabled.append(k) + else: + parsed_result.capability_disabled.append(k) + + # Remove key from raw config + config.pop(k) + + # C2 + c2_port = config.pop('c2_port', None) + for c2_url in config.pop("C2", []): + parsed_result.http.append(MACOModel.Http(uri=c2_url, port=c2_port, usage="c2")) + + # Mutex + if config.get('internal_mutex'): + parsed_result.mutex.append(config.pop('internal_mutex')) + + return parsed_result + +class DarkGate(Extractor): + author = "kevoreilly" + family = "DarkGate" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/DoppelPaymer.py b/modules/processing/parsers/MACO/DoppelPaymer.py new file mode 100644 index 00000000000..78bb7e965eb --- /dev/null +++ b/modules/processing/parsers/MACO/DoppelPaymer.py @@ -0,0 +1,27 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.DoppelPaymer import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="DoppelPaymer") + + if "strings" in raw_config: + parsed_result.decoded_strings = raw_config["strings"] + + if "RSA public key" in raw_config: + parsed_result.encryption.append(MACOModel.Encryption(algorithm="RSA", public_key=raw_config["RSA public key"])) + + return parsed_result + +class DoppelPaymer(Extractor): + author = "kevoreilly" + family = "DoppelPaymer" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/DridexLoader.py b/modules/processing/parsers/MACO/DridexLoader.py new file mode 100644 index 00000000000..3a165ee5641 --- /dev/null +++ b/modules/processing/parsers/MACO/DridexLoader.py @@ -0,0 +1,30 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.DridexLoader import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="DridexLoader", other=raw_config) + + for c2_address in raw_config.get("address", []): + parsed_result.http.append(MACOModel.Http(uri=c2_address, usage="c2")) + + if "RC4 key" in raw_config: + parsed_result.encryption.append(MACOModel.Encryption(algorithm="RC4", key=raw_config["RC4 key"])) + + if "Botnet ID" in raw_config: + parsed_result.identifier.append(raw_config["Botnet ID"]) + + return parsed_result + +class DridexLoader(Extractor): + author = "kevoreilly" + family = "DridexLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Emotet.py b/modules/processing/parsers/MACO/Emotet.py new file mode 100644 index 00000000000..960ce9e0270 --- /dev/null +++ b/modules/processing/parsers/MACO/Emotet.py @@ -0,0 +1,29 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Emotet import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Emotet", other=raw_config) + + for c2_address in raw_config.get("address", []): + parsed_result.http.append(MACOModel.Http(uri=c2_address, usage="c2")) + + if "RC4 public key" in raw_config: + parsed_result.encryption.append(MACOModel.Encryption(algorithm="RC4", public_key=raw_config["RSA public key"])) + + parsed_result.other = {k: raw_config[k] for k in raw_config.keys() if k not in ["address", "RSA public key"] } + + return parsed_result + +class Emotet(Extractor): + author = "kevoreilly" + family = "Emotet" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Enfal.py b/modules/processing/parsers/MACO/Enfal.py new file mode 100644 index 00000000000..5996968b4b3 --- /dev/null +++ b/modules/processing/parsers/MACO/Enfal.py @@ -0,0 +1,22 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Enfal import extract_config, rule_source + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + # TODO: Assign fields to MACO model + parsed_result = MACOModel(family="Enfal", other=raw_config) + + return parsed_result + +class Enfal(Extractor): + author = "kevoreilly" + family = "Enfal" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/EvilGrab.py b/modules/processing/parsers/MACO/EvilGrab.py new file mode 100644 index 00000000000..69d5e19bd8e --- /dev/null +++ b/modules/processing/parsers/MACO/EvilGrab.py @@ -0,0 +1,37 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.EvilGrab import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="EvilGrab", other=raw_config) + + if "mutex" in raw_config: + parsed_result.mutex.append(raw_config["mutex"]) + + if "missionid" in raw_config: + parsed_result.campaign_id.append(raw_config["missionid"]) + + if "version" in raw_config: + parsed_result.version = raw_config["version"] + + if "c2_address" in raw_config: + parsed_result.http.append( + parsed_result.Http(uri=raw_config["c2_address"], port=raw_config["port"][0] if "port" in raw_config else None) + ) + + return parsed_result + + +class EvilGrab(Extractor): + author = "kevoreilly" + family = "EvilGrab" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Fareit.py b/modules/processing/parsers/MACO/Fareit.py new file mode 100644 index 00000000000..3f6ca981c82 --- /dev/null +++ b/modules/processing/parsers/MACO/Fareit.py @@ -0,0 +1,25 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Fareit import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + # TODO: Assign fields to MACO model + parsed_result = MACOModel(family="Fareit", other=raw_config) + + return parsed_result + + +class Fareit(Extractor): + author = "kevoreilly" + family = "Fareit" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Formbook.py b/modules/processing/parsers/MACO/Formbook.py new file mode 100644 index 00000000000..52fec228f90 --- /dev/null +++ b/modules/processing/parsers/MACO/Formbook.py @@ -0,0 +1,30 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Formbook import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Formbook", other=raw_config) + + if "C2" in raw_config: + parsed_result.http.append(MACOModel.Http(uri=raw_config["C2"], usage="c2")) + + for decoy in raw_config.get("Decoys", []): + parsed_result.http.append(MACOModel.Http(uri=decoy, usage="decoy")) + + return parsed_result + + +class Formbook(Extractor): + author = "kevoreilly" + family = "Formbook" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Greame.py b/modules/processing/parsers/MACO/Greame.py new file mode 100644 index 00000000000..c2c82bb5de7 --- /dev/null +++ b/modules/processing/parsers/MACO/Greame.py @@ -0,0 +1,22 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Greame import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Greame", other=raw_config) + + return parsed_result + + +class Greame(Extractor): + author = "kevoreilly" + family = "Greame" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/GuLoader.py b/modules/processing/parsers/MACO/GuLoader.py new file mode 100644 index 00000000000..d6cba02a4ee --- /dev/null +++ b/modules/processing/parsers/MACO/GuLoader.py @@ -0,0 +1,27 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.GuLoader import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="GuLoader", other=raw_config) + + for url in raw_config.get("URLs", []): + parsed_result.http.append(MACOModel.Http(uri=url, usage="download")) + + return parsed_result + + +class GuLoader(Extractor): + author = "kevoreilly" + family = "GuLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], "data/yara/CAPE/Guloader.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Hancitor.py b/modules/processing/parsers/MACO/Hancitor.py new file mode 100644 index 00000000000..8d45cb53ae6 --- /dev/null +++ b/modules/processing/parsers/MACO/Hancitor.py @@ -0,0 +1,30 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Hancitor import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Hancitor", other=raw_config) + + for url in raw_config.get("address", []): + parsed_result.http.append(MACOModel.Http(uri=url, usage="c2")) + + if "Build ID" in raw_config: + parsed_result.identifier.append(raw_config["Build ID"]) + + return parsed_result + + +class Hancitor(Extractor): + author = "kevoreilly" + family = "Hancitor" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/HttpBrowser.py b/modules/processing/parsers/MACO/HttpBrowser.py new file mode 100644 index 00000000000..298997d5f81 --- /dev/null +++ b/modules/processing/parsers/MACO/HttpBrowser.py @@ -0,0 +1,34 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.HttpBrowser import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="HttpBrowser", other=raw_config) + + port = raw_config["port"][0] if "port" in raw_config else None + + if "c2_address" in raw_config: + parsed_result.http.append(MACOModel.Http(uri=raw_config["c2_address"], port=port, usage="c2")) + + if "filepath" in raw_config: + parsed_result.paths.append(MACOModel.Path(path=raw_config["filepath"])) + + if "injectionprocess" in raw_config: + parsed_result["injectionprocess"] = raw_config["injectionprocess"] + + return parsed_result + + +class HttpBrowser(Extractor): + author = "kevoreilly" + family = "HttpBrowser" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/IcedID.py b/modules/processing/parsers/MACO/IcedID.py new file mode 100644 index 00000000000..1b355f78867 --- /dev/null +++ b/modules/processing/parsers/MACO/IcedID.py @@ -0,0 +1,23 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.IcedID import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + return MACOModel(**raw_config) + + +class IcedID(Extractor): + author = "kevoreilly" + family = "IcedID" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/IcedIDLoader.py b/modules/processing/parsers/MACO/IcedIDLoader.py new file mode 100644 index 00000000000..ae85fbf8083 --- /dev/null +++ b/modules/processing/parsers/MACO/IcedIDLoader.py @@ -0,0 +1,31 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.IcedIDLoader import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="IcedIDLoader", other=raw_config) + + if "C2" in raw_config: + parsed_result.http.append(MACOModel.Http(hostname=raw_config["C2"], usage="c2")) + + if "Campaign" in raw_config: + parsed_result.campaign_id.append(str(raw_config["Campaign"])) + + return parsed_result + + +class IcedIDLoader(Extractor): + author = "kevoreilly" + family = "IcedIDLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/KoiLoader.py b/modules/processing/parsers/MACO/KoiLoader.py new file mode 100644 index 00000000000..3841f4342fe --- /dev/null +++ b/modules/processing/parsers/MACO/KoiLoader.py @@ -0,0 +1,26 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.KoiLoader import extract_config, RULE_SOURCE + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="KoiLoader", other=raw_config) + + for c2_url in raw_config.get("C2", []): + parsed_result.http.append(MACOModel.Http(uri=c2_url, usage="c2")) + + return parsed_result + + +class KoiLoader(Extractor): + author = "kevoreilly" + family = "KoiLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = RULE_SOURCE + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Latrodectus.py b/modules/processing/parsers/MACO/Latrodectus.py new file mode 100644 index 00000000000..d0483106858 --- /dev/null +++ b/modules/processing/parsers/MACO/Latrodectus.py @@ -0,0 +1,43 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Latrodectus import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Latrodectus", other=raw_config) + + for c2_url in raw_config.get("C2", []): + parsed_result.http.append(MACOModel.Http(uri=c2_url, usage="c2")) + + if "Group name" in raw_config: + parsed_result.identifier.append(raw_config["Group name"]) + + if "Campaign ID" in raw_config: + parsed_result.campaign_id.append(str(raw_config["Campaign ID"])) + + if "Version" in raw_config: + parsed_result.version = raw_config["Version"] + + if "RC4 key" in raw_config: + parsed_result.encryption.append(MACOModel.Encryption(algorithm="RC4", key=raw_config["RC4 key"])) + + if "Strings" in raw_config: + parsed_result.decoded_strings = raw_config["Strings"] + + return parsed_result + + +class Latrodectus(Extractor): + author = "kevoreilly" + family = "Latrodectus" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/LokiBot.py b/modules/processing/parsers/MACO/LokiBot.py new file mode 100644 index 00000000000..6a4f3196acf --- /dev/null +++ b/modules/processing/parsers/MACO/LokiBot.py @@ -0,0 +1,27 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.LokiBot import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="LokiBot", other=raw_config) + + for address in raw_config.get("address", []): + parsed_result.http.append(MACOModel.Http(uri=address)) + + return parsed_result + + +class LokiBot(Extractor): + author = "kevoreilly" + family = "LokiBot" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Lumma.py b/modules/processing/parsers/MACO/Lumma.py new file mode 100644 index 00000000000..16c8a37c056 --- /dev/null +++ b/modules/processing/parsers/MACO/Lumma.py @@ -0,0 +1,27 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Lumma import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Lumma", other=raw_config) + + for address in raw_config.get("C2", []): + parsed_result.http.append(MACOModel.Http(hostname=address, usage="c2")) + + return parsed_result + + +class Lumma(Extractor): + author = "kevoreilly" + family = "Lumma" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/NanoCore.py b/modules/processing/parsers/MACO/NanoCore.py new file mode 100644 index 00000000000..05c17620ee4 --- /dev/null +++ b/modules/processing/parsers/MACO/NanoCore.py @@ -0,0 +1,44 @@ +from copy import deepcopy + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.NanoCore import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + + parsed_result = MACOModel(family="NanoCore", other=raw_config) + + config_copy = deepcopy(raw_config) + capabilities = {k: config_copy.pop(k) for k in list(config_copy.keys()) if config_copy[k] in ["True", "False"]} + + if "Version" in config_copy: + parsed_result.version = config_copy.pop("Version") + + if "Mutex" in config_copy: + parsed_result.mutex.append(config_copy.pop("Mutex")) + + for capability, enabled in capabilities.items(): + if enabled.lower() == "true": + parsed_result.capability_enabled.append(capability) + else: + parsed_result.capability_disabled.append(capability) + + for address in config_copy.pop("cncs", []): + host, port = address.split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, usage="c2")) + + return parsed_result + + +class NanoCore(Extractor): + author = "kevoreilly" + family = "NanoCore" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Nighthawk.py b/modules/processing/parsers/MACO/Nighthawk.py new file mode 100644 index 00000000000..4d45e8de950 --- /dev/null +++ b/modules/processing/parsers/MACO/Nighthawk.py @@ -0,0 +1,26 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Nighthawk import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + + parsed_result = MACOModel(family="Nighthawk", other=raw_config) + + return parsed_result + + +class Nighthawk(Extractor): + author = "kevoreilly" + family = "Nighthawk" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Njrat.py b/modules/processing/parsers/MACO/Njrat.py new file mode 100644 index 00000000000..404bf100599 --- /dev/null +++ b/modules/processing/parsers/MACO/Njrat.py @@ -0,0 +1,34 @@ + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Njrat import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + + parsed_result = MACOModel(family="Njrat", other=raw_config) + + if "version" in raw_config: + parsed_result.version = raw_config["version"] + + if "campaign_id" in raw_config: + parsed_result.campaign_id.append(raw_config["campaign_id"]) + + for c2 in raw_config.get("cncs", []): + host, port = c2.split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, usage="c2")) + + return parsed_result + + +class Njrat(Extractor): + author = "kevoreilly" + family = "Njrat" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Oyster.py b/modules/processing/parsers/MACO/Oyster.py new file mode 100644 index 00000000000..7ff072ae745 --- /dev/null +++ b/modules/processing/parsers/MACO/Oyster.py @@ -0,0 +1,33 @@ +import os +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Oyster import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Oyster", other=raw_config) + + for address in raw_config.get("C2", []): + parsed_result.http.append(MACOModel.Http(uri=address, usage="c2")) + + if "Dll Version" in raw_config: + parsed_result.version = raw_config["Dll Version"] + + if "Strings" in raw_config: + parsed_result.decoded_strings = raw_config["Strings"] + + return parsed_result + + +class Oyster(Extractor): + author = "kevoreilly" + family = "Oyster" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Pandora.py b/modules/processing/parsers/MACO/Pandora.py new file mode 100644 index 00000000000..cd745f75750 --- /dev/null +++ b/modules/processing/parsers/MACO/Pandora.py @@ -0,0 +1,49 @@ +import os +from copy import deepcopy + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Pandora import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + config_copy = deepcopy(raw_config) + parsed_result = MACOModel( + family="Pandora", + mutex=[config_copy.pop("Mutex")], + campaign_id=[config_copy.pop("Campaign ID")], + version=config_copy.pop("Version"), + http=[dict(hostname=config_copy.pop("Domain"), port=config_copy.pop("Port"), password=config_copy.pop("Password"))], + other=raw_config + ) + + parsed_result.paths.append( + MACOModel.Path(path=os.path.join(config_copy.pop("Install Path"), config_copy.pop("Install Name")), usage="install") + ) + + parsed_result.registry.append(MACOModel.Registry(key=config_copy.pop("HKCU Key"))) + parsed_result.registry.append(MACOModel.Registry(key=config_copy.pop("ActiveX Key"))) + + for field in list(config_copy.keys()): + # TODO: Unsure what's the value of the remaining fields + if config_copy[field].lower() in ["true", "false"]: + enabled = config_copy.pop(field).lower() == "true" + if enabled: + parsed_result.capability_enabled.append(field) + else: + parsed_result.capability_disabled.append(field) + + return parsed_result + + +class Pandora(Extractor): + author = "kevoreilly" + family = "Pandora" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/PhemedroneStealer.py b/modules/processing/parsers/MACO/PhemedroneStealer.py new file mode 100644 index 00000000000..b7797c7edb8 --- /dev/null +++ b/modules/processing/parsers/MACO/PhemedroneStealer.py @@ -0,0 +1,22 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.PhemedroneStealer import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="PhemedroneStealer", other=raw_config) + + return parsed_result + + +class PhemedroneStealer(Extractor): + author = "kevoreilly" + family = "PhemedroneStealer" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/PikaBot.py b/modules/processing/parsers/MACO/PikaBot.py new file mode 100644 index 00000000000..ae8a423fb54 --- /dev/null +++ b/modules/processing/parsers/MACO/PikaBot.py @@ -0,0 +1,34 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.PikaBot import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="PikaBot", other=raw_config) + + if "C2" in raw_config: + [parsed_result.http.append(MACOModel.Http(uri=c2, usage="c2")) for c2 in raw_config["C2"]] + parsed_result.binaries.append(MACOModel.Binary(datatype="payload", data=raw_config["Powershell"])) + elif "C2s" in raw_config: + parsed_result.version = raw_config["Version"] + parsed_result.campaign_id.append(raw_config["Campaign Name"]) + parsed_result.registry.append(MACOModel.Registry(key=raw_config["Registry Key"])) + for c2 in raw_config["C2s"]: + host, port = c2.split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, user_agent=raw_config["User Agent"])) + + return parsed_result + + +class PikaBot(Extractor): + author = "kevoreilly" + family = "PikaBot" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/PlugX.py b/modules/processing/parsers/MACO/PlugX.py new file mode 100644 index 00000000000..87239f9e115 --- /dev/null +++ b/modules/processing/parsers/MACO/PlugX.py @@ -0,0 +1,22 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.PlugX import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="PlugX", other=raw_config) + + return parsed_result + + +class PlugX(Extractor): + author = "kevoreilly" + family = "PlugX" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/PoisonIvy.py b/modules/processing/parsers/MACO/PoisonIvy.py new file mode 100644 index 00000000000..079f526aac5 --- /dev/null +++ b/modules/processing/parsers/MACO/PoisonIvy.py @@ -0,0 +1,44 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.PoisonIvy import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="PoisonIvy", other=raw_config) + + if "Campaign ID" in raw_config: + parsed_result.campaign_id.append(raw_config["Campaign ID"]) + if "Group ID" in raw_config: + parsed_result.identifier.append(raw_config["Group ID"]) + if "Domains" in raw_config: + for domain_port in raw_config["Domains"].split('|'): + host, port = domain_port.split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port)) + if "Password" in raw_config: + parsed_result.password.append(raw_config["Password"]) + if "Mutex" in raw_config: + parsed_result.mutex.append(raw_config["Mutex"]) + + for field in list(raw_config.keys()): + value = raw_config[field] + if value.lower() == "true": + parsed_result.capability_enabled.append(field) + elif value.lower() == "false": + parsed_result.capability_disabled.append(field) + + return parsed_result + + +class PoisonIvy(Extractor): + author = "kevoreilly" + family = "PoisonIvy" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + output = extract_config(stream.read()) + if output: + return convert_to_MACO(output[0]) diff --git a/modules/processing/parsers/MACO/Punisher.py b/modules/processing/parsers/MACO/Punisher.py new file mode 100644 index 00000000000..2f32dccb34c --- /dev/null +++ b/modules/processing/parsers/MACO/Punisher.py @@ -0,0 +1,45 @@ +import os +from copy import deepcopy + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Punisher import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + config_copy = deepcopy(raw_config) + parsed_result = MACOModel( + family="Punisher", + campaign_id=config_copy["Campaign Name"], + password=[config_copy["Password"]], + registry=[MACOModel.Registry(key=config_copy["Registry Key"])], + paths=[MACOModel.Path(path=os.path.join(config_copy["Install Path"], config_copy["Install Name"]))], + http=[MACOModel.Http(hostname=config_copy["Domain"], port=config_copy["Port"])], + other=raw_config + ) + + for field in raw_config.keys(): + value = raw_config[field] + if value.lower() == "true": + parsed_result.capability_enabled.append(field) + elif value.lower() == "false": + parsed_result.capability_disabled.append(field) + else: + parsed_result.other[field] = value + + return parsed_result + + +class Punisher(Extractor): + author = "kevoreilly" + family = "Punisher" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + output = extract_config(stream.read()) + if output: + return convert_to_MACO(output[0]) diff --git a/modules/processing/parsers/MACO/QakBot.py b/modules/processing/parsers/MACO/QakBot.py new file mode 100644 index 00000000000..c8f83926cb0 --- /dev/null +++ b/modules/processing/parsers/MACO/QakBot.py @@ -0,0 +1,27 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.QakBot import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="QakBot", other=raw_config) + + for address in raw_config.get("address", []) + raw_config.get("C2s", []): + host, port = address.split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, usage="c2")) + + return parsed_result + + +class QakBot(Extractor): + author = "kevoreilly" + family = "QakBot" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/QuasarRAT.py b/modules/processing/parsers/MACO/QuasarRAT.py new file mode 100644 index 00000000000..50301847706 --- /dev/null +++ b/modules/processing/parsers/MACO/QuasarRAT.py @@ -0,0 +1,25 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.QuasarRAT import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="QuasarRAT", other=raw_config) + + return parsed_result + + +class QuasarRAT(Extractor): + author = "kevoreilly" + family = "QuasarRAT" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Quickbind.py b/modules/processing/parsers/MACO/Quickbind.py new file mode 100644 index 00000000000..49f8580320b --- /dev/null +++ b/modules/processing/parsers/MACO/Quickbind.py @@ -0,0 +1,34 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Quickbind import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Quickbind", other=raw_config) + + if "Mutex" in raw_config: + parsed_result.mutex = raw_config["Mutex"] + + for c2 in raw_config.get("C2", []): + parsed_result.http.append(MACOModel.Http(hostname=c2, usage="c2")) + + if "Encryption Key" in raw_config: + parsed_result.encryption.append(MACOModel.Encryption(key=raw_config["Encryption Key"])) + + return parsed_result + + +class Quickbind(Extractor): + author = "kevoreilly" + family = "Quickbind" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/RCSession.py b/modules/processing/parsers/MACO/RCSession.py new file mode 100644 index 00000000000..451d12b78ed --- /dev/null +++ b/modules/processing/parsers/MACO/RCSession.py @@ -0,0 +1,43 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.RCSession import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="RCSession", other=raw_config) + + for address in raw_config.get("c2_address", []): + parsed_result.http.append(MACOModel.Http(hostname=address, usage="c2")) + + if "directory" in raw_config: + parsed_result.paths.append(MACOModel.Path(path=raw_config["directory"], usage="install")) + + service = {} + + if "servicename" in raw_config: + service["name"] = raw_config["servicename"] + if "servicedisplayname" in raw_config: + service["display_name"] = raw_config["servicedisplayname"] + if "servicedescription" in raw_config: + service["description"] = raw_config["servicedescription"] + if "filename" in raw_config: + service["dll"] = raw_config["filename"] + + if service: + parsed_result.service.append(MACOModel.Service(**service)) + + return parsed_result + + +class RCSession(Extractor): + author = "kevoreilly" + family = "RCSession" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/REvil.py b/modules/processing/parsers/MACO/REvil.py new file mode 100644 index 00000000000..d6268c2ef03 --- /dev/null +++ b/modules/processing/parsers/MACO/REvil.py @@ -0,0 +1,22 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.REvil import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="REvil", other=raw_config) + + return parsed_result + + +class REvil(Extractor): + author = "kevoreilly" + family = "REvil" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/RedLeaf.py b/modules/processing/parsers/MACO/RedLeaf.py new file mode 100644 index 00000000000..82537136a05 --- /dev/null +++ b/modules/processing/parsers/MACO/RedLeaf.py @@ -0,0 +1,35 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.RedLeaf import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="RedLeaf", other=raw_config) + + for address in raw_config.get("c2_address", []): + parsed_result.http.append(MACOModel.Http(hostname=address, usage="c2")) + + if "missionid" in raw_config: + parsed_result.campaign_id.append(raw_config["missionid"]) + + if "mutex" in raw_config: + parsed_result.mutex.append(raw_config["mutex"]) + + if "key" in raw_config: + parsed_result.other["key"] = raw_config["key"] + + return parsed_result + + +class RedLeaf(Extractor): + author = "kevoreilly" + family = "RedLeaf" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/RedLine.py b/modules/processing/parsers/MACO/RedLine.py new file mode 100644 index 00000000000..d150529ca91 --- /dev/null +++ b/modules/processing/parsers/MACO/RedLine.py @@ -0,0 +1,26 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.RedLine import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="RedLine", other=raw_config) + + if "C2" in raw_config: + host, port = raw_config["C2"].split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, usage="c2")) + + return parsed_result + + +class RedLine(Extractor): + author = "kevoreilly" + family = "RedLine" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Remcos.py b/modules/processing/parsers/MACO/Remcos.py new file mode 100644 index 00000000000..6bd750006c9 --- /dev/null +++ b/modules/processing/parsers/MACO/Remcos.py @@ -0,0 +1,25 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Remcos import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Remcos", other=raw_config) + + return parsed_result + + +class Remcos(Extractor): + author = "kevoreilly" + family = "Remcos" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Retefe.py b/modules/processing/parsers/MACO/Retefe.py new file mode 100644 index 00000000000..6eb25cd834b --- /dev/null +++ b/modules/processing/parsers/MACO/Retefe.py @@ -0,0 +1,23 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Retefe import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Retefe", other=raw_config) + + return parsed_result + + +class Retefe(Extractor): + author = "kevoreilly" + family = "Retefe" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Rhadamanthys.py b/modules/processing/parsers/MACO/Rhadamanthys.py new file mode 100644 index 00000000000..05053168d03 --- /dev/null +++ b/modules/processing/parsers/MACO/Rhadamanthys.py @@ -0,0 +1,26 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Rhadamanthys import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Rhadamanthys", other=raw_config) + parsed_result.http = [MACOModel.Http(hostname=raw_config["C2"], usage="c2")] + + return parsed_result + + +class Rhadamanthys(Extractor): + author = "kevoreilly" + family = "Rhadamanthys" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Rozena.py b/modules/processing/parsers/MACO/Rozena.py new file mode 100644 index 00000000000..29a5e676d3a --- /dev/null +++ b/modules/processing/parsers/MACO/Rozena.py @@ -0,0 +1,26 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Rozena import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Rozena", other=raw_config) + parsed_result.http = [MACOModel.Http(hostname=raw_config["C2"], port=raw_config["Port"], usage="c2")] + + return parsed_result + + +class Rozena(Extractor): + author = "kevoreilly" + family = "Rozena" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split('/modules', 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/SmallNet.py b/modules/processing/parsers/MACO/SmallNet.py new file mode 100644 index 00000000000..8c3e5018fda --- /dev/null +++ b/modules/processing/parsers/MACO/SmallNet.py @@ -0,0 +1,26 @@ + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.SmallNet import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="SmallNet", other=raw_config) + + return parsed_result + + +class SmallNet(Extractor): + author = "kevoreilly" + family = "SmallNet" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + output = extract_config(stream.read()) + if output: + config = output if isinstance(output, dict) else output[0] + return convert_to_MACO(config) diff --git a/modules/processing/parsers/MACO/SmokeLoader.py b/modules/processing/parsers/MACO/SmokeLoader.py new file mode 100644 index 00000000000..bdcc63768e2 --- /dev/null +++ b/modules/processing/parsers/MACO/SmokeLoader.py @@ -0,0 +1,24 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.SmokeLoader import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="SmokeLoader", other=raw_config, + http=[MACOModel.Http(uri=c2, usage="c2") for c2 in raw_config["C2s"]]) + + return parsed_result + + +class SmokeLoader(Extractor): + author = "kevoreilly" + family = "SmokeLoader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Socks5Systemz.py b/modules/processing/parsers/MACO/Socks5Systemz.py new file mode 100644 index 00000000000..7d3c2c0c6fc --- /dev/null +++ b/modules/processing/parsers/MACO/Socks5Systemz.py @@ -0,0 +1,30 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Socks5Systemz import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel( + family="Socks5Systemz", + other=raw_config, + http=[MACOModel.Http(hostname=c2, usage="c2") for c2 in raw_config.get("C2s", [])] + + [MACOModel.Http(hostname=decoy, usage="decoy") for decoy in raw_config.get("Dummy domain", [])], + ) + + return parsed_result + + +class Socks5Systemz(Extractor): + author = "kevoreilly" + family = "Socks5Systemz" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/SparkRAT.py b/modules/processing/parsers/MACO/SparkRAT.py new file mode 100644 index 00000000000..5140d3e2c38 --- /dev/null +++ b/modules/processing/parsers/MACO/SparkRAT.py @@ -0,0 +1,31 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.SparkRAT import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="SparkRAT", other=raw_config) + + url = f"http{'s' if raw_config['secure'] else ''}://{raw_config['host']}:{raw_config['port']}{raw_config['path']}" + + parsed_result.http.append(MACOModel.Http(uri=url, hostname=raw_config["host"], port=raw_config["port"], path=raw_config["path"])) + + parsed_result.identifier.append(raw_config["uuid"]) + + return parsed_result + + +class SparkRAT(Extractor): + author = "kevoreilly" + family = "SparkRAT" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/SquirrelWaffle.py b/modules/processing/parsers/MACO/SquirrelWaffle.py new file mode 100644 index 00000000000..8bede861803 --- /dev/null +++ b/modules/processing/parsers/MACO/SquirrelWaffle.py @@ -0,0 +1,24 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.SquirrelWaffle import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="SquirrelWaffle", other=raw_config, + http=[MACOModel.Http(uri=c2, usage="c2") for c2 in raw_config["URLs"]]) + + return parsed_result + + +class SquirrelWaffle(Extractor): + author = "kevoreilly" + family = "SquirrelWaffle" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Stealc.py b/modules/processing/parsers/MACO/Stealc.py new file mode 100644 index 00000000000..91916f77975 --- /dev/null +++ b/modules/processing/parsers/MACO/Stealc.py @@ -0,0 +1,24 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Stealc import extract_config, RULE_SOURCE + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Stealc", other=raw_config, + http=[MACOModel.Http(uri=c2, usage="c2") for c2 in raw_config["C2"]]) + + return parsed_result + + +class Stealc(Extractor): + author = "kevoreilly" + family = "Stealc" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = RULE_SOURCE + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Strrat.py b/modules/processing/parsers/MACO/Strrat.py new file mode 100644 index 00000000000..8c76bc83618 --- /dev/null +++ b/modules/processing/parsers/MACO/Strrat.py @@ -0,0 +1,22 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Strrat import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Strrat", other=raw_config) + + return parsed_result + + +class Strrat(Extractor): + author = "kevoreilly" + family = "Strrat" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/TSCookie.py b/modules/processing/parsers/MACO/TSCookie.py new file mode 100644 index 00000000000..a71fe849a82 --- /dev/null +++ b/modules/processing/parsers/MACO/TSCookie.py @@ -0,0 +1,25 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.TSCookie import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="TSCookie", other=raw_config) + + return parsed_result + + +class TSCookie(Extractor): + author = "kevoreilly" + family = "TSCookie" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/TrickBot.py b/modules/processing/parsers/MACO/TrickBot.py new file mode 100644 index 00000000000..e227e38e76d --- /dev/null +++ b/modules/processing/parsers/MACO/TrickBot.py @@ -0,0 +1,23 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.TrickBot import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="TrickBot", other=raw_config) + + return parsed_result + + +class TrickBot(Extractor): + author = "kevoreilly" + family = "TrickBot" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/UrsnifV3.py b/modules/processing/parsers/MACO/UrsnifV3.py new file mode 100644 index 00000000000..999034bf8e6 --- /dev/null +++ b/modules/processing/parsers/MACO/UrsnifV3.py @@ -0,0 +1,25 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.UrsnifV3 import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="UrsnifV3", other=raw_config) + + return parsed_result + + +class UrsnifV3(Extractor): + author = "kevoreilly" + family = "UrsnifV3" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/VenomRat.py b/modules/processing/parsers/MACO/VenomRat.py new file mode 100644 index 00000000000..406a49b9aa4 --- /dev/null +++ b/modules/processing/parsers/MACO/VenomRat.py @@ -0,0 +1,23 @@ + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.VenomRAT import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="VenomRAT", other=raw_config) + + return parsed_result + + +class VenomRAT(Extractor): + author = "kevoreilly" + family = "VenomRAT" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/WarzoneRAT.py b/modules/processing/parsers/MACO/WarzoneRAT.py new file mode 100644 index 00000000000..548885ceada --- /dev/null +++ b/modules/processing/parsers/MACO/WarzoneRAT.py @@ -0,0 +1,27 @@ + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.WarzoneRAT import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="WarzoneRAT", other=raw_config) + + if "C2" in raw_config: + host, port = raw_config["C2"].split(':') + parsed_result.http.append(MACOModel.Http(hostname=host, port=port, usage="c2")) + + return parsed_result + + +class WarzoneRAT(Extractor): + author = "kevoreilly" + family = "WarzoneRAT" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/XWorm.py b/modules/processing/parsers/MACO/XWorm.py new file mode 100644 index 00000000000..27abc58c9c2 --- /dev/null +++ b/modules/processing/parsers/MACO/XWorm.py @@ -0,0 +1,25 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.XWorm import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="XWorm", other=raw_config) + + return parsed_result + + +class XWorm(Extractor): + author = "kevoreilly" + family = "XWorm" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/XenoRAT.py b/modules/processing/parsers/MACO/XenoRAT.py new file mode 100644 index 00000000000..d4a93cc32e7 --- /dev/null +++ b/modules/processing/parsers/MACO/XenoRAT.py @@ -0,0 +1,25 @@ +import os + +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.XenoRAT import extract_config + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="XenoRAT", other=raw_config) + + return parsed_result + + +class XenoRAT(Extractor): + author = "kevoreilly" + family = "XenoRAT" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = open(os.path.join(os.path.dirname(__file__).split("/modules", 1)[0], f"data/yara/CAPE/{family}.yar")).read() + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/Zloader.py b/modules/processing/parsers/MACO/Zloader.py new file mode 100644 index 00000000000..43b12c15f92 --- /dev/null +++ b/modules/processing/parsers/MACO/Zloader.py @@ -0,0 +1,32 @@ +from maco.model import ExtractorModel as MACOModel +from maco.extractor import Extractor +from modules.processing.parsers.CAPE.Zloader import extract_config, rule_source + + +def convert_to_MACO(raw_config: dict): + if not raw_config: + return None + + parsed_result = MACOModel(family="Zloader", other=raw_config) + + if "Campaign ID" in raw_config: + parsed_result.campaign_id = [raw_config["Campaign ID"]] + + if "RC4 key" in raw_config: + parsed_result.encryption = [MACOModel.Encryption(algorithm="RC4", key=raw_config[:"RC4 key"])] + + for address in raw_config.get("address", []): + parsed_result.http.append(MACOModel.Http(uri=address)) + + return parsed_result + + +class Zloader(Extractor): + author = "kevoreilly" + family = "Zloader" + last_modified = "2024-10-26" + sharing = "TLP:CLEAR" + yara_rule = rule_source + + def run(self, stream, matches): + return convert_to_MACO(extract_config(stream.read())) diff --git a/modules/processing/parsers/MACO/__init__.py b/modules/processing/parsers/MACO/__init__.py new file mode 100644 index 00000000000..f39e5e8d683 --- /dev/null +++ b/modules/processing/parsers/MACO/__init__.py @@ -0,0 +1 @@ +# Init diff --git a/modules/processing/parsers/MACO/test_maco.py b/modules/processing/parsers/MACO/test_maco.py new file mode 100644 index 00000000000..b294bd5ba5e --- /dev/null +++ b/modules/processing/parsers/MACO/test_maco.py @@ -0,0 +1,9 @@ +from maco.extractor import Extractor + +class Test(Extractor): + author = "test" + family = "test" + last_modified = "2024-10-20" + + def run(self, stream, matches): + pass diff --git a/poetry.lock b/poetry.lock index 5450ebc85d2..c47b98b49da 100644 --- a/poetry.lock +++ b/poetry.lock @@ -275,6 +275,19 @@ files = [ {file = "capstone-4.0.2.tar.gz", hash = "sha256:2842913092c9b69fd903744bc1b87488e1451625460baac173056e1808ec1c66"}, ] +[[package]] +name = "cart" +version = "1.2.2" +description = "CaRT Neutering format" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cart-1.2.2-py2.py3-none-any.whl", hash = "sha256:c111398038683c85d3edcadaa3b16183461907bdb613e05cbb60d381f2886309"}, +] + +[package.dependencies] +pycryptodome = "*" + [[package]] name = "certifi" version = "2024.7.4" @@ -1579,6 +1592,23 @@ files = [ {file = "LnkParse3-1.2.0.tar.gz", hash = "sha256:102b2aba6c2896127cb719f814a8579210368f9277fd5ec0d0151fe070166e1d"}, ] +[[package]] +name = "maco" +version = "1.1.8" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "maco-1.1.8-py3-none-any.whl", hash = "sha256:ab2d1d8e846c0abc455d16f718ba71dda5492ddc22533484156090aa4439fb06"}, + {file = "maco-1.1.8.tar.gz", hash = "sha256:e0985efdf645d3c55e3d4d4f2bf40b8d2260fa4add608bb8e8fdefba0500cb4a"}, +] + +[package.dependencies] +cart = "*" +pydantic = ">=2.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +yara-python = "*" + [[package]] name = "mako" version = "1.3.5" @@ -2452,6 +2482,47 @@ files = [ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +[[package]] +name = "pycryptodome" +version = "3.21.0" +description = "Cryptographic library for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c"}, + {file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297"}, +] + [[package]] name = "pycryptodomex" version = "3.20.0" @@ -4541,4 +4612,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.10, <4.0" -content-hash = "ba6ac8df00c9c33e24ff9946fbc9285619d931904a334b60969533d9bce85551" +content-hash = "d8e5cf52c678477800f15c8cbc7b3aa9a888a98e0cab0c8f4c3e18dd7b30e705" diff --git a/pyproject.toml b/pyproject.toml index ceda1f9f6ba..418cbb9aff9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ ruff = "0.0.290" paramiko = "3.4.0" psutil = "5.9.8" # peepdf-3 = "4.0.0" +maco = "1.1.8" Werkzeug = "3.0.6" packaging = "23.1" diff --git a/tests_parsers/test_agenttesla.py b/tests_parsers/test_agenttesla.py new file mode 100644 index 00000000000..5390edb9b70 --- /dev/null +++ b/tests_parsers/test_agenttesla.py @@ -0,0 +1,41 @@ +from modules.processing.parsers.CAPE.AgentTesla import extract_config +from modules.processing.parsers.MACO.AgentTesla import convert_to_MACO + + +def test_agenttesla(): + # AgentTeslaV5 + with open("tests/data/malware/893f4dc8f8a1dcee05a0840988cf90bc93c1cda5b414f35a6adb5e9f40678ce9", "rb") as data: + conf = extract_config(data.read()) + assert conf == { + "Protocol": "SMTP", + "C2": "mail.guestequipment.com.au", + "Username": "sendlog@guestequipment.com.au", + "Password": "Clone89!", + "EmailTo": "info@marethon.com", + "Persistence_Filename": "newfile.exe", + "ExternalIPCheckServices": ["http://ip-api.com/line/?fields=hosting"], + } + + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "AgentTesla", + "other": { + "Protocol": "SMTP", + "C2": "mail.guestequipment.com.au", + "Username": "sendlog@guestequipment.com.au", + "Password": "Clone89!", + "EmailTo": "info@marethon.com", + "Persistence_Filename": "newfile.exe", + "ExternalIPCheckServices": ["http://ip-api.com/line/?fields=hosting"], + }, + "smtp": [ + { + "username": "sendlog@guestequipment.com.au", + "password": "Clone89!", + "hostname": "mail.guestequipment.com.au", + "mail_to": ["info@marethon.com"], + "usage": "c2", + } + ], + "http": [{"uri": "http://ip-api.com/line/?fields=hosting", "usage": "other"}], + "paths": [{"path": "newfile.exe", "usage": "storage"}], + } diff --git a/tests_parsers/test_asyncrat.py b/tests_parsers/test_asyncrat.py new file mode 100644 index 00000000000..376e711751b --- /dev/null +++ b/tests_parsers/test_asyncrat.py @@ -0,0 +1,36 @@ +from modules.processing.parsers.CAPE.AsyncRAT import extract_config +from modules.processing.parsers.MACO.AsyncRAT import convert_to_MACO + + +def test_asyncrat(): + with open("tests/data/malware/f08b325f5322a698e14f97db29d322e9ee91ad636ac688af352d51057fc56526", "rb") as data: + conf = extract_config(data.read()) + assert conf == { + "C2s": ["todfg.duckdns.org"], + "Ports": "6745", + "Version": "0.5.7B", + "Folder": "%AppData%", + "Filename": "updateee.exe", + "Install": "false", + "Mutex": "AsyncMutex_6SI8OkPnk", + "Pastebin": "null", + } + + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "AsyncRAT", + "version": "0.5.7B", + "capability_disabled": ["persistence"], + "mutex": ["AsyncMutex_6SI8OkPnk"], + "other": { + "C2s": ["todfg.duckdns.org"], + "Ports": "6745", + "Version": "0.5.7B", + "Folder": "%AppData%", + "Filename": "updateee.exe", + "Install": "false", + "Mutex": "AsyncMutex_6SI8OkPnk", + "Pastebin": "null", + }, + "http": [{"hostname": "todfg.duckdns.org", "port": 6, "usage": "c2"}], + "paths": [{"path": "%AppData%/updateee.exe", "usage": "install"}], + } diff --git a/tests_parsers/test_aurorastealer.py b/tests_parsers/test_aurorastealer.py new file mode 100644 index 00000000000..53af23b3f8c --- /dev/null +++ b/tests_parsers/test_aurorastealer.py @@ -0,0 +1,30 @@ +from modules.processing.parsers.CAPE.AuroraStealer import extract_config +from modules.processing.parsers.MACO.AuroraStealer import convert_to_MACO + + +def test_aurorastealer(): + with open("tests/data/malware/8da8821d410b94a2811ce7ae80e901d7e150ad3420d677b158e45324a6606ac4", "rb") as data: + conf = extract_config(data.read()) + assert conf == { + "BuildID": "x64pump", + "MD5Hash": "f29f33b296b35ec5e7fc3ee784ef68ee", + "C2": "77.91.85.73", + "Architecture": "X64", + "BuildGroup": "x64pump", + "BuildAccept": "0", + "Date": "2023-04-06 19", + } + + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "AuroraStealer", + "other": { + "BuildID": "x64pump", + "MD5Hash": "f29f33b296b35ec5e7fc3ee784ef68ee", + "C2": "77.91.85.73", + "Architecture": "X64", + "BuildGroup": "x64pump", + "BuildAccept": "0", + "Date": "2023-04-06 19", + }, + "http": [{"hostname": "77.91.85.73", "usage": "c2"}], + } diff --git a/tests_parsers/test_blackdropper.py b/tests_parsers/test_blackdropper.py index 7237dd53cd6..7e4dc59d067 100644 --- a/tests_parsers/test_blackdropper.py +++ b/tests_parsers/test_blackdropper.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.BlackDropper import extract_config +from modules.processing.parsers.MACO.BlackDropper import convert_to_MACO def test_blackdropper(): @@ -13,3 +14,15 @@ def test_blackdropper(): "directories": ["\\Music\\dkcydqtwjv"], "campaign": "oFwQ0aQ3v", } + + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "BlackDropper", + "campaign_id": ["oFwQ0aQ3v"], + "other": { + "urls": ["http://72.5.42.222:8568/api/dll/", "http://72.5.42.222:8568/api/fileZip"], + "directories": ["\\Music\\dkcydqtwjv"], + "campaign": "oFwQ0aQ3v", + }, + "http": [{"uri": "http://72.5.42.222:8568/api/dll/"}, {"uri": "http://72.5.42.222:8568/api/fileZip"}], + "paths": [{"path": "\\Music\\dkcydqtwjv"}], + } diff --git a/tests_parsers/test_bumblebee.py b/tests_parsers/test_bumblebee.py index 88aba1604a5..fd06f12ee10 100644 --- a/tests_parsers/test_bumblebee.py +++ b/tests_parsers/test_bumblebee.py @@ -3,9 +3,18 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.BumbleBee import extract_config +from modules.processing.parsers.MACO.BumbleBee import convert_to_MACO def test_bumblebee(): with open("tests/data/malware/f8a6eddcec59934c42ea254cdd942fb62917b5898f71f0feeae6826ba4f3470d", "rb") as data: conf = extract_config(data.read()) assert conf == {"Botnet ID": "YTBSBbNTWU", "Campaign ID": "1904r", "Data": "XNgHUGLrCD", "C2s": ["444"]} + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "BumbleBee", + "campaign_id": ["1904r"], + "identifier": ["YTBSBbNTWU"], + "other": {"Botnet ID": "YTBSBbNTWU", "Campaign ID": "1904r", "Data": "XNgHUGLrCD", "C2s": ["444"]}, + "binaries": [{"data": "XNgHUGLrCD"}], + "http": [{"hostname": "444", "usage": "c2"}], + } diff --git a/tests_parsers/test_carbanak.py b/tests_parsers/test_carbanak.py index 8460665d911..8d5169fea96 100644 --- a/tests_parsers/test_carbanak.py +++ b/tests_parsers/test_carbanak.py @@ -1,7 +1,13 @@ from modules.processing.parsers.CAPE.Carbanak import extract_config +from modules.processing.parsers.MACO.Carbanak import convert_to_MACO def test_carbanak(): with open("tests/data/malware/c9c1b06cb9c9bd6fc4451f5e2847a1f9524bb2870d7bb6f0ee09b9dd4e3e4c84", "rb") as data: conf = extract_config(data.read()) assert conf["C2"] == ["5.161.223.210:443", "207.174.30.226:443"] + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Carbanak", + "other": {"C2": ["5.161.223.210:443", "207.174.30.226:443"]}, + "http": [{"hostname": "5.161.223.210:443", "usage": "c2"}, {"hostname": "207.174.30.226:443", "usage": "c2"}], + } diff --git a/tests_parsers/test_cobaltstrikebeacon.py b/tests_parsers/test_cobaltstrikebeacon.py index 8505e402035..47c26163b54 100644 --- a/tests_parsers/test_cobaltstrikebeacon.py +++ b/tests_parsers/test_cobaltstrikebeacon.py @@ -3,12 +3,13 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.CobaltStrikeBeacon import extract_config +from modules.processing.parsers.MACO.CobaltStrikeBeacon import convert_to_MACO def test_csb(): with open("tests/data/malware/2588fd3232138f587e294aea5cc9a0611d1e165b199743552c84bfddc1e4c063", "rb") as data: conf = extract_config(data.read()) - assert conf == { + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { "BeaconType": ["HTTP"], "Port": 4848, "SleepTime": 60000, @@ -55,3 +56,62 @@ def test_csb(): "bUsesCookies": "True", "HostHeader": "", } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "CobaltStrikeBeacon", + "capability_enabled": ["ProcInject_StartRWX", "ProcInject_UseRWX", "UsesCookies"], + "capability_disabled": ["StageCleanup", "CFGCaution"], + "sleep_delay": 60000, + "sleep_delay_jitter": 0, + "other": { + "BeaconType": ["HTTP"], + "Port": 4848, + "SleepTime": 60000, + "MaxGetSize": 1048576, + "Jitter": 0, + "MaxDNS": "Not Found", + "PublicKey": "30819f300d06092a864886f70d010101050003818d0030818902818100bebe41805d3c15a738caf3e308a992d4d507ce827996a8c9d783c766963e7e73083111729ae0abc1b49af0bcf803efdcaf83ac694fb53d043a88e9333f169e026a3c4e63cc6d4cd1aa5e199cb95eec500f948ac472c0ab2eda385d35fb8592d74b1154a1c671afb310eccb0b139ee1100907bfcdd8dfbf3385803a11bc252995020301000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "C2Server": "192.144.206.100,/load", + "UserAgent": "Not Found", + "HttpPostUri": "/submit.php", + "Malleable_C2_Instructions": [], + "HttpGet_Metadata": "Not Found", + "HttpPost_Metadata": "Not Found", + "SpawnTo": "d7a9ca15a07f82bfd3b63020da38aa16", + "PipeName": "Not Found", + "DNS_Idle": "Not Found", + "DNS_Sleep": "Not Found", + "SSH_Host": "Not Found", + "SSH_Port": "Not Found", + "SSH_Username": "Not Found", + "SSH_Password_Plaintext": "Not Found", + "SSH_Password_Pubkey": "Not Found", + "HttpGet_Verb": "GET", + "HttpPost_Verb": "POST", + "HttpPostChunk": 0, + "Spawnto_x86": "%windir%\\syswow64\\rundll32.exe", + "Spawnto_x64": "%windir%\\sysnative\\rundll32.exe", + "CryptoScheme": 0, + "Proxy_Config": "Not Found", + "Proxy_User": "Not Found", + "Proxy_Password": "Not Found", + "Proxy_Behavior": "Use IE settings", + "Watermark": 391144938, + "bStageCleanup": "False", + "bCFGCaution": "False", + "KillDate": 0, + "bProcInject_StartRWX": "True", + "bProcInject_UseRWX": "True", + "bProcInject_MinAllocSize": 0, + "ProcInject_PrependAppend_x86": "Empty", + "ProcInject_PrependAppend_x64": "Empty", + "ProcInject_Execute": ["CreateThread", "SetThreadContext", "CreateRemoteThread", "RtlCreateUserThread"], + "ProcInject_AllocationMethod": "VirtualAllocEx", + "bUsesCookies": "True", + "HostHeader": "", + }, + "http": [ + {"hostname": "192.144.206.100", "port": 4848, "path": "/load", "method": "GET", "usage": "c2"}, + {"hostname": "192.144.206.100", "port": 4848, "path": "/submit.php", "method": "POST", "usage": "c2"}, + ], + "paths": [{"path": "%windir%\\syswow64\\rundll32.exe"}, {"path": "%windir%\\sysnative\\rundll32.exe"}], + } diff --git a/tests_parsers/test_darkgate.py b/tests_parsers/test_darkgate.py index 3052934e0b1..9a32a515a5a 100644 --- a/tests_parsers/test_darkgate.py +++ b/tests_parsers/test_darkgate.py @@ -1,7 +1,13 @@ from modules.processing.parsers.CAPE.DarkGate import extract_config +from modules.processing.parsers.MACO.DarkGate import convert_to_MACO def test_darkgate(): with open("tests/data/malware/1c3ae64795b61034080be00601b947819fe071efd69d7fc791a99ec666c2043d", "rb") as data: conf = extract_config(data.read()) assert conf["C2"] == ["http://80.66.88.145"] + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "DarkGate", + "other": {"C2": ["http://80.66.88.145"]}, + "http": [{"uri": "http://80.66.88.145", "usage": "c2"}], + } diff --git a/tests_parsers/test_icedid.py b/tests_parsers/test_icedid.py index c4b6b93f4d3..590591c2701 100644 --- a/tests_parsers/test_icedid.py +++ b/tests_parsers/test_icedid.py @@ -1,7 +1,14 @@ from modules.processing.parsers.CAPE.IcedIDLoader import extract_config +from modules.processing.parsers.MACO.IcedIDLoader import convert_to_MACO def test_icedid(): with open("tests/data/malware/7aaf80eb1436b946b2bd710ab57d2dcbaad2b1553d45602f2f3af6f2cfca5212", "rb") as data: conf = extract_config(data.read()) assert conf == {"C2": "anscowerbrut.com", "Campaign": 2738000827} + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "IcedIDLoader", + "campaign_id": ["2738000827"], + "other": {"C2": "anscowerbrut.com", "Campaign": 2738000827}, + "http": [{"hostname": "anscowerbrut.com", "usage": "c2"}], + } diff --git a/tests_parsers/test_koiloader.py b/tests_parsers/test_koiloader.py index cc7ab8de3d6..8844a08acef 100644 --- a/tests_parsers/test_koiloader.py +++ b/tests_parsers/test_koiloader.py @@ -3,9 +3,18 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.KoiLoader import extract_config +from modules.processing.parsers.MACO.KoiLoader import convert_to_MACO def test_koiloader(): with open("tests/data/malware/b462e3235c7578450b2b56a8aff875a3d99d22f6970a01db3ba98f7ecb6b01a0", "rb") as data: conf = extract_config(data.read()) assert conf == {"C2": ["http://91.202.233.209/hypermetropia.php", "https://admiralpub.ca/wp-content/uploads/2017"]} + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "KoiLoader", + "other": {"C2": ["http://91.202.233.209/hypermetropia.php", "https://admiralpub.ca/wp-content/uploads/2017"]}, + "http": [ + {"uri": "http://91.202.233.209/hypermetropia.php", "usage": "c2"}, + {"uri": "https://admiralpub.ca/wp-content/uploads/2017", "usage": "c2"}, + ], + } diff --git a/tests_parsers/test_latrodectus.py b/tests_parsers/test_latrodectus.py index 0348b115470..d71d20a4977 100644 --- a/tests_parsers/test_latrodectus.py +++ b/tests_parsers/test_latrodectus.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.Latrodectus import extract_config +from modules.processing.parsers.MACO.Latrodectus import convert_to_MACO def test_latrodectus(): @@ -98,3 +99,187 @@ def test_latrodectus(): "URLS|%d|%s\r\n", ], } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Latrodectus", + "version": "1.1", + "campaign_id": ["1053565364"], + "identifier": ["Novik"], + "decoded_strings": [ + "/c ipconfig /all", + "C:\\Windows\\System32\\cmd.exe", + "/c systeminfo", + "C:\\Windows\\System32\\cmd.exe", + "/c nltest /domain_trusts", + "C:\\Windows\\System32\\cmd.exe", + "/c nltest /domain_trusts /all_trusts", + "C:\\Windows\\System32\\cmd.exe", + "/c net view /all /domain", + "C:\\Windows\\System32\\cmd.exe", + "/c net view /all", + "C:\\Windows\\System32\\cmd.exe", + '/c net group "Domain Admins" /domain', + "C:\\Windows\\System32\\cmd.exe", + "/Node:localhost /Namespace:\\\\root\\SecurityCenter2 Path AntiVirusProduct Get * /Format:List", + "C:\\Windows\\System32\\wbem\\wmic.exe", + "/c net config workstation", + "C:\\Windows\\System32\\cmd.exe", + "/c wmic.exe /node:localhost /namespace:\\\\root\\SecurityCenter2 path AntiVirusProduct Get DisplayName | findstr /V /B /C:displayName || echo No Antivirus installed", + "C:\\Windows\\System32\\cmd.exe", + "/c whoami /groups", + "C:\\Windows\\System32\\cmd.exe", + ".dll", + ".exe", + '"%s"', + "rundll32.exe", + '"%s", %s %s', + "runnung", + ":wtfbbq", + "%s%s", + "%s\\%d.dll", + "%d.dat", + "%s\\%s", + 'init -zzzz="%s\\%s"', + "front", + "/files/", + "Novik", + ".exe", + "Content-Type: application/x-www-form-urlencoded", + "POST", + "GET", + "curl/7.88.1", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)", + "CLEARURL", + "URLS", + "COMMAND", + "ERROR", + "12345", + "counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s", + "counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s", + "counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)", + "%s%d.dll", + "%s%d.exe", + "LogonTrigger", + "%x%x", + "TimeTrigger", + "PT1H%02dM", + "&mac=", + "%04d-%02d-%02dT%02d:%02d:%02d", + "%02x", + ":%02x", + "PT0S", + "&computername=%s", + "&domain=%s", + "\\*.dll", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + "%04X%04X%04X%04X%08X%04X", + "%04X%04X%04X%04X%08X%04X", + "\\Registry\\Machine\\", + "AppData", + "Desktop", + "Startup", + "Personal", + "Local AppData", + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", + "C:\\WINDOWS\\SYSTEM32\\rundll32.exe %s,%s", + "C:\\WINDOWS\\SYSTEM32\\rundll32.exe %s", + "URLS", + "URLS|%d|%s\r\n", + ], + "other": { + "C2": ["https://arsimonopa.com/live/", "https://lemonimonakio.com/live/"], + "Group name": "Novik", + "Campaign ID": 1053565364, + "Version": "1.1", + "RC4 key": "12345", + "Strings": [ + "/c ipconfig /all", + "C:\\Windows\\System32\\cmd.exe", + "/c systeminfo", + "C:\\Windows\\System32\\cmd.exe", + "/c nltest /domain_trusts", + "C:\\Windows\\System32\\cmd.exe", + "/c nltest /domain_trusts /all_trusts", + "C:\\Windows\\System32\\cmd.exe", + "/c net view /all /domain", + "C:\\Windows\\System32\\cmd.exe", + "/c net view /all", + "C:\\Windows\\System32\\cmd.exe", + '/c net group "Domain Admins" /domain', + "C:\\Windows\\System32\\cmd.exe", + "/Node:localhost /Namespace:\\\\root\\SecurityCenter2 Path AntiVirusProduct Get * /Format:List", + "C:\\Windows\\System32\\wbem\\wmic.exe", + "/c net config workstation", + "C:\\Windows\\System32\\cmd.exe", + "/c wmic.exe /node:localhost /namespace:\\\\root\\SecurityCenter2 path AntiVirusProduct Get DisplayName | findstr /V /B /C:displayName || echo No Antivirus installed", + "C:\\Windows\\System32\\cmd.exe", + "/c whoami /groups", + "C:\\Windows\\System32\\cmd.exe", + ".dll", + ".exe", + '"%s"', + "rundll32.exe", + '"%s", %s %s', + "runnung", + ":wtfbbq", + "%s%s", + "%s\\%d.dll", + "%d.dat", + "%s\\%s", + 'init -zzzz="%s\\%s"', + "front", + "/files/", + "Novik", + ".exe", + "Content-Type: application/x-www-form-urlencoded", + "POST", + "GET", + "curl/7.88.1", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)", + "CLEARURL", + "URLS", + "COMMAND", + "ERROR", + "12345", + "counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s", + "counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s", + "counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)", + "%s%d.dll", + "%s%d.exe", + "LogonTrigger", + "%x%x", + "TimeTrigger", + "PT1H%02dM", + "&mac=", + "%04d-%02d-%02dT%02d:%02d:%02d", + "%02x", + ":%02x", + "PT0S", + "&computername=%s", + "&domain=%s", + "\\*.dll", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + "%04X%04X%04X%04X%08X%04X", + "%04X%04X%04X%04X%08X%04X", + "\\Registry\\Machine\\", + "AppData", + "Desktop", + "Startup", + "Personal", + "Local AppData", + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", + "C:\\WINDOWS\\SYSTEM32\\rundll32.exe %s,%s", + "C:\\WINDOWS\\SYSTEM32\\rundll32.exe %s", + "URLS", + "URLS|%d|%s\r\n", + ], + }, + "http": [ + {"uri": "https://arsimonopa.com/live/", "usage": "c2"}, + {"uri": "https://lemonimonakio.com/live/", "usage": "c2"}, + ], + "encryption": [{"algorithm": "RC4", "key": "12345"}], + } diff --git a/tests_parsers/test_lumma.py b/tests_parsers/test_lumma.py index c56efdb3aea..6f13eb15f3e 100644 --- a/tests_parsers/test_lumma.py +++ b/tests_parsers/test_lumma.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.Lumma import extract_config +from modules.processing.parsers.MACO.Lumma import convert_to_MACO def test_lumma(): @@ -21,3 +22,30 @@ def test_lumma(): "agentyanlark.site", ] } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Lumma", + "other": { + "C2": [ + "delaylacedmn.site", + "writekdmsnu.site", + "agentyanlark.site", + "bellykmrebk.site", + "underlinemdsj.site", + "commandejorsk.site", + "possiwreeste.site", + "famikyjdiag.site", + "agentyanlark.site", + ] + }, + "http": [ + {"hostname": "delaylacedmn.site", "usage": "c2"}, + {"hostname": "writekdmsnu.site", "usage": "c2"}, + {"hostname": "agentyanlark.site", "usage": "c2"}, + {"hostname": "bellykmrebk.site", "usage": "c2"}, + {"hostname": "underlinemdsj.site", "usage": "c2"}, + {"hostname": "commandejorsk.site", "usage": "c2"}, + {"hostname": "possiwreeste.site", "usage": "c2"}, + {"hostname": "famikyjdiag.site", "usage": "c2"}, + {"hostname": "agentyanlark.site", "usage": "c2"}, + ], + } diff --git a/tests_parsers/test_nanocore.py b/tests_parsers/test_nanocore.py index d27a0b1cb6a..445f2127e82 100644 --- a/tests_parsers/test_nanocore.py +++ b/tests_parsers/test_nanocore.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.NanoCore import extract_config +from modules.processing.parsers.MACO.NanoCore import convert_to_MACO def test_nanocore(): @@ -41,3 +42,59 @@ def test_nanocore(): "BackupDnsServer": "8.8.4.4", "cncs": ["6coinc.zapto.org:6696", "127.0.0.1:6696"], } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "NanoCore", + "version": "1.2.2.0", + "capability_enabled": [ + "RunOnStartup", + "BypassUserAccountControl", + "ClearZoneIdentifier", + "PreventSystemSleep", + "UseCustomDnsServer", + ], + "capability_disabled": [ + "RequestElevation", + "ClearAccessControl", + "SetCriticalProcess", + "ActivateAwayMode", + "EnableDebugMode", + ], + "mutex": ["dc5ce709-95b6-4a26-9175-16a1a8446828"], + "other": { + "BuildTime": "2023-11-22 00:25:26.569697", + "Version": "1.2.2.0", + "Mutex": "dc5ce709-95b6-4a26-9175-16a1a8446828", + "DefaultGroup": "6coinc", + "PrimaryConnectionHost": "6coinc.zapto.org", + "BackupConnectionHost": "127.0.0.1", + "ConnectionPort": "6696", + "RunOnStartup": "True", + "RequestElevation": "False", + "BypassUserAccountControl": "True", + "ClearZoneIdentifier": "True", + "ClearAccessControl": "False", + "SetCriticalProcess": "False", + "PreventSystemSleep": "True", + "ActivateAwayMode": "False", + "EnableDebugMode": "False", + "RunDelay": "0", + "ConnectDelay": "4000", + "RestartDelay": "5000", + "TimeoutInterval": "5000", + "KeepAliveTimeout": "30000", + "MutexTimeout": "5000", + "LanTimeout": "2500", + "WanTimeout": "8000", + "BufferSize": "65535", + "MaxPacketSize": "10485760", + "GCThreshold": "10485760", + "UseCustomDnsServer": "True", + "PrimaryDnsServer": "8.8.8.8", + "BackupDnsServer": "8.8.4.4", + "cncs": ["6coinc.zapto.org:6696", "127.0.0.1:6696"], + }, + "http": [ + {"hostname": "6coinc.zapto.org", "port": 6696, "usage": "c2"}, + {"hostname": "127.0.0.1", "port": 6696, "usage": "c2"}, + ], + } diff --git a/tests_parsers/test_njrat.py b/tests_parsers/test_njrat.py index 8ca43e83a9c..5fb816a30a1 100644 --- a/tests_parsers/test_njrat.py +++ b/tests_parsers/test_njrat.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.Njrat import extract_config +from modules.processing.parsers.MACO.Njrat import convert_to_MACO def test_njrat(): @@ -13,6 +14,16 @@ def test_njrat(): "campaign id": "HacKed", "version": "Njrat 0.7 Golden By Hassan Amiri", } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Njrat", + "version": "Njrat 0.7 Golden By Hassan Amiri", + "other": { + "cncs": ["peter-bikini.gl.at.ply.gg:64215"], + "campaign id": "HacKed", + "version": "Njrat 0.7 Golden By Hassan Amiri", + }, + "http": [{"hostname": "peter-bikini.gl.at.ply.gg", "port": 64215, "usage": "c2"}], + } """ diff --git a/tests_parsers/test_oyster.py b/tests_parsers/test_oyster.py index bf77aac1bc7..ed758e42362 100644 --- a/tests_parsers/test_oyster.py +++ b/tests_parsers/test_oyster.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.Oyster import extract_config +from modules.processing.parsers.MACO.Oyster import convert_to_MACO def test_oyster(): @@ -13,3 +14,14 @@ def test_oyster(): "Dll Version": "v1.0 #ads 2", "Strings": ["api/connect", "Content-Type: application/json", "api/session"], } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Oyster", + "version": "v1.0 #ads 2", + "decoded_strings": ["api/connect", "Content-Type: application/json", "api/session"], + "other": { + "C2": ["https://connectivity-check.linkpc.net/"], + "Dll Version": "v1.0 #ads 2", + "Strings": ["api/connect", "Content-Type: application/json", "api/session"], + }, + "http": [{"uri": "https://connectivity-check.linkpc.net/", "usage": "c2"}], + } diff --git a/tests_parsers/test_pikabot.py b/tests_parsers/test_pikabot.py index 2562dc84441..18bc7018f06 100644 --- a/tests_parsers/test_pikabot.py +++ b/tests_parsers/test_pikabot.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.PikaBot import extract_config +from modules.processing.parsers.MACO.PikaBot import convert_to_MACO def test_pikabot(): @@ -27,3 +28,85 @@ def test_pikabot(): "Campaign Name": "GG24_T@T@f0adda360d2b4ccda11468e026526576", "Registry Key": "MWnkl", } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "PikaBot", + "version": "1.8.32-beta", + "campaign_id": ["GG24_T@T@f0adda360d2b4ccda11468e026526576"], + "other": { + "C2s": [ + "154.53.55.165:13783", + "158.247.240.58:5632", + "70.34.223.164:5000", + "70.34.199.64:9785", + "45.77.63.237:5632", + "198.38.94.213:2224", + "94.72.104.80:5000", + "84.46.240.42:2083", + "154.12.236.248:13786", + "94.72.104.77:13724", + "209.126.86.48:1194", + ], + "Version": "1.8.32-beta", + "User Agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + "Campaign Name": "GG24_T@T@f0adda360d2b4ccda11468e026526576", + "Registry Key": "MWnkl", + }, + "http": [ + { + "hostname": "154.53.55.165", + "port": 13783, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "158.247.240.58", + "port": 5632, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "70.34.223.164", + "port": 5000, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "70.34.199.64", + "port": 9785, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "45.77.63.237", + "port": 5632, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "198.38.94.213", + "port": 2224, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "94.72.104.80", + "port": 5000, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "84.46.240.42", + "port": 2083, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "154.12.236.248", + "port": 13786, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "94.72.104.77", + "port": 13724, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + { + "hostname": "209.126.86.48", + "port": 1194, + "user_agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; A7F; BRI/2; Tablet PC 2.0; wbx 1.0.0; Microsoft Outlook 14.0.7233; ms-office;", + }, + ], + "registry": [{"key": "MWnkl"}], + } diff --git a/tests_parsers/test_quickbind.py b/tests_parsers/test_quickbind.py index 089371fc437..91a2248dd70 100644 --- a/tests_parsers/test_quickbind.py +++ b/tests_parsers/test_quickbind.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.Quickbind import extract_config +from modules.processing.parsers.MACO.Quickbind import convert_to_MACO def test_quickbind(): @@ -13,3 +14,14 @@ def test_quickbind(): "Mutex": ["15432a4d-34ca-4d0d-a4ac-04df9a373862"], "C2": ["185.49.69.41"], } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Quickbind", + "mutex": ["15432a4d-34ca-4d0d-a4ac-04df9a373862"], + "other": { + "Encryption Key": "24de21a8dc08434c", + "Mutex": ["15432a4d-34ca-4d0d-a4ac-04df9a373862"], + "C2": ["185.49.69.41"], + }, + "http": [{"hostname": "185.49.69.41", "usage": "c2"}], + "encryption": [{"key": "24de21a8dc08434c"}], + } diff --git a/tests_parsers/test_redline.py b/tests_parsers/test_redline.py index 96d133b72eb..17ffaaa20cf 100644 --- a/tests_parsers/test_redline.py +++ b/tests_parsers/test_redline.py @@ -3,6 +3,7 @@ # See the file "docs/LICENSE" for copying permission. from modules.processing.parsers.CAPE.RedLine import extract_config +from modules.processing.parsers.MACO.RedLine import convert_to_MACO def test_redline(): @@ -14,3 +15,13 @@ def test_redline(): "Botnet": "krast", "Key": "Formative", } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "RedLine", + "other": { + "Authorization": "9059ea331e4599de3746df73ccb24514", + "C2": "77.91.68.68:19071", + "Botnet": "krast", + "Key": "Formative", + }, + "http": [{"hostname": "77.91.68.68", "port": 19071, "usage": "c2"}], + } diff --git a/tests_parsers/test_smokeloader.py b/tests_parsers/test_smokeloader.py index b77499c256d..cb7f4085a4e 100644 --- a/tests_parsers/test_smokeloader.py +++ b/tests_parsers/test_smokeloader.py @@ -1,7 +1,13 @@ from modules.processing.parsers.CAPE.SmokeLoader import extract_config +from modules.processing.parsers.MACO.SmokeLoader import convert_to_MACO def test_smokeloader(): with open("tests/data/malware/6929fff132c05ae7d348867f4ea77ba18f84fb8fae17d45dde3571c9e33f01f8", "rb") as data: conf = extract_config(data.read()) assert conf == {"C2s": ["http://host-file-host6.com/", "http://host-host-file8.com/"]} + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "SmokeLoader", + "other": {"C2s": ["http://host-file-host6.com/", "http://host-host-file8.com/"]}, + "http": [{"uri": "http://host-file-host6.com/", "usage": "c2"}, {"uri": "http://host-host-file8.com/", "usage": "c2"}], + } diff --git a/tests_parsers/test_sparkrat.py b/tests_parsers/test_sparkrat.py index 412c9165ae5..ba60ca225be 100644 --- a/tests_parsers/test_sparkrat.py +++ b/tests_parsers/test_sparkrat.py @@ -1,4 +1,5 @@ from modules.processing.parsers.CAPE.SparkRAT import extract_config +from modules.processing.parsers.MACO.SparkRAT import convert_to_MACO def test_sparkrat(): @@ -12,3 +13,16 @@ def test_sparkrat(): "uuid": "8dc7e7d8f8576f3e55a00850b72887db", "key": "a1348fb8969ad7a9f85ac173c2027622135e52e0e6d94d10e6a81916a29648ac", } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "SparkRAT", + "identifier": ["8dc7e7d8f8576f3e55a00850b72887db"], + "other": { + "secure": False, + "host": "67.217.62.106", + "port": 4443, + "path": "/", + "uuid": "8dc7e7d8f8576f3e55a00850b72887db", + "key": "a1348fb8969ad7a9f85ac173c2027622135e52e0e6d94d10e6a81916a29648ac", + }, + "http": [{"uri": "http://67.217.62.106:4443/", "hostname": "67.217.62.106", "port": 4443, "path": "/"}], + } diff --git a/tests_parsers/test_zloader.py b/tests_parsers/test_zloader.py index 2168f2e9c7a..d9c0ef1f174 100644 --- a/tests_parsers/test_zloader.py +++ b/tests_parsers/test_zloader.py @@ -3,6 +3,7 @@ # See the file 'docs/LICENSE' for copying permission. from modules.processing.parsers.CAPE.Zloader import extract_config +from modules.processing.parsers.MACO.Zloader import convert_to_MACO def test_zloader(): @@ -14,3 +15,14 @@ def test_zloader(): "address": ["https://dem.businessdeep.com"], "Public key": "-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKGAOWVkikqE7TyKIMtWI8dFsaleTaJNXMJNIPnRE/fGCzqrV+rtY3+ex4MCHEtq2Vwppthf0Rglv8OiWgKlerIN5P6NEyCfIsFYUMDfldQTF03VES8GBIvHq5SjlIz7lawuwfdjdEkaHfOmmu9srraftkI9gZO8WRQgY1uNdsXwIDAQAB-----END PUBLIC KEY-----", } + assert convert_to_MACO(conf).model_dump(exclude_defaults=True, exclude_none=True) == { + "family": "Zloader", + "campaign_id": ["M1"], + "other": { + "Botnet name": "Bing_Mod5", + "Campaign ID": "M1", + "address": ["https://dem.businessdeep.com"], + "Public key": "-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKGAOWVkikqE7TyKIMtWI8dFsaleTaJNXMJNIPnRE/fGCzqrV+rtY3+ex4MCHEtq2Vwppthf0Rglv8OiWgKlerIN5P6NEyCfIsFYUMDfldQTF03VES8GBIvHq5SjlIz7lawuwfdjdEkaHfOmmu9srraftkI9gZO8WRQgY1uNdsXwIDAQAB-----END PUBLIC KEY-----", + }, + "http": [{"uri": "https://dem.businessdeep.com"}], + }