Esempio n. 1
0
 def detect_imports(rule):
     """Detect imports: DEPRECATED."""
     from plyara.utils import detect_imports
     import warnings
     warnings.warn(
         'Static method detect_imports() is deprecated: use plyara.utils',
         DeprecationWarning)
     return detect_imports(rule)
Esempio n. 2
0
 def test_detect_imports(self):
     for imp in ('androguard', 'cuckoo', 'dotnet', 'elf', 'hash', 'magic',
                 'math', 'pe'):
         with data_dir.joinpath(
                 'import_ruleset_{}.yar'.format(imp)).open('r') as fh:
             inputString = fh.read()
         results = Plyara().parse_string(inputString)
         for rule in results:
             self.assertEqual(detect_imports(rule), [imp])
Esempio n. 3
0
def generate_kwargs_from_parsed_rule(parsed_rule):
    # Generate parsed rule kwargs for saving a rule
    name = parsed_rule['rule_name']
    tags = parsed_rule.get('tags', [])
    scopes = parsed_rule.get('scopes', [])

    # TODO : Update when Plyara moves to clean Python types
    metadata = parsed_rule.get('metadata', {})
    for key, value in metadata.items():
        if value not in ('true', 'false'):
            try:
                value = int(value)
            except ValueError:
                metadata[key] = '"' + value + '"'

    strings = parsed_rule.get('strings', [])
    condition = parsed_rule['condition_terms']

    # TODO : Update when Plyara moves to stripping quotes from detect_imports module
    imports = [imp.strip('"') for imp in utils.detect_imports(parsed_rule)]
    comments = parsed_rule.get('comments', [])
    dependencies = utils.detect_dependencies(parsed_rule)

    # Calculate hash value of rule strings and condition
    logic_hash = utils.generate_logic_hash(parsed_rule)

    # TEMP FIX - Use only a single instance of a metakey
    # until YaraGuardian models and functions can be updated
    for key, value in metadata.items():
        if isinstance(value, list):
            metadata[key] = value[0]

    return {
        'name': name,
        'tags': list(set(tags)),
        'scopes': list(set(scopes)),
        'imports': list(set(imports)),
        'comments': list(set(comments)),
        'metadata': metadata,
        'strings': strings,
        'condition': condition,
        'dependencies': dependencies,
        'logic_hash': logic_hash
    }
    def _save_signatures(self,
                         signatures,
                         source,
                         default_status=DEFAULT_STATUS,
                         default_classification=None):
        if len(signatures) == 0:
            self.log.info(f"There are no signatures for {source}, skipping...")
            return False

        order = 1
        upload_list = []
        for signature in signatures:
            classification = default_classification or self.classification.UNRESTRICTED
            signature_id = None
            version = 1
            status = default_status

            for meta in signature.get('metadata', {}):
                for k, v in meta.items():
                    if k in ["classification", "sharing"]:
                        classification = v
                    elif k in ['id', 'rule_id', 'signature_id']:
                        signature_id = v
                    elif k in ['version', 'rule_version', 'revision']:
                        version = v
                    elif k in ['status', 'al_status']:
                        status = v

            # Convert CCCS YARA status to AL signature status
            if status == "RELEASED":
                status = "DEPLOYED"
            elif status == "DEPRECATED":
                status = "DISABLED"

            # Fallback status
            if status not in [
                    "DEPLOYED", "NOISY", "DISABLED", "STAGING", "TESTING",
                    "INVALID"
            ]:
                status = default_status

            # Fix imports and remove cuckoo
            signature['imports'] = utils.detect_imports(signature)
            if "cuckoo" not in signature['imports']:
                sig = Signature(
                    dict(
                        classification=classification,
                        data=utils.rebuild_yara_rule(signature),
                        name=signature.get('rule_name'),
                        order=order,
                        revision=int(float(version)),
                        signature_id=signature_id
                        or signature.get('rule_name'),
                        source=source,
                        status=status,
                        type=self.importer_type,
                    ))
                upload_list.append(sig.as_primitives())
            else:
                self.log.warning(
                    f"Signature '{signature.get('rule_name')}' skipped because it uses cuckoo module."
                )

            order += 1

        r = self.update_client.signature.add_update_many(
            source, self.importer_type, upload_list)
        self.log.info(
            f"Imported {r['success']}/{order - 1} signatures from {source} into Assemblyline"
        )

        return r['success']
def yara_update(updater_type, update_config_path, update_output_path,
                download_directory, externals, cur_logger) -> None:
    """
    Using an update configuration file as an input, which contains a list of sources, download all the file(s).
    """
    # noinspection PyBroadException
    try:
        # Load updater configuration
        update_config = {}
        if update_config_path and os.path.exists(update_config_path):
            with open(update_config_path, 'r') as yml_fh:
                update_config = yaml.safe_load(yml_fh)
        else:
            cur_logger.error(f"Update configuration file doesn't exist: {update_config_path}")
            exit()

        # Exit if no update sources given
        if 'sources' not in update_config.keys() or not update_config['sources']:
            cur_logger.error(f"Update configuration does not contain any source to update from")
            exit()

        # Initialise al_client
        server = update_config['ui_server']
        user = update_config['api_user']
        api_key = update_config['api_key']
        cur_logger.info(f"Connecting to Assemblyline API: {server}...")
        al_client = get_client(server, apikey=(user, api_key), verify=False)
        cur_logger.info(f"Connected!")

        # Parse updater configuration
        previous_update = update_config.get('previous_update', None)
        previous_hash = json.loads(update_config.get('previous_hash', None) or "{}")
        sources = {source['name']: source for source in update_config['sources']}
        files_sha256 = {}
        files_default_classification = {}

        # Create working directory
        updater_working_dir = os.path.join(tempfile.gettempdir(), 'updater_working_dir')
        if os.path.exists(updater_working_dir):
            shutil.rmtree(updater_working_dir)
        os.makedirs(updater_working_dir)

        # Go through each source and download file
        for source_name, source in sources.items():
            os.makedirs(os.path.join(updater_working_dir, source_name))
            # 1. Download signatures
            cur_logger.info(f"Downloading files from: {source['uri']}")
            uri: str = source['uri']

            if uri.endswith('.git'):
                files = git_clone_repo(download_directory, source, cur_logger, previous_update=previous_update)
            else:
                files = [url_download(download_directory, source, cur_logger, previous_update=previous_update)]

            processed_files = set()

            # 2. Aggregate files
            file_name = os.path.join(updater_working_dir, f"{source_name}.yar")
            mode = "w"
            for file in files:
                # File has already been processed before, skip it to avoid duplication of rules
                if file in processed_files:
                    continue

                cur_logger.info(f"Processing file: {file}")

                file_dirname = os.path.dirname(file)
                processed_files.add(os.path.normpath(file))
                with open(file, 'r') as f:
                    f_lines = f.readlines()

                temp_lines = []
                for i, f_line in enumerate(f_lines):
                    if f_line.startswith("include"):
                        lines, processed_files = replace_include(f_line, file_dirname, processed_files, cur_logger)
                        temp_lines.extend(lines)
                    else:
                        temp_lines.append(f_line)

                # guess the type of files that we have in the current file
                guessed_category = guess_category(file)
                parser = Plyara()
                signatures = parser.parse_string("\n".join(temp_lines))

                # Ignore "cuckoo" rules
                if "cuckoo" in parser.imports:
                    parser.imports.remove("cuckoo")

                # Guess category
                if guessed_category:
                    for s in signatures:
                        if 'metadata' not in s:
                            s['metadata'] = []

                        # Do not override category with guessed category if it already exists
                        for meta in s['metadata']:
                            if 'category' in meta:
                                continue

                        s['metadata'].append({'category': guessed_category})
                        s['metadata'].append({guessed_category: s.get('rule_name')})

                # Save all rules from source into single file
                with open(file_name, mode) as f:
                    for s in signatures:
                        # Fix imports and remove cuckoo
                        s['imports'] = utils.detect_imports(s)
                        if "cuckoo" not in s['imports']:
                            f.write(utils.rebuild_yara_rule(s))

                if mode == "w":
                    mode = "a"

            # Check if the file is the same as the last run
            if os.path.exists(file_name):
                cache_name = os.path.basename(file_name)
                sha256 = get_sha256_for_file(file_name)
                if sha256 != previous_hash.get(cache_name, None):
                    files_sha256[cache_name] = sha256
                    files_default_classification[cache_name] = source.get('default_classification',
                                                                          classification.UNRESTRICTED)
                else:
                    cur_logger.info(f'File {cache_name} has not changed since last run. Skipping it...')

        if files_sha256:
            cur_logger.info(f"Found new {updater_type.upper()} rules files to process!")

            yara_importer = YaraImporter(updater_type, al_client, logger=cur_logger)

            # Validating and importing the different signatures
            for base_file in files_sha256:
                cur_logger.info(f"Validating output file: {base_file}")
                cur_file = os.path.join(updater_working_dir, base_file)
                source_name = os.path.splitext(os.path.basename(cur_file))[0]
                default_classification = files_default_classification.get(base_file, classification.UNRESTRICTED)

                try:
                    _compile_rules(cur_file, externals, cur_logger)
                    yara_importer.import_file(cur_file, source_name, default_classification=default_classification)
                except Exception as e:
                    raise e
        else:
            cur_logger.info(f'No new {updater_type.upper()} rules files to process...')

        # Check if new signatures have been added
        if al_client.signature.update_available(since=previous_update or '', sig_type=updater_type)['update_available']:
            cur_logger.info("An update is available for download from the datastore")

            if not os.path.exists(update_output_path):
                os.makedirs(update_output_path)

            temp_zip_file = os.path.join(update_output_path, 'temp.zip')
            al_client.signature.download(output=temp_zip_file,
                                         query=f"type:{updater_type} AND (status:NOISY OR status:DEPLOYED)")

            if os.path.exists(temp_zip_file):
                with ZipFile(temp_zip_file, 'r') as zip_f:
                    zip_f.extractall(update_output_path)

                os.remove(temp_zip_file)

            # Create the response yaml
            with open(os.path.join(update_output_path, 'response.yaml'), 'w') as yml_fh:
                yaml.safe_dump(dict(hash=json.dumps(files_sha256)), yml_fh)

            cur_logger.info(f"New ruleset successfully downloaded and ready to use")

        cur_logger.info(f"{updater_type.upper()} updater completed successfully")
    except Exception:
        cur_logger.exception("Updater ended with an exception!")