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)._plugin_display_name_, type=type(self)._plugin_category_, version=None) try: # lookup the specified sha1 started = timestamp(datetime.utcnow()) with open(paths,"r") as fileobj: response = self.module.lookup_by_sha1(sha1sum(fileobj)) 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 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 = str(e) return results
class VirusTotalPlugin(PluginBase): class VirusTotalResult: ERROR = -1 FOUND = 1 NOT_FOUND = 0 # ================= # plugin metadata # ================= _plugin_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 = SafeConfigParser() 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 = hashlib.md5(filedesc.read()).hexdigest() return self.module.get_file_report(digest) # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).plugin_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 = 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 UnarchivePlugin(PluginBase): class UnarchiveResult: ERROR = -1 OK = 0 # ================= # plugin metadata # ================= _plugin_name_ = "Unarchive" _plugin_display_name_ = "Unarchive" _plugin_author_ = "Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = "tools" # TODO add an IrmaProbetype _plugin_description_ = "Plugin to unarchive files" _plugin_dependencies_ = [ PlatformDependency('linux'), ModuleDependency('pyunpack', help='See requirements.txt for needed dependencies'), BinaryDependency( 'patool', help='unarchiver frontend required to support various formats'), ] _plugin_mimetype_regexp = 'archive' # ============= # constructor # ============= def __init__(self): pass def unarchive(self, filename, dst_dir): Archive = sys.modules['pyunpack'].Archive Archive(filename).extractall(dst_dir, auto_create_dir=True) path_list = [] # Make sure dst_dir ends with a '/' # useful when removing from filepath if len(dst_dir) > 1 and dst_dir[-1] != '/': dst_dir += '/' for (dirname, _, filenames) in os.walk(dst_dir): for filename in filenames: relative_dirname = dirname.replace(dst_dir, "") path = os.path.join(relative_dirname, filename) path_list.append(path) return path_list # ================== # probe interfaces # ================== def run(self, paths): results = PluginResult(name=type(self).plugin_name, type=type(self).plugin_category, version=None) try: started = timestamp(datetime.utcnow()) output_dir = tempfile.mkdtemp() file_list = self.unarchive(paths, output_dir) results.output_files = {} results.output_files['output_dir'] = output_dir results.output_files['file_list'] = file_list stopped = timestamp(datetime.utcnow()) results.duration = stopped - started results.status = self.UnarchiveResult.OK results.results = None except Exception as e: results.status = self.UnarchiveResult.ERROR results.error = str(e) return results
class PEAnalyzerPlugin(PluginBase): class StaticAnalyzerResults: ERROR = -1 SUCCESS = 1 FAILURE = 0 # ================= # plugin metadata # ================= _plugin_name_ = "StaticAnalyzer" _plugin_display_name_ = "PE Static Analyzer" _plugin_author_ = "IRMA (c) Quarkslab" _plugin_version_ = "1.0.0" _plugin_category_ = IrmaProbeType.metadata _plugin_description_ = "Plugin to analyze PE files" _plugin_dependencies_ = [ ModuleDependency('pefile', help='See requirements.txt for needed dependencies'), ModuleDependency('peutils', help='See requirements.txt for needed dependencies'), ModuleDependency('modules.metadata.pe_analyzer.pe'), ModuleDependency('lib.common.mimetypes'), ] _plugin_mimetype_regexp = 'PE32' # ================================== # constructor and destructor stuff # ================================== def __init__(self): module = sys.modules['modules.metadata.pe_analyzer.pe'].PE self.module = module() def analyze(self, filename): # check parameters if not filename: raise RuntimeError("filename is invalid") # check if file exists mimetype = None if os.path.exists(filename): # guess mimetype for file magic = sys.modules['lib.common.mimetypes'].Magic mimetype = magic.from_file(filename) else: raise RuntimeError("file does not exist") result = None # look for PE mime type if mimetype and re.match('PE32', mimetype): result = self.module.analyze(filename) else: logging.warning("{0} not yet handled".format(mimetype)) return result 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()) response = self.analyze(filename=paths) stopped = timestamp(datetime.utcnow()) results.duration = stopped - started # update results if not response: results.status = self.StaticAnalyzerResults.FAILURE results.results = "Not a PE file" else: results.status = self.StaticAnalyzerResults.SUCCESS results.results = response except Exception as e: results.status = self.StaticAnalyzerResults.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 = str(e) return results