Exemplo n.º 1
0
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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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