diff --git a/api_app/analyzers_manager/file_analyzers/iocfinder.py b/api_app/analyzers_manager/file_analyzers/iocfinder.py new file mode 100644 index 0000000000..53bd453aa0 --- /dev/null +++ b/api_app/analyzers_manager/file_analyzers/iocfinder.py @@ -0,0 +1,40 @@ +import logging + +from ioc_finder import find_iocs + +from api_app.analyzers_manager.classes import FileAnalyzer + +logger = logging.getLogger(__name__) + + +class IocFinder(FileAnalyzer): + parse_domain_from_url: bool = True + parse_from_url_path: bool = True + parse_domain_from_email_address: bool = True + parse_address_from_cidr: bool = True + parse_domain_name_from_xmpp_address: bool = True + parse_urls_without_scheme: bool = True + parse_imphashes: bool = True + parse_authentihashes: bool = True + + def update(self): + pass + + def run(self): + logger.info(f"Running IOC Finder on {self.filepath} for {self.md5}") + binary_data = self.read_file_bytes() + text_data = binary_data.decode("utf-8") + + iocs = find_iocs( + text_data, + parse_domain_from_url=self.parse_domain_from_url, + parse_from_url_path=self.parse_from_url_path, + parse_domain_from_email_address=self.parse_domain_from_email_address, + parse_address_from_cidr=self.parse_address_from_cidr, + parse_domain_name_from_xmpp_address=self.parse_domain_name_from_xmpp_address, # noqa: E501 + parse_urls_without_scheme=self.parse_urls_without_scheme, + parse_imphashes=self.parse_imphashes, + parse_authentihashes=self.parse_authentihashes, + ) + + return iocs diff --git a/api_app/analyzers_manager/migrations/0109_analyzer_config_iocfinder.py b/api_app/analyzers_manager/migrations/0109_analyzer_config_iocfinder.py new file mode 100644 index 0000000000..91c014cd95 --- /dev/null +++ b/api_app/analyzers_manager/migrations/0109_analyzer_config_iocfinder.py @@ -0,0 +1,206 @@ +from django.db import migrations +from django.db.models.fields.related_descriptors import ( + ForwardManyToOneDescriptor, + ForwardOneToOneDescriptor, + ManyToManyDescriptor, +) + +plugin = { + "python_module": { + "health_check_schedule": None, + "update_schedule": None, + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "IocFinder", + "description": "[IocFinder](https://github.com/fhightower/ioc-finder) a library to find different types of indicators of compromise (a.k.a observables) and data pertinent to indicators of compromise!", + "disabled": False, + "soft_time_limit": 20, + "routing_key": "default", + "health_check_status": True, + "type": "file", + "docker_based": False, + "maximum_tlp": "RED", + "observable_supported": [], + "supported_filetypes": ["text/plain"], + "run_hash": False, + "run_hash_type": "", + "not_supported_filetypes": [], + "model": "analyzers_manager.AnalyzerConfig", +} + +params = [ + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_domain_from_url", + "type": "bool", + "description": "to parse domain from URL", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_from_url_path", + "type": "bool", + "description": "parse from URL path", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_domain_from_email_address", + "type": "bool", + "description": "to parse domain from email address", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_address_from_cidr", + "type": "bool", + "description": "to parse address from CIDR", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_domain_name_from_xmpp_address", + "type": "bool", + "description": "to parse domain name from XMPP address", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_urls_without_scheme", + "type": "bool", + "description": "to parse URLs without scheme", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_imphashes", + "type": "bool", + "description": "to parse imphashes", + "is_secret": False, + "required": False, + }, + { + "python_module": { + "module": "iocfinder.IocFinder", + "base_path": "api_app.analyzers_manager.file_analyzers", + }, + "name": "parse_authentihashes", + "type": "bool", + "description": "to parse authentihashes", + "is_secret": False, + "required": False, + }, +] + +values = [] + + +def _get_real_obj(Model, field, value): + def _get_obj(Model, other_model, value): + if isinstance(value, dict): + real_vals = {} + for key, real_val in value.items(): + real_vals[key] = _get_real_obj(other_model, key, real_val) + value = other_model.objects.get_or_create(**real_vals)[0] + # it is just the primary key serialized + else: + if isinstance(value, int): + if Model.__name__ == "PluginConfig": + value = other_model.objects.get(name=plugin["name"]) + else: + value = other_model.objects.get(pk=value) + else: + value = other_model.objects.get(name=value) + return value + + if ( + type(getattr(Model, field)) + in [ForwardManyToOneDescriptor, ForwardOneToOneDescriptor] + and value + ): + other_model = getattr(Model, field).get_queryset().model + value = _get_obj(Model, other_model, value) + elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value: + other_model = getattr(Model, field).rel.model + value = [_get_obj(Model, other_model, val) for val in value] + return value + + +def _create_object(Model, data): + mtm, no_mtm = {}, {} + for field, value in data.items(): + value = _get_real_obj(Model, field, value) + if type(getattr(Model, field)) is ManyToManyDescriptor: + mtm[field] = value + else: + no_mtm[field] = value + try: + o = Model.objects.get(**no_mtm) + except Model.DoesNotExist: + o = Model(**no_mtm) + o.full_clean() + o.save() + for field, value in mtm.items(): + attribute = getattr(o, field) + if value is not None: + attribute.set(value) + return False + return True + + +def migrate(apps, schema_editor): + Parameter = apps.get_model("api_app", "Parameter") + PluginConfig = apps.get_model("api_app", "PluginConfig") + python_path = plugin.pop("model") + Model = apps.get_model(*python_path.split(".")) + if not Model.objects.filter(name=plugin["name"]).exists(): + exists = _create_object(Model, plugin) + if not exists: + for param in params: + _create_object(Parameter, param) + for value in values: + _create_object(PluginConfig, value) + + +def reverse_migrate(apps, schema_editor): + python_path = plugin.pop("model") + Model = apps.get_model(*python_path.split(".")) + Model.objects.get(name=plugin["name"]).delete() + + +class Migration(migrations.Migration): + atomic = False + dependencies = [ + ("api_app", "0062_alter_parameter_python_module"), + ("analyzers_manager", "0108_analyzer_config_iocextract"), + ] + + operations = [migrations.RunPython(migrate, reverse_migrate)] diff --git a/docs/source/Usage.md b/docs/source/Usage.md index 75c2d2aea2..587e2c6939 100644 --- a/docs/source/Usage.md +++ b/docs/source/Usage.md @@ -125,7 +125,8 @@ The following is the list of the available analyzers you can run out-of-the-box. * [edelucia Yara rules](https://github.com/edelucia/rules/tree/main/yara) * [LOLDrivers Yara Rules](https://github.com/magicsword-io/LOLDrivers) * your own added signatures. See [Advanced-Usage](./Advanced-Usage.html#analyzers-with-special-configuration) for more details. -* `GoReSym`:[GoReSym](https://github.com/mandiant/GoReSym) is a Go symbol parser that extracts program metadata (such as CPU architecture, OS, endianness, compiler version, etc), function metadata (start & end addresses, names, sources), filename and line number metadata, and embedded structures and types. + * `GoReSym`:[GoReSym](https://github.com/mandiant/GoReSym) is a Go symbol parser that extracts program metadata (such as CPU architecture, OS, endianness, compiler version, etc), function metadata (start & end addresses, names, sources), filename and line number metadata, and embedded structures and types. + * `IocFinder`:[IocFinder](https://github.com/fhightower/ioc-finder) a library to find different types of indicators of compromise (a.k.a observables) and data pertinent to indicators of compromise! ###### External services - `CapeSandbox`: [CAPESandbox](https://capesandbox.com) automatically scans suspicious files using the CapeSandbox API. Analyzer works for private instances as well. diff --git a/requirements/project-requirements.txt b/requirements/project-requirements.txt index 6ad3afb5eb..22ed3cb739 100644 --- a/requirements/project-requirements.txt +++ b/requirements/project-requirements.txt @@ -80,6 +80,8 @@ hfinger==0.2.2 permhash==0.1.4 ail_typo_squatting==2.7.4 iocextract==1.16.1 +ioc-finder==7.0.0 + # this is required because XLMMacroDeobfuscator does not pin the following packages pyxlsb2==0.0.8 xlrd2==1.3.4