class EicarPlugin(AntivirusPluginInterface, metaclass=PluginMetaClass): # ================= # plugin metadata # ================= _plugin_name_ = "Eicar" _plugin_display_name_ = Eicar.name _plugin_author_ = "IRMA (c) Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.antivirus _plugin_description_ = "Plugin for Eicar test on Linux" _plugin_dependencies_ = [ PlatformDependency('linux'), FileDependency( '/opt/Eicar_probe_activated', help='Make sure you have activated the probe', ), ] # ================ # interface data # ================ module_cls = Eicar # `AntivirusPluginInterface`'s verify method check that the AV executable is # available. # We are bypassing this verification, because this probe doesn't have any # executable. @classmethod def verify(cls): return True
class TrIDPlugin(PluginBase): class TrIDResults: ERROR = -1 FAILURE = 0 SUCCESS = 1 _plugin_name_ = "TrID" _plugin_display_name_ = "TrID File Identifier" _plugin_author_ = "IRMA (c) Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = "metadata" _plugin_description_ = "Plugin to determine file type" _plugin_dependencies_ = [ PlatformDependency('linux'), FileDependency( os.path.join('/opt/trid/', 'trid'), help='Make sure you have downloaded trid binary' ), FileDependency( os.path.join('/opt/trid/', 'triddefs.trd'), help='Make sure to have downloaded trid definitions' ), ] def __init__(self): module = sys.modules['modules.metadata.trid.trid'].TrID self.module = module() def run(self, paths): results = PluginResult(name=type(self).plugin_display_name, type=type(self).plugin_category, version=None) # launch file analysis try: started = timestamp(datetime.utcnow()) results.status, results.results = self.module.analyze(paths) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started except Exception as e: results.status = self.TrIDResults.ERROR results.error = type(e).__name__ + " : " + str(e) return results
class DummyPlugin(PluginBase): class DummyResult: ERROR = -1 FAILURE = 0 SUCCESS = 1 # ================= # plugin metadata # ================= _plugin_name_ = "Dummy" _plugin_display_name_ = "Dummy" _plugin_author_ = "IRMA (c) Quarkslab" _plugin_version_ = "0.0.1" _plugin_category_ = IrmaProbeType.metadata _plugin_description_ = "Plugin to return SHA256 of a file" _plugin_dependencies_ = [ PlatformDependency('linux'), FileDependency('/opt/Dummy_probe_activated', help='Make sure you have activated the Dummy probe'), ] # ============= # constructor # ============= def __init__(self): pass # ================== # probe interfaces # ================== def run(self, paths): response = PluginResult(name=type(self).plugin_display_name, type=type(self).plugin_category, version=None) try: started = timestamp(datetime.utcnow()) response.results = sha256sum(open(paths, 'rb')) stopped = timestamp(datetime.utcnow()) response.duration = stopped - started response.status = self.DummyResult.SUCCESS except Exception as e: response.status = self.DummyResult.ERROR response.results = type(e).__name__ + " : " + str(e) return response
class ICAPPlugin(PluginBase): class ICAPResult: ERROR = -1 INFECTED = 0 CLEAN = 1 # ================= # plugin metadata # ================= _plugin_name_ = "ICAP" _plugin_display_name_ = "ICAP" _plugin_author_ = "Vincent Rasneur <*****@*****.**>" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.external _plugin_description_ = "Plugin to query an ICAP antivirus server" _plugin_dependencies_ = [ ModuleDependency( 'icapclient', help='See requirements.txt for needed dependencies' ), FileDependency( os.path.join(os.path.dirname(__file__), 'config.ini') ) ] # ============= # constructor # ============= def __init__(self, **kwargs): # load default configuration file config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) self.conn_kwargs = self.retrieve_options(config, kwargs, (('host', str), ('port', int))) self.req_kwargs = self.retrieve_options(config, kwargs, (('service', str), ('url', str), ('timeout', int))) self.module = sys.modules['icapclient'] @staticmethod def retrieve_options(config, kwargs, keys): options = {} # override default values if specified for option, type_ in keys: value = kwargs.get(option) if value is None: try: value = config.get('ICAP', option) except NoOptionError: pass if value is not None: options[option] = type_(value) return options def query_server(self, filename): conn = self.module.ICAPConnection(**self.conn_kwargs) conn.request('REQMOD', filename, read_content=False, **self.req_kwargs) resp = conn.getresponse() conn.close() # look for the headers defined inside # the RFC Draft for ICAP Extensions threat = resp.get_icap_header('X-Violations-Found') # multiple threats? try to parse the header values if threat is not None: try: values = threat.split('\n') # only read the human readable descriptions threats = [s.strip() for idx, s in enumerate(values[1:]) if idx % 4 == 1] threat = '%s threat(s) found: %s' % \ (threats[0].strip(), ', '.join(threats)) except: threat = 'Multiple threats found: %s' % threat if threat is None: # only a description threat = resp.get_icap_header('X-Virus-ID') if threat is not None: threat = 'Threat found: %s' % threat if threat is None: threat = resp.get_icap_header('X-Infection-Found') if threat is not None: # only return the human readable threat name cookie = SimpleCookie(threat) kv = cookie.get('Threat') if kv is not None: threat = kv.value if threat is not None: threat = 'Threat found: %s' % threat return threat # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).plugin_display_name, type=type(self).plugin_category, version=None) try: # query the ICAP server: issue a REQMOD request started = timestamp(datetime.utcnow()) response = self.query_server(paths) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started if response is None: results.status = self.ICAPResult.CLEAN results.results = 'No threat found' else: results.status = self.ICAPResult.INFECTED results.results = response except Exception as e: results.status = self.ICAPResult.ERROR results.error = type(e).__name__ + " : " + str(e) return results
class VirusTotalPlugin(PluginBase): class VirusTotalResult: ERROR = -1 FOUND = 1 NOT_FOUND = 0 # ================= # plugin metadata # ================= _plugin_name_ = "VirusTotal" _plugin_display_name_ = "VirusTotal" _plugin_author_ = "IRMA (c) Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.external _plugin_description_ = "Plugin to query VirusTotal API" _plugin_dependencies_ = [ ModuleDependency('virus_total_apis', help='See requirements.txt for needed dependencies'), FileDependency(os.path.join(os.path.dirname(__file__), 'config.ini')) ] # ============= # constructor # ============= def __init__(self, apikey=None, private=None): # load default configuration file config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) # override default values if specified if apikey is None: self.apikey = config.get('VirusTotal', 'apikey') else: self.apikey = apikey if private is None: self.private = bool(config.get('VirusTotal', 'private')) else: self.private = private # choose either public or private API for requests if private: module = sys.modules['virus_total_apis'].PrivateApi else: module = sys.modules['virus_total_apis'].PublicApi self.module = module(self.apikey) def get_file_report(self, filename): with open(filename, 'rb') as filedesc: digest = md5sum(filedesc) return self.module.get_file_report(digest) # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).plugin_display_name, type=type(self).plugin_category, version=None) try: # get the report, automatically append results started = timestamp(datetime.utcnow()) response = self.get_file_report(paths) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started # check eventually for errors if 'error' in response: results.status = self.VirusTotalResult.ERROR results.error = str(response['error']) elif response['response_code'] == 204: results.status = self.VirusTotalResult.ERROR results.error = "Public API request rate limit exceeded" elif response['response_code'] == 403: results.status = self.VirusTotalResult.ERROR results.error = "Access forbidden (wrong key value or type)" elif response['response_code'] == 200 and \ response['results']['response_code'] != 1: results.status = self.VirusTotalResult.NOT_FOUND else: results.status = self.VirusTotalResult.FOUND results.results = response if 'error' not in response else None except Exception as e: results.status = self.VirusTotalResult.ERROR results.results = str(e) return results
class YaraPlugin(PluginBase): class YaraResult: ERROR = -1 FOUND = 1 NOT_FOUND = 0 # ================= # plugin metadata # ================= _plugin_name_ = "Yara" _plugin_display_name_ = "Yara" _plugin_author_ = "Bryan Nolen @BryanNolen" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.metadata _plugin_description_ = "Plugin to run files against yara rules" _plugin_dependencies_ = [ ModuleDependency( 'yara', help='Requires yara 3 or greater and matching yara-python' ), FileDependency( os.path.join(os.path.dirname(__file__), 'config.ini') ) ] # ============= # constructor # ============= def __init__(self, rule_path=None): # load default configuration file config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) # override default values if specified if rule_path is None: self.rule_path = config.get('Yara', 'rule_path') else: self.rule_path = rule_path self.rules = sys.modules['yara'].compile(filepath=self.rule_path) def get_file_report(self, filename): try: results = (False, self.rules.match(filename, timeout=60)) except Exception as e: results = (True, str(e)) finally: return results # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).plugin_display_name, type=type(self).plugin_category, version=None) try: # get the report, automatically append results started = timestamp(datetime.utcnow()) (error_raised, response) = self.get_file_report(paths) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started # check eventually for errors if error_raised: results.status = self.YaraResult.ERROR results.error = response elif response.__len__() == 0: results.status = self.YaraResult.NOT_FOUND else: results.status = self.YaraResult.FOUND match_string = "" matches = [] if results.status is self.YaraResult.FOUND: for match in response: match_string = "{0}, {1}".format(match_string, match) matches.append("{0!s}".format(match)) results.results = None if not error_raised: # results.results = {'Matches': "{0}".format(match_string)} results.results = {'Matches': matches} except Exception as e: results.status = self.YaraResult.ERROR results.results = str(e) return results
class NSRLPlugin(PluginBase): class NSRLPluginResult: ERROR = -1 FOUND = 1 NOT_FOUND = 0 # ================= # plugin metadata # ================= _plugin_name_ = "NSRL" _plugin_display_name_ = "National Software Reference Library" _plugin_author_ = "IRMA (c) Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.database _plugin_description_ = "Information plugin to query hashes on " \ "NRSL database" _plugin_dependencies_ = [ ModuleDependency( 'leveldict', help='See requirements.txt for needed dependencies' ), ModuleDependency( 'modules.database.nsrl.nsrl' ), FileDependency( os.path.join(os.path.dirname(__file__), 'config.ini') ) ] _plugin_mimetype_regexp = 'PE32' @classmethod def verify(cls): # load default configuration file config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) os_db = config.get('NSRL', 'nsrl_os_db') mfg_db = config.get('NSRL', 'nsrl_mfg_db') file_db = config.get('NSRL', 'nsrl_file_db') prod_db = config.get('NSRL', 'nsrl_prod_db') databases = [os_db, mfg_db, file_db, prod_db] # check for configured database path results = list(map(os.path.exists, databases)) dbs_available = reduce(lambda x, y: x or y, results, False) if not dbs_available: raise PluginLoadError("{0}: verify() failed because " "databases are not available." "".format(cls.__name__)) # check for LOCK file and remove it dbs_locks = [os.path.join(x, "LOCK") for x in databases] for lock in dbs_locks: try: if os.path.exists(lock): os.unlink(lock) except: raise PluginLoadError("unable to remove lock {0}".format(lock)) # ================================== # constructor and destructor stuff # ================================== def __init__(self): # load default configuration file config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) # get configuration values nsrl_os_db = config.get('NSRL', 'nsrl_os_db') nsrl_mfg_db = config.get('NSRL', 'nsrl_mfg_db') nsrl_file_db = config.get('NSRL', 'nsrl_file_db') nsrl_prod_db = config.get('NSRL', 'nsrl_prod_db') # lookup module module = sys.modules['modules.database.nsrl.nsrl'].NSRL self.module = module(nsrl_file_db, nsrl_prod_db, nsrl_os_db, nsrl_mfg_db) # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).display_name, type=type(self).plugin_category, version=None) try: # lookup the specified sha1 started = timestamp(datetime.utcnow()) response = self.module.lookup_by_sha1(sha1sum(paths).upper()) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started # check for errors if isinstance(response, dict) and \ (not response.get('MfgCode', None) or not response.get('OpSystemCode', None) or not response.get('ProductCode', None) or not response.get('SHA-1', None)): results.status = self.NSRLPluginResult.NOT_FOUND response = None else: results.status = self.NSRLPluginResult.FOUND results.results = response except Exception as e: results.status = self.NSRLPluginResult.ERROR results.error = str(e) return results
class PEiDPlugin(PluginBase): class PEiDResult: ERROR = -1 FOUND = 1 NOT_FOUND = 0 # ================= # plugin metadata # ================= _plugin_name_ = "PEiD" _plugin_display_name_ = "PEiD PE Packer Identifier" _plugin_author_ = "Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.metadata _plugin_description_ = "Plugin to run files against PEiD signatures" _plugin_dependencies_ = [ ModuleDependency('pefile', help='See requirements.txt for needed dependencies'), ModuleDependency('peutils', help='See requirements.txt for needed dependencies'), FileDependency(os.path.join(os.path.dirname(__file__), 'config.ini')) ] _plugin_mimetype_regexp = 'PE32' @classmethod def verify(cls): # load default configuration file config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) sign_path = config.get('PEiD', 'sign_path') # check for configured signatures path if not os.path.exists(sign_path): raise PluginLoadError("{0}: verify() failed because " "signatures file not found." "".format(cls.__name__)) # ============= # constructor # ============= def __init__(self): config = ConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'config.ini')) sign_path = config.get('PEiD', 'sign_path') peutils = sys.modules['peutils'] data = open(sign_path, "r", encoding="utf8", errors="ignore").read() self.signatures = peutils.SignatureDatabase(data=data) def analyze(self, filename): pefile = sys.modules['pefile'] try: pe = pefile.PE(filename) results = self.signatures.match(pe) if results is None: return self.PEiDResult.NOT_FOUND, "No match found" else: return self.PEiDResult.FOUND, results[0] except pefile.PEFormatError: return self.PEiDResult.NOT_FOUND, "Not a PE" # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).plugin_display_name, type=type(self).plugin_category, version=None) try: started = timestamp(datetime.utcnow()) (status, response) = self.analyze(paths) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started results.status = status results.results = response except Exception as e: results.status = self.PEiDResult.ERROR results.error = type(e).__name__ + " : " + str(e) return results