예제 #1
0
    def add_extracted_file(self, filepath, automatic_analysis=True):
        self.log('debug', u"Adding extracted file '{}'".format(filepath))

        fd = open(filepath, 'rb')
        filename = os.path.basename(filepath)
        f = File(filename=filename, stream=fd, create=False)

        if not f.existing:
            if fame_config.remote:
                response = send_file_to_remote(filepath, '/files/')
                f = File(response.json()['file'])
            else:
                f = File(filename=os.path.basename(filepath), stream=fd)

            # Automatically analyze extracted file if magic is enabled and module did not disable it
            if self.magic_enabled() and automatic_analysis:
                modules = None
                config = Config.get(name="extracted").get_values()
                if config is not None and "modules" in config:
                    modules = config["modules"].split()
                f.analyze(self['groups'], self['analyst'], modules,
                          self['options'])

        fd.close()

        self.append_to('extracted_files', f['_id'])
        f.add_parent_analysis(self)
예제 #2
0
    def add_extracted_file(self, filepath):
        self.log('debug', "Adding extracted file '{}'".format(filepath))

        fd = open(filepath, 'rb')
        filename = os.path.basename(filepath)
        f = File(filename=filename, stream=fd, create=False)

        if not f.existing:
            if fame_config.remote:
                response = send_file_to_remote(filepath, '/files/')
                f = File(response.json()['file'])
            else:
                f = File(filename=os.path.basename(filepath), stream=fd)

            f.analyze(self['groups'], self['analyst'], None, self['options'])

        fd.close()

        self.append_to('extracted_files', f['_id'])
        f.add_parent_analysis(self)
예제 #3
0
class Analysis(MongoDict):
    STATUS_ERROR = 'error'
    STATUS_PENDING = 'pending'
    STATUS_RUNNING = 'running'
    STATUS_FINISHED = 'finished'

    collection_name = 'analysis'

    def __init__(self, values):
        self['status'] = self.STATUS_PENDING
        self['executed_modules'] = []
        self['pending_modules'] = []
        self['waiting_modules'] = []
        self['canceled_modules'] = []
        self['tags'] = []
        self['iocs'] = []
        self['results'] = {}
        self['generated_files'] = {}
        self['extracted_files'] = []
        self['support_files'] = {}
        self['logs'] = []
        self['extractions'] = []
        self['probable_names'] = []
        self['options'] = {}
        self['date'] = datetime.datetime.now()
        self['end_date'] = None
        self['groups'] = []
        self['analyst'] = []
        MongoDict.__init__(self, values)

        self._file = File(store.files.find_one({'_id': self['file']}))

        if '_id' not in self:
            self._init_threat_intelligence()
            self.save()

            if self['modules']:
                self.queue_modules(self['modules'])
            else:
                self._automatic()

    def magic_enabled(self):
        return ('magic_enabled'
                not in self['options']) or (self['options']['magic_enabled'])

    def add_generated_files(self, file_type, locations):
        # First, save the files to db / storage
        if file_type not in self['generated_files']:
            self['generated_files'][file_type] = []

        for location in iterify(locations):
            if fame_config.remote:
                response = send_file_to_remote(
                    location,
                    '/analyses/{}/generated_file'.format(self['_id']))
                filepath = response.json()['path']
            else:
                filepath = location

            self.log(
                'debug', u"Adding generated file '{0}' of type '{1}'".format(
                    filepath, file_type))
            self.append_to(['generated_files', file_type], filepath)

        # Then, trigger registered modules if magic is enabled
        if self.magic_enabled():
            self.queue_modules(
                dispatcher.triggered_by("_generated_file(%s)" % file_type))

    def add_extracted_file(self, filepath, automatic_analysis=True):
        self.log('debug', u"Adding extracted file '{}'".format(filepath))

        fd = open(filepath, 'rb')
        filename = os.path.basename(filepath)
        f = File(filename=filename, stream=fd, create=False)

        if not f.existing:
            if fame_config.remote:
                response = send_file_to_remote(filepath, '/files/')
                f = File(response.json()['file'])
            else:
                f = File(filename=os.path.basename(filepath), stream=fd)

            # Automatically analyze extracted file if magic is enabled and module did not disable it
            if self.magic_enabled() and automatic_analysis:
                modules = None
                config = Config.get(name="extracted").get_values()
                if config is not None and "modules" in config:
                    modules = config["modules"].split()
                f.analyze(self['groups'], self['analyst'], modules,
                          self['options'])

        fd.close()

        self.append_to('extracted_files', f['_id'])
        f.add_parent_analysis(self)

    def change_type(self, filepath, new_type):
        if self.get_main_file() == filepath:
            self._file.update_value('type', new_type)

            # Automatically re-analyze file if magic is enabled
            if self.magic_enabled():
                self._file.analyze(self['groups'], self['analyst'], None,
                                   self['options'])
        else:
            self.log(
                'warning',
                u"Tried to change type of generated file '{}'".format(
                    filepath))

    def add_support_file(self, module_name, name, filepath):
        self.log('debug',
                 "Adding support file '{}' at '{}'".format(name, filepath))

        if fame_config.remote:
            response = send_file_to_remote(
                filepath,
                '/analyses/{}/support_file/{}'.format(self['_id'],
                                                      module_name))
            dstfilepath = response.json()['path']
        else:
            dirpath = os.path.join(fame_config.storage_path, 'support_files',
                                   module_name, str(self['_id']))
            dstfilepath = os.path.join(dirpath, os.path.basename(filepath))

            # Create parent dirs if they don't exist
            try:
                os.makedirs(dirpath)
            except:
                pass

            copy(filepath, dstfilepath)

        if module_name not in self['support_files']:
            self['support_files'][module_name] = []

        self.append_to(['support_files', module_name],
                       (name, os.path.basename(dstfilepath)))

    def add_extraction(self, label, extraction):
        extraction_object = {'label': label, 'content': u(extraction)}
        self.append_to('extractions', extraction_object)

    def add_probable_name(self, probable_name):
        for name in self['probable_names']:
            if name.find(probable_name) != -1 or probable_name.find(
                    name) != -1:
                break
        else:
            self._file.add_probable_name(probable_name)
            self.append_to('probable_names', probable_name)

    def add_ioc(self, value, source, tags=[]):
        # First, we need to make sure there is a record for this IOC
        r = self.collection.update_one(
            {
                '_id': self['_id'],
                'iocs.value': {
                    '$ne': value
                }
            }, {
                '$push': {
                    'iocs': {
                        'value': value,
                        'tags': [],
                        'ti_tags': [],
                        'ti_indicators': [],
                        'sources': []
                    }
                }
            })

        # If this is the first time we are adding this IOC, lookup Threat Intelligence data
        if r.modified_count == 1:
            ti_tags, ti_indicators = self._lookup_ioc(value)
            # If we have Threat Intelligence data, enrich analysis
            if ti_tags:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$addToSet': {
                        'iocs.$.ti_tags': {
                            '$each': ti_tags
                        }
                    }})

            if ti_indicators:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {
                        '$addToSet': {
                            'iocs.$.ti_indicators': {
                                '$each': ti_indicators
                            }
                        }
                    })

        # Then add tags to the list
        self.collection.update_one({
            '_id': self['_id'],
            'iocs.value': value
        }, {'$addToSet': {
            'iocs.$.tags': {
                '$each': iterify(tags)
            }
        }})

        # Finally, add the source
        self.collection.update_one({
            '_id': self['_id'],
            'iocs.value': value
        }, {'$addToSet': {
            'iocs.$.sources': source
        }})

    # Starts / Resumes an analysis to reach the target module
    def resume(self):
        was_resumed = False

        # First, see if there is pending modules remaining
        if self._run_pending_modules():
            was_resumed = True
        else:
            # If not, look for a path to a waiting module
            for module in self['waiting_modules']:
                try:
                    next_module = dispatcher.next_module(
                        self._types_available(), module, self._tried_modules())
                    self.queue_modules(next_module)
                    was_resumed = True
                except DispatchingException:
                    self.remove_from('waiting_modules', module)
                    self.append_to('canceled_modules', module)
                    self.log(
                        'warning',
                        'could not find execution path to "{}" (cancelled)'.
                        format(module))

        if not was_resumed and self['status'] != self.STATUS_ERROR:
            self._mark_as_finished()

    # Queue execution of specific module(s)
    def queue_modules(self, modules, fallback_waiting=True):
        for module_name in iterify(modules):
            self.log("debug",
                     "Trying to queue module '{0}'".format(module_name))
            if module_name not in self[
                    'executed_modules'] and module_name not in self[
                        'pending_modules']:
                module = self._get_module(module_name)
                if module is None:
                    self._error_with_module(
                        module_name, "module has been removed or disabled.")
                else:
                    if self._can_execute_module(module):
                        if self.append_to('pending_modules', module_name):
                            run_module.apply_async((self['_id'], module_name),
                                                   queue=module.info['queue'])
                    elif fallback_waiting:
                        self.append_to('waiting_modules', module_name)

    # Run specific module, should only be executed on celery worker
    def run(self, module_name):
        self.log('debug', "Trying to run {0}".format(module_name))
        print "Trying to run {0}".format(module_name)

        # This test prevents multiple execution of the same module
        if self.append_to('executed_modules', module_name):
            module = self._get_module(module_name)

            if module is None:
                self._error_with_module(
                    module_name, "module has been removed or disabled.")
            else:
                try:
                    module.initialize()

                    self.update_value('status', self.STATUS_RUNNING)

                    if module.execute(self):
                        # Save results, if any
                        if module.results is not None:
                            self.update_value(['results', module_name],
                                              module.results)

                        # Save tags, and queue triggered modules
                        for tag in module.tags:
                            tag_string = "%s(%s)" % (module_name, tag)
                            self.add_tag(tag_string)

                        self.add_tag(module_name)

                    self.log('debug', "Done with {0}".format(module_name))
                except Exception, e:
                    self._error_with_module(module_name, str(e))

            self.remove_from('pending_modules', module_name)
            self.remove_from('waiting_modules', module_name)

        self.resume()
예제 #4
0
class Analysis(MongoDict):
    STATUS_ERROR = 'error'
    STATUS_PENDING = 'pending'
    STATUS_PRELOADING = 'preloading'
    STATUS_RUNNING = 'running'
    STATUS_FINISHED = 'finished'

    collection_name = 'analysis'

    def __init__(self, values):
        self['status'] = self.STATUS_PENDING
        self['executed_modules'] = []
        self['pending_modules'] = []
        self['waiting_modules'] = []
        self['canceled_modules'] = []
        self['preloading_modules'] = []
        self['tags'] = []
        self['iocs'] = []
        self['results'] = {}
        self['generated_files'] = {}
        self['extracted_files'] = []
        self['support_files'] = {}
        self['logs'] = []
        self['extractions'] = []
        self['probable_names'] = []
        self['options'] = {}
        self['date'] = datetime.datetime.now()
        self['end_date'] = None
        self['groups'] = []
        self['analyst'] = []
        MongoDict.__init__(self, values)

        self._file = File(store.files.find_one({'_id': self['file']}))

        if '_id' not in self:
            self._init_threat_intelligence()

            # Sort preloading and processing modules
            if self['modules']:
                processing = []
                for module_name in self['modules']:
                    module = dispatcher.get_module(module_name)
                    if module is not None:
                        if module.info['type'] == "Preloading":
                            self['preloading_modules'].append(module_name)
                        else:
                            processing.append(module_name)

                self['modules'] = processing

            self.save()

            if self['modules']:
                self.queue_modules(self['modules'])

            self._automatic()
            self.resume()

    # can/will be overridden by the worker implementation
    def _get_generated_file_path(self, location):
        return location

    def magic_enabled(self):
        return ('magic_enabled'
                not in self['options']) or (self['options']['magic_enabled'])

    def add_generated_files(self, file_type, locations):
        # First, save the files to db / storage
        if file_type not in self['generated_files']:
            self['generated_files'][file_type] = []

        for location in iterify(locations):
            location = self._get_generated_file_path(location)
            self.log(
                'debug', u"Adding generated file '{0}' of type '{1}'".format(
                    location, file_type))
            self.append_to(['generated_files', file_type], location)

        # Then, trigger registered modules if magic is enabled
        if self.magic_enabled():
            self.queue_modules(
                dispatcher.triggered_by("_generated_file(%s)" % file_type))

    # can/will be overridden by the worker implementation
    def _get_file_from_filepath(self, filepath, fd):
        return File(filename=os.path.basename(filepath), stream=fd)

    def add_extracted_file(self, filepath, automatic_analysis=True):
        self.log('debug', u"Adding extracted file '{}'".format(filepath))

        fd = open(filepath, 'rb')
        filename = os.path.basename(filepath)
        f = File(filename=filename, stream=fd, create=False)

        if not f.existing:
            f = self._get_file_from_filepath(filepath, fd)

            # Automatically analyze extracted file if magic is enabled and module did not disable it
            if self.magic_enabled() and automatic_analysis:
                modules = []
                config = Config.get(name="extracted").get_values()
                if config is not None and "modules" in config:
                    modules = config["modules"].split()
                f.analyze(self['groups'], self['analyst'], modules,
                          self['options'])
        fd.close()
        f.add_groups(self['groups'])

        self.append_to('extracted_files', f['_id'])
        f.add_parent_analysis(self)

    def change_type(self, filepath, new_type):
        if self.get_main_file() == filepath:
            self._file.update_value('type', new_type)

            # Automatically re-analyze file if magic is enabled
            if self.magic_enabled():
                self._file.analyze(self['groups'], self['analyst'], None,
                                   self['options'])
        else:
            self.log(
                'warning',
                u"Tried to change type of generated file '{}'".format(
                    filepath))

    # can/will be overridden by the worker implementation
    def _store_support_file(self, filepath, module_name):
        dirpath = os.path.join(fame_config.storage_path, 'support_files',
                               module_name, str(self['_id']))
        dstfilepath = os.path.join(dirpath, os.path.basename(filepath))

        # Create parent dirs if they don't exist
        try:
            os.makedirs(dirpath)
        except OSError:
            pass

        copy(filepath, dstfilepath)

        return dstfilepath

    def add_support_file(self, module_name, name, filepath):
        self.log('debug',
                 "Adding support file '{}' at '{}'".format(name, filepath))

        dstfilepath = self._store_support_file(filepath, module_name)

        if module_name not in self['support_files']:
            self['support_files'][module_name] = []

        self.append_to(['support_files', module_name],
                       (name, os.path.basename(dstfilepath)))

    def add_extraction(self, label, extraction):
        extraction_object = {'label': label, 'content': u(extraction)}
        self.append_to('extractions', extraction_object)

    def add_probable_name(self, probable_name):
        for name in self['probable_names']:
            if name.find(probable_name) != -1 or probable_name.find(
                    name) != -1:
                break
        else:
            self._file.add_probable_name(probable_name)
            self.append_to('probable_names', probable_name)

    def refresh_iocs(self):
        for ioc in self["iocs"]:
            value = ioc["value"]
            ti_tags, ti_indicators = self._lookup_ioc(value)
            if ti_tags:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$set': {
                        'iocs.$.ti_tags': ti_tags
                    }})

            if ti_indicators:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$set': {
                        'iocs.$.ti_indicators': ti_indicators
                    }})

            ioc["ti_tags"] = ti_tags
            ioc["ti_indicators"] = ti_indicators

    def add_ioc(self, value, source, tags=[]):
        # First, we need to make sure there is a record for this IOC
        r = self.collection.update_one(
            {
                '_id': self['_id'],
                'iocs.value': {
                    '$ne': value
                }
            }, {
                '$push': {
                    'iocs': {
                        'value': value,
                        'tags': [],
                        'ti_tags': [],
                        'ti_indicators': [],
                        'sources': []
                    }
                }
            })

        # If this is the first time we are adding this IOC, lookup Threat Intelligence data
        if r.modified_count == 1:
            ti_tags, ti_indicators = self._lookup_ioc(value)
            # If we have Threat Intelligence data, enrich analysis
            if ti_tags:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$addToSet': {
                        'iocs.$.ti_tags': {
                            '$each': ti_tags
                        }
                    }})

            if ti_indicators:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {
                        '$addToSet': {
                            'iocs.$.ti_indicators': {
                                '$each': ti_indicators
                            }
                        }
                    })

        # Then add tags to the list
        self.collection.update_one({
            '_id': self['_id'],
            'iocs.value': value
        }, {'$addToSet': {
            'iocs.$.tags': {
                '$each': iterify(tags)
            }
        }})

        # Finally, add the source
        self.collection.update_one({
            '_id': self['_id'],
            'iocs.value': value
        }, {'$addToSet': {
            'iocs.$.sources': source
        }})

    def _store_preloaded_file(self, filepath=None, fd=None):
        if not filepath and not fd:
            raise ValueError(
                "Please provide either the path to the file or a file-like "
                "object containing the data.")

        if filepath and fd:
            self.log(
                "debug", "Please provide either the path to the file or a "
                "file-like object containing the data, not both."
                "Choosing the filepath for now.")

        if fame_config.remote:
            if filepath:
                response = send_file_to_remote(filepath, '/files/')
            else:
                response = send_file_to_remote(fd, '/files/')

            return File(response.json()['file'])
        else:
            if filepath:
                with open(filepath, 'rb') as f:
                    return File(filename=os.path.basename(filepath), stream=f)
            else:
                return File(filename=self._file['names'][0], stream=fd)

    def add_preloaded_file(self, filepath, fd):
        f = self._store_preloaded_file(filepath, fd)
        self._file = f

        if f['_id'] != self['file']:
            f.append_to('analysis', self['_id'])

            if f['names'] == ['file']:
                f['names'] = self._file['names']
                f.save()

            self['file'] = f['_id']
            self.save()

        # Queue general purpose modules if necessary
        self._automatic()

    # Starts / Resumes an analysis to reach the target module
    def resume(self):
        was_resumed = False

        # First, see if there is pending modules remaining
        if self._run_pending_modules():
            was_resumed = True
        # If not and there is no file, look for a preloading module
        elif self._needs_preloading():
            try:
                next_module = dispatcher.next_preloading_module(
                    self['preloading_modules'], self._tried_modules())
                self.queue_modules(next_module)
                was_resumed = True
            except DispatchingException:
                self.log(
                    'warning',
                    'no preloading module was able to find a file for submitted hash'
                )

                for module in list(self['waiting_modules']):
                    self._cancel_module(module)
        # If not, look for a path to a waiting module
        else:
            for module in list(self['waiting_modules']):
                try:
                    next_module = dispatcher.next_module(
                        self._types_available(), module, self._tried_modules())
                    self.queue_modules(next_module)
                    was_resumed = True
                except DispatchingException:
                    self._cancel_module(module)

        if not was_resumed and self['status'] != self.STATUS_ERROR:
            self._mark_as_finished()

    def _cancel_module(self, module):
        self.remove_from('waiting_modules', module)
        self.append_to('canceled_modules', module)
        self.log(
            'warning',
            'could not find execution path to "{}" (cancelled)'.format(module))

    # Queue execution of specific module(s)
    def queue_modules(self, modules, fallback_waiting=True):
        for module_name in iterify(modules):
            self.log("debug",
                     "Trying to queue module '{0}'".format(module_name))
            if (module_name not in self['executed_modules']
                    and module_name not in self['pending_modules']):
                module = dispatcher.get_module(module_name)

                if module is None:
                    self._error_with_module(
                        module_name, "module has been removed or disabled.")
                else:
                    if self._can_execute_module(module):
                        if self.append_to('pending_modules', module_name):
                            celery.send_task('run_module',
                                             args=(self['_id'], module_name),
                                             queue=module.info['queue'])
                    elif fallback_waiting:
                        self.append_to('waiting_modules', module_name)

    def add_tag(self, tag):
        self.append_to('tags', tag)

        # Queue triggered modules if magic is enabled
        if self.magic_enabled():
            self.queue_modules(dispatcher.triggered_by(tag))

    def log(self, level, message):
        message = "%s: %s: %s" % (
            datetime.datetime.now().strftime("%Y-%m-%d %H:%M"), level, message)
        self.append_to('logs', message, set_=False)

    def filepath(self, path):
        return path

    def get_main_file(self):
        filepath = self._file['filepath']
        if self._needs_preloading():
            return filepath
        return self.filepath(filepath)

    def get_files(self, file_type):
        results = []

        if file_type in self['generated_files']:
            for filepath in self['generated_files'][file_type]:
                results.append(self.filepath(filepath))

        if self._file['type'] == file_type:
            results.append(self.get_main_file())

        return results

    def _reporting_hook(self, hook_name):
        for module in dispatcher.get_reporting_modules():
            try:
                getattr(module, hook_name)(self)
            except Exception, e:
                self.log(
                    'error', "error in reporting module '{0}': {1}".format(
                        module.name, e))
예제 #5
0
class Analysis(MongoDict):
    STATUS_ERROR = 'error'
    STATUS_PENDING = 'pending'
    STATUS_PRELOADING = 'preloading'
    STATUS_RUNNING = 'running'
    STATUS_FINISHED = 'finished'

    collection_name = 'analysis'

    def __init__(self, values):
        self['status'] = self.STATUS_PENDING
        self['executed_modules'] = []
        self['pending_modules'] = []
        self['waiting_modules'] = []
        self['canceled_modules'] = []
        self['preloading_modules'] = []
        self['tags'] = []
        self['iocs'] = []
        self['results'] = {}
        self['generated_files'] = {}
        self['extracted_files'] = []
        self['support_files'] = {}
        self['logs'] = []
        self['extractions'] = []
        self['probable_names'] = []
        self['options'] = {}
        self['date'] = datetime.datetime.now()
        self['end_date'] = None
        self['groups'] = []
        self['analyst'] = []
        MongoDict.__init__(self, values)

        self._file = File(store.files.find_one({'_id': self['file']}))

        if '_id' not in self:
            self._init_threat_intelligence()

            # Sort preloading and processing modules
            if self['modules']:
                processing = []
                for module_name in self['modules']:
                    module = dispatcher.get_module(module_name)
                    if module is not None:
                        if module.info['type'] == "Preloading":
                            self['preloading_modules'].append(module_name)
                        else:
                            processing.append(module_name)

                self['modules'] = processing

            self.save()

            if self['modules']:
                self.queue_modules(self['modules'])

            self._automatic()
            self.resume()

    def magic_enabled(self):
        return ('magic_enabled'
                not in self['options']) or (self['options']['magic_enabled'])

    def add_generated_files(self, file_type, locations):
        # First, save the files to db / storage
        if file_type not in self['generated_files']:
            self['generated_files'][file_type] = []

        for location in iterify(locations):
            if fame_config.remote:
                response = send_file_to_remote(
                    location,
                    '/analyses/{}/generated_file'.format(self['_id']))
                filepath = response.json()['path']
            else:
                filepath = location

            self.log(
                'debug', "Adding generated file '{0}' of type '{1}'".format(
                    filepath, file_type))
            self.append_to(['generated_files', file_type], filepath)

        # Then, trigger registered modules if magic is enabled
        if self.magic_enabled():
            self.queue_modules(
                dispatcher.triggered_by("_generated_file(%s)" % file_type))

    def add_extracted_file(self, filepath, automatic_analysis=True):
        self.log('debug', "Adding extracted file '{}'".format(filepath))

        fd = open(filepath, 'rb')
        filename = os.path.basename(filepath)
        f = File(filename=filename, stream=fd, create=False)

        if not f.existing:
            if fame_config.remote:
                response = send_file_to_remote(filepath, '/files/')
                f = File(response.json()['file'])
            else:
                f = File(filename=os.path.basename(filepath), stream=fd)

            # Automatically analyze extracted file if magic is enabled and module did not disable it
            if self.magic_enabled() and automatic_analysis:
                modules = []
                config = Config.get(name="extracted").get_values()
                if config is not None and "modules" in config:
                    modules = config["modules"].split()
                f.analyze(self['groups'], self['analyst'], modules,
                          self['options'])
        fd.close()
        f.add_groups(self['groups'])

        self.append_to('extracted_files', f['_id'])
        f.add_parent_analysis(self)

    def change_type(self, filepath, new_type):
        if self.get_main_file() == filepath:
            self._file.update_value('type', new_type)

            # Automatically re-analyze file if magic is enabled
            if self.magic_enabled():
                self._file.analyze(self['groups'], self['analyst'], None,
                                   self['options'])
        else:
            self.log(
                'warning',
                "Tried to change type of generated file '{}'".format(filepath))

    def add_support_file(self, module_name, name, filepath):
        self.log('debug',
                 "Adding support file '{}' at '{}'".format(name, filepath))

        if fame_config.remote:
            response = send_file_to_remote(
                filepath,
                '/analyses/{}/support_file/{}'.format(self['_id'],
                                                      module_name))
            dstfilepath = response.json()['path']
        else:
            dirpath = os.path.join(fame_config.storage_path, 'support_files',
                                   module_name, str(self['_id']))
            dstfilepath = os.path.join(dirpath, os.path.basename(filepath))

            # Create parent dirs if they don't exist
            try:
                os.makedirs(dirpath)
            except:
                pass

            copy(filepath, dstfilepath)

        if module_name not in self['support_files']:
            self['support_files'][module_name] = []

        self.append_to(['support_files', module_name],
                       (name, os.path.basename(dstfilepath)))

    def add_extraction(self, label, extraction):
        extraction_object = {'label': label, 'content': u(extraction)}
        self.append_to('extractions', extraction_object)

    def add_probable_name(self, probable_name):
        for name in self['probable_names']:
            if name.find(probable_name) != -1 or probable_name.find(
                    name) != -1:
                break
        else:
            self._file.add_probable_name(probable_name)
            self.append_to('probable_names', probable_name)

    def refresh_iocs(self):
        for ioc in self["iocs"]:
            value = ioc["value"]
            ti_tags, ti_indicators = self._lookup_ioc(value)
            if ti_tags:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$set': {
                        'iocs.$.ti_tags': ti_tags
                    }})

            if ti_indicators:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$set': {
                        'iocs.$.ti_indicators': ti_indicators
                    }})

            ioc["ti_tags"] = ti_tags
            ioc["ti_indicators"] = ti_indicators

    def add_ioc(self, value, source, tags=[]):
        # First, we need to make sure there is a record for this IOC
        r = self.collection.update_one(
            {
                '_id': self['_id'],
                'iocs.value': {
                    '$ne': value
                }
            }, {
                '$push': {
                    'iocs': {
                        'value': value,
                        'tags': [],
                        'ti_tags': [],
                        'ti_indicators': [],
                        'sources': []
                    }
                }
            })

        # If this is the first time we are adding this IOC, lookup Threat Intelligence data
        if r.modified_count == 1:
            ti_tags, ti_indicators = self._lookup_ioc(value)
            # If we have Threat Intelligence data, enrich analysis
            if ti_tags:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {'$addToSet': {
                        'iocs.$.ti_tags': {
                            '$each': ti_tags
                        }
                    }})

            if ti_indicators:
                self.collection.update_one(
                    {
                        '_id': self['_id'],
                        'iocs.value': value
                    }, {
                        '$addToSet': {
                            'iocs.$.ti_indicators': {
                                '$each': ti_indicators
                            }
                        }
                    })

        # Then add tags to the list
        self.collection.update_one({
            '_id': self['_id'],
            'iocs.value': value
        }, {'$addToSet': {
            'iocs.$.tags': {
                '$each': iterify(tags)
            }
        }})

        # Finally, add the source
        self.collection.update_one({
            '_id': self['_id'],
            'iocs.value': value
        }, {'$addToSet': {
            'iocs.$.sources': source
        }})

    def _store_preloaded_file(self, filepath=None, fd=None):
        if not filepath and not fd:
            raise ValueError(
                "Please provide either the path to the file or a file-like "
                "object containing the data.")

        if filepath and fd:
            self.log(
                "debug", "Please provide either the path to the file or a "
                "file-like object containing the data, not both."
                "Choosing the filepath for now.")

        if fame_config.remote:
            if filepath:
                response = send_file_to_remote(filepath, '/files/')
            else:
                response = send_file_to_remote(fd, '/files/')

            return File(response.json()['file'])
        else:
            if filepath:
                with open(filepath, 'rb') as f:
                    return File(filename=os.path.basename(filepath), stream=f)
            else:
                return File(filename=self._file['names'][0], stream=fd)

    def add_preloaded_file(self, filepath, fd):
        f = self._store_preloaded_file(filepath, fd)
        self._file = f

        if f['_id'] != self['file']:
            f.append_to('analysis', self['_id'])

            if f['names'] == ['file']:
                f['names'] = self._file['names']
                f.save()

            self['file'] = f['_id']
            self.save()

        # Queue general purpose modules if necessary
        self._automatic()

    # Starts / Resumes an analysis to reach the target module
    def resume(self):
        was_resumed = False

        # First, see if there is pending modules remaining
        if self._run_pending_modules():
            was_resumed = True
        # If not and there is no file, look for a preloading module
        elif self._needs_preloading():
            try:
                next_module = dispatcher.next_preloading_module(
                    self['preloading_modules'], self._tried_modules())
                self.queue_modules(next_module)
                was_resumed = True
            except DispatchingException:
                self.log(
                    'warning',
                    'no preloading module was able to find a file for submitted hash'
                )

                for module in list(self['waiting_modules']):
                    self._cancel_module(module)
        # If not, look for a path to a waiting module
        else:
            for module in list(self['waiting_modules']):
                try:
                    next_module = dispatcher.next_module(
                        self._types_available(), module, self._tried_modules())
                    self.queue_modules(next_module)
                    was_resumed = True
                except DispatchingException:
                    self._cancel_module(module)

        if not was_resumed and self['status'] != self.STATUS_ERROR:
            self._mark_as_finished()

    def _cancel_module(self, module):
        self.remove_from('waiting_modules', module)
        self.append_to('canceled_modules', module)
        self.log(
            'warning',
            'could not find execution path to "{}" (cancelled)'.format(module))

    # Queue execution of specific module(s)
    def queue_modules(self, modules, fallback_waiting=True):
        for module_name in iterify(modules):
            self.log("debug",
                     "Trying to queue module '{0}'".format(module_name))
            if module_name not in self[
                    'executed_modules'] and module_name not in self[
                        'pending_modules']:
                module = dispatcher.get_module(module_name)

                if module is None:
                    self._error_with_module(
                        module_name, "module has been removed or disabled.")
                else:
                    if self._can_execute_module(module):
                        if self.append_to('pending_modules', module_name):
                            run_module.apply_async((self['_id'], module_name),
                                                   queue=module.info['queue'])
                    elif fallback_waiting:
                        self.append_to('waiting_modules', module_name)

    # Run specific module, should only be executed on celery worker
    def run(self, module_name):
        self.log('debug', "Trying to run {0}".format(module_name))
        print(("Trying to run {0}".format(module_name)))

        # This test prevents multiple execution of the same module
        if self.append_to('executed_modules', module_name):
            module = dispatcher.get_module(module_name)

            if module is None:
                self._error_with_module(
                    module_name, "module has been removed or disabled.")
            else:
                try:
                    module.initialize()

                    if module.info['type'] == "Preloading":
                        self.update_value('status', self.STATUS_PRELOADING)
                    else:
                        self.update_value('status', self.STATUS_RUNNING)

                    if module.execute(self):
                        # Save results, if any
                        if module.results is not None:
                            self.update_value(['results', module_name],
                                              module.results)

                        # Save tags, and queue triggered modules
                        for tag in module.tags:
                            tag_string = "%s(%s)" % (module_name, tag)
                            self.add_tag(tag_string)

                        self.add_tag(module_name)

                    self.log('debug', "Done with {0}".format(module_name))
                except Exception:
                    tb = traceback.format_exc()
                    self._error_with_module(module_name, tb)

            self.remove_from('pending_modules', module_name)
            self.remove_from('waiting_modules', module_name)

        self.resume()

    def add_tag(self, tag):
        self.append_to('tags', tag)

        # Queue triggered modules if magic is enabled
        if self.magic_enabled():
            self.queue_modules(dispatcher.triggered_by(tag))

    def log(self, level, message):
        message = "%s: %s: %s" % (
            datetime.datetime.now().strftime("%Y-%m-%d %H:%M"), level, message)
        self.append_to('logs', message)

    # This will give the correct and locally valid filepath of given file
    # When on a remote worker, the file needs to be retrieved first
    def filepath(self, path):
        if fame_config.remote:
            pathhash = md5(path.encode('utf-8')).hexdigest()
            local_path = os.path.join(fame_config.storage_path, pathhash)
            if not os.path.isfile(local_path):
                # Make sure fame_config.storage_path exists
                try:
                    os.makedirs(fame_config.storage_path)
                except Exception:
                    pass

                url = urljoin(
                    fame_config.remote,
                    '/analyses/{}/get_file/{}'.format(self['_id'], pathhash))
                response = requests.get(
                    url,
                    stream=True,
                    headers={'X-API-KEY': fame_config.api_key})
                response.raise_for_status()
                f = open(local_path, 'ab')
                for chunk in response.iter_content(1024):
                    f.write(chunk)

                f.close()

            return local_path
        else:
            return path

    def get_main_file(self):
        filepath = self._file['filepath']
        if self._needs_preloading():
            return filepath
        return self.filepath(filepath)

    def get_files(self, file_type):
        results = []

        if file_type in self['generated_files']:
            for filepath in self['generated_files'][file_type]:
                results.append(self.filepath(filepath))

        if self._file['type'] == file_type:
            results.append(self.get_main_file())

        return results

    def _reporting_hook(self, hook_name):
        for module in dispatcher.get_reporting_modules():
            try:
                getattr(module, hook_name)(self)
            except Exception as e:
                self.log(
                    'error', "error in reporting module '{0}': {1}".format(
                        module.name, e))

    def _lookup_ioc(self, ioc):
        ti_tags = []
        ti_indicators = []

        for module in dispatcher.get_threat_intelligence_modules():
            try:
                tags, indicators = module.ioc_lookup(ioc)
                ti_tags += tags
                ti_indicators += indicators
            except Exception as e:
                import traceback
                traceback.print_exc()
                self.log(
                    'error',
                    "error in threat intelligence module '{}': {}".format(
                        module.name, e))

        return ti_tags, ti_indicators

    def _init_threat_intelligence(self):
        self['threat_intelligence'] = {}

    def _mark_as_finished(self):
        self.update_value('status', self.STATUS_FINISHED)
        self.update_value('end_date', datetime.datetime.now())
        self._reporting_hook('done')

    def _error(self, reason):
        self.log('error', reason)
        self.update_value('status', self.STATUS_ERROR)
        self._reporting_hook('done')

    def _types_available(self):
        if self._file['type'] in self['generated_files']:
            return list(self['generated_files'].keys())
        else:
            return list(self['generated_files'].keys()) + [self._file['type']]

    def _needs_preloading(self):
        return self._file['type'] == 'hash'

    # Determine if a module could be run on the current status of analysis
    def _can_execute_module(self, module):
        # Only Preloading modules can execute on a hash
        if self._needs_preloading():
            return module.info['type'] == "Preloading"
        # When a file is present, look at acts_on property
        elif 'acts_on' not in module.info or not module.info['acts_on']:
            return True
        else:
            for source_type in iterify(module.info['acts_on']):
                if source_type in self._types_available():
                    return True

            return False

    # Returns True if any module was in the queue
    def _run_pending_modules(self):
        self.refresh()

        if len(self['pending_modules']) == 0:
            return False
        else:
            return True

    def _tried_modules(self):
        return self['executed_modules'] + self['canceled_modules']

    # Automatic analysis
    def _automatic(self):
        # If magic is enabled, schedule general purpose modules
        if self.magic_enabled() and not self['modules']:
            self.queue_modules(dispatcher.general_purpose(), False)

    def _error_with_module(self, module, message):
        self.log("error", "{}: {}".format(module, message))
        self.append_to('canceled_modules', module)
예제 #6
0
    def post(self):
        """Create a new analysis.

        .. :quickref: Analysis; Create an analysis

        Launch a new analysis. You have to specify on which object this analysis
        will be made, by specifying one of:

        * ``file_id`` for an existing object
        * ``file`` for file uploads
        * ``url``
        * ``hash`` if VirusTotal sample retrieval is enabled.

        You should also supply all enabled analysis options with the name
        ``options[OPTION_NAME]``. For boolean options, any value different than
        ``0`` or ``False`` means the option is enabled.

        If the submitted object already exists (and ``file_id`` was not specified),
        the response will be a file object. When a new analysis was successfuly
        created, the analysis object will be returned, in the ``analysis`` field.

        If there was error in your submission, they will be returned in the
        ``errors`` field.

        **Example request**::

            headers = {
                'Accept': "application/json",
                'X-API-KEY': FAME_API_KEY
            }

            with open(filepath, 'rb') as f:
                params = {
                    'options[allow_internet_access]':  "on",
                    'options[analysis_time]': "300",
                    'groups': "cert"
                }

                files = {
                    'file': f
                }

                r = requests.post(ENDPOINT, data=params, files=files, headers=headers)

        :form string file_id: (optional) the id of the object on which this analysis should run.
        :form file file: (optional) file to analyze.
        :form string url: (optional) url to analyze.
        :form string hash: (optional) hash to analyze.
        :form string module: (optional) the name of the target module.
        :form string groups: a comma-separated list of groups that will have access to this analysis.
        :form string comment: comment to add to this object.
        :form string option[*]: value of each enabled option.
        """
        file_id = request.form.get('file_id')
        modules = filter(None, request.form.get('modules', '').split(','))
        groups = request.form.get('groups', '').split(',')
        comment = request.form.get('comment', '')

        options = get_options()
        if options is None:
            return validation_error()

        valid_submission = self._validate_form(groups, modules, options)
        if not valid_submission:
            return validation_error()

        if file_id is not None:
            f = File(get_or_404(current_user.files, _id=file_id))
            analysis = {
                'analysis':
                f.analyze(groups, current_user['_id'], modules, options)
            }
            return redirect(
                analysis,
                url_for('AnalysesView:get', id=analysis['analysis']['_id']))
        else:
            # When this is a new submission, validate the comment
            if not self._validate_comment(comment):
                return validation_error()

            f = self._get_object_to_analyze()
            if f is not None:
                f.add_owners(set(current_user['groups']) & set(groups))

                if comment:
                    f.add_comment(current_user['_id'], comment)

                if f.existing:
                    f.add_groups(groups)
                    flash(
                        "File already exists, so the analysis was not launched."
                    )

                    return redirect(clean_files(f),
                                    url_for('FilesView:get', id=f['_id']))
                else:
                    analysis = {
                        'analysis':
                        clean_analyses(
                            f.analyze(groups, current_user['_id'], modules,
                                      options))
                    }
                    analysis['analysis']['file'] = clean_files(f)

                    return redirect(
                        analysis,
                        url_for('AnalysesView:get',
                                id=analysis['analysis']['_id']))
            else:
                return render_template('analyses/new.html',
                                       options=dispatcher.options)