plyara-2.2.8/0000775000175000017500000000000014751212104011120 5ustar rharhaplyara-2.2.8/src/0000775000175000017500000000000014751212104011707 5ustar rharhaplyara-2.2.8/src/ply0000777000175000017500000000000014751212104017317 2../third_party/ply/src/plyustar rharhaplyara-2.2.8/src/plyara/0000775000175000017500000000000014751212104013177 5ustar rharhaplyara-2.2.8/src/plyara/command_line.py0000775000175000017500000000335414751212104016206 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """plyara command line script. This module contains command line script for parsing rules. """ import argparse import json import logging import pathlib import sys import plyara.core def _set_logging(): """Set the console logger.""" logger = logging.getLogger('plyara') logger.setLevel(logging.DEBUG) sh = logging.StreamHandler() sh.setLevel(logging.DEBUG) logger.addHandler(sh) def main(): """Run the command line process to parse a yara rule file and output pretty printed JSON.""" parser = argparse.ArgumentParser(description='Parse YARA rules into a JSON representation.') parser.add_argument('file', metavar='FILE', help='File containing YARA rules to parse.') parser.add_argument('--log', help='Enable debug logging to the console.', action='store_true') args = parser.parse_args() try: input_string = pathlib.Path(args.file).read_text(encoding='utf-8') except FileNotFoundError as e: sys.exit(e) parser = plyara.core.Plyara() if args.log: _set_logging() rules = parser.parse_string(input_string) print(json.dumps(rules, sort_keys=True, indent=4)) plyara-2.2.8/src/plyara/utils.py0000775000175000017500000003513514751212104014723 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """plyara utility functions. This module contains various utility functions for working with plyara output. """ import hashlib import logging import re import warnings from plyara.core import Parser # Initialize the logger logger = logging.getLogger(__name__) def is_valid_rule_name(entry): """Check to see if entry is a valid rule name. Args: entry: String containing rule name. Returns: bool """ warnings.warn( 'Rule name validity checked by parser. is_valid_rule_name will be removed in plyara version 2.3.0', DeprecationWarning ) # Check if entry is blank if not entry: return False # Check length if len(entry) > 128: return False # Ensure doesn't start with a digit if entry[0].isdigit(): return False # Accept only alphanumeric and underscores if not re.match(r'[a-zA-Z_][a-zA-Z_0-9]*$', entry): return False # Verify not in keywords if entry in Parser.KEYWORDS: return False return True def is_valid_rule_tag(entry): """Check to see if entry is a valid rule tag. Args: entry: String containing tag. Returns: bool """ warnings.warn( 'Tag name validity checked by parser. is_valid_rule_tag will be removed in plyara version 2.3.0', DeprecationWarning ) # Same lexical conventions as name return is_valid_rule_name(entry) def detect_imports(rule): """Take a parsed yararule and provide a list of required imports based on condition. Args: rule: Dict output from a parsed rule. Returns: list: Imports that are required. """ warnings.warn( 'Imports now parsed for all rules. detect_imports will be removed in plyara version 2.3.0', DeprecationWarning ) detected_imports = list() condition_terms = rule['condition_terms'] for imp in Parser.IMPORT_OPTIONS: imp_module = '{}.'.format(imp) for term in condition_terms: if term.startswith(imp_module): detected_imports.append(imp) break return detected_imports def detect_dependencies(rule): """Take a parsed yararule and provide a list of external rule dependencies. Args: rule: Dict output from a parsed rule. Returns: list: External rule dependencies. """ warnings.warn( 'Deprecation: detect_dependencies will be removed in plyara version 2.3.0', DeprecationWarning ) dependencies = list() string_iteration_variables = list() condition_terms = rule['condition_terms'] # Number of terms for index iteration and reference term_count = len(condition_terms) for index in range(0, term_count): # Grab term by index term = condition_terms[index] if is_valid_rule_name(term) and (term not in Parser.IMPORT_OPTIONS): # Grab reference to previous term for logic checks if index > 0: previous_term = condition_terms[index - 1] else: previous_term = None # Grab reference to next term for logic checks if index < (term_count - 1): next_term = condition_terms[index + 1] else: next_term = None # Extend term indexes beyond wrapping parentheses for logic checks if previous_term == '(' and next_term == ')': if (index - 2) >= 0: previous_term = condition_terms[index - 2] else: previous_term = None if (index + 2) < term_count: next_term = condition_terms[index + 2] else: next_term = None # Check if reference is a variable for string iteration if term in string_iteration_variables: continue if previous_term in ('any', 'all', ) and next_term == 'in': string_iteration_variables.append(term) continue # Check for external string variable dependency if next_term in ('matches', 'contains', ) or previous_term in ('matches', 'contains', ): continue # Check for external integer variable dependency if next_term in Parser.COMPARISON_OPERATORS or previous_term in Parser.COMPARISON_OPERATORS: continue # Check for external boolean dependency may not be possible without stripping out valid rule references # Checks for likely rule reference if previous_term is None and next_term is None: dependencies.append(term) elif previous_term in ('and', 'or', 'not', ) or next_term in ('and', 'or', 'not', ): dependencies.append(term) return dependencies def generate_hash(rule, legacy=False): """Calculate a secure hash of the logic in the rule strings and condition. If the resultant hashes are identical for two YARA rules, the rules will match on identical content. The reverse it not true, so two rules that match the same content may not generate the same hash. For example, if a rule only contains one string, the logic for 'any of' and 'all of' generate different hashes, but the rules contain the same logic. Args: rule: Dict output from a parsed rule. legacy: Enables legacy mode with no versioning or algo name. Returns: str: hexdigest """ version = 'v1' hf = hashlib.sha256() algo = 'sha256' condition_string_prefaces = ('$', '!', '#', '@') strings = rule.get('strings', list()) conditions = rule['condition_terms'] string_values = list() condition_mapping = list() string_mapping = {'anonymous': list(), 'named': dict()} for entry in strings: name = entry['name'] modifiers = entry.get('modifiers', list()) if entry['type'] == 'byte': value = re.sub(r'[^-a-fA-F?0-9\[\]{}]+', '', entry['value']) elif entry['type'] == 'text': value = '{}'.format(entry['value']) else: value = entry['value'] # Handle string modifiers if modifiers: value += '{}'.format(' & '.join(sorted(modifiers))) if name == '$': # Track anonymous strings string_mapping['anonymous'].append(value) else: # Track named strings string_mapping['named'][name] = value # Track all string values string_values.append(value) # Sort all string values string_values.sort() for cond in conditions: # All string references (sort for consistency) if cond == 'them' or cond == '$*': all_values = '{}'.format(' | '.join(string_values)) if cond == 'them': condition_mapping.extend(['(', all_values, ')']) else: condition_mapping.append(all_values) elif cond in ['#', '@', '!', '$']: condition_mapping.append(cond) elif cond.startswith('$') and cond != '$': # Exact Match if cond in string_mapping['named']: condition_mapping.append('{}'.format(string_mapping['named'][cond])) # Wildcard Match elif '*' in cond: wildcard_strings = list() cond = cond.replace('$', r'\$').replace('*', '.*') pattern = re.compile(cond) for name, value in string_mapping['named'].items(): if pattern.match(name): wildcard_strings.append(value) wildcard_strings.sort() condition_mapping.append('{}'.format(' | '.join(wildcard_strings))) else: condstr = ' '.join(conditions) logger.error(f'Unhandled String Condition "{cond}" in "{condstr}"') # Count Match elif cond[:1] in condition_string_prefaces and cond not in ('#', '!='): symbol = cond[:1] cond = '${}'.format(cond[1:]) if symbol == '#': symbol_type = 'COUNTOFSTRING' elif symbol == '@': symbol_type = 'POSITIONOFSTRING' elif symbol == '!': symbol_type = 'LENGTHOFSTRING' elif symbol == cond == '$': symbol_type = 'ANONYMOUSSTRING' else: symbol_type = 'UNKNOWN' if cond in string_mapping['named']: condition_mapping.append('<{}>{}'.format(symbol_type, string_mapping['named'][cond])) else: condition_mapping.append('<{}>{}'.format(symbol_type, cond)) condstr = ' '.join(conditions) logger.error(f'Unhandled {symbol_type} Condition "{symbol}" in "{condstr}"') else: condition_mapping.append(cond) hf.update(''.join(condition_mapping).encode()) if legacy: hexdigest = hf.hexdigest() else: hexdigest = f'{version}_{algo}_{hf.hexdigest()}' return hexdigest def rebuild_yara_rule(rule, condition_indents=False): """Take a parsed yararule and rebuild it into a usable one. Args: rule: Dict output from a parsed rule. condition_indents: Use nested indentation for condition Returns: str: Formatted text string of YARA rule. """ rule_format = "{imports}{scopes}rule {rulename}{tags}\n{{{meta}{strings}{condition}\n}}\n" rule_name = rule['rule_name'] # Rule Imports if rule.get('imports'): unpacked_imports = ['import "{}"\n'.format(entry) for entry in rule['imports']] rule_imports = '{}\n'.format(''.join(unpacked_imports)) else: rule_imports = str() # Rule Scopes if rule.get('scopes'): rule_scopes = '{} '.format(' '.join(rule['scopes'])) else: rule_scopes = str() # Rule Tags if rule.get('tags'): rule_tags = ' : {}'.format(' '.join(rule['tags'])) else: rule_tags = str() # Rule Metadata if rule.get('metadata'): unpacked_meta = [] kv_list = [(k, ) + (v, ) for dic in rule['metadata'] for k, v in dic.items()] # Check for and handle correctly quoting string metadata for k, v in kv_list: if isinstance(v, bool): v = str(v).lower() elif isinstance(v, int): v = str(v) else: v = '"{}"'.format(v) unpacked_meta.append('\n\t\t{key} = {value}'.format(key=k, value=v)) rule_meta = '\n\tmeta:{}\n'.format(''.join(unpacked_meta)) else: rule_meta = str() # Rule Strings if rule.get('strings'): string_container = list() for rule_string in rule['strings']: if 'modifiers' in rule_string: string_modifiers = [x for x in rule_string['modifiers'] if isinstance(x, str)] if rule_string['type'] == 'text': string_format = '\n\t\t{} = "{}" {}' else: string_format = '\n\t\t{} = {} {}' fstring = string_format.format(rule_string['name'], rule_string['value'], ' '.join(string_modifiers)) else: if rule_string['type'] == 'text': string_format = '\n\t\t{} = "{}"' else: string_format = '\n\t\t{} = {}' fstring = string_format.format(rule_string['name'], rule_string['value']) string_container.append(fstring) rule_strings = '\n\tstrings:{}\n'.format(''.join(string_container)) else: rule_strings = str() if rule.get('condition_terms'): # Format condition with appropriate whitespace between keywords cond = list() indents = '\n\t\t' for term in rule['condition_terms']: if condition_indents: if term == '(': indents = indents + '\t' if term == ')' and len(indents) > 3: indents = indents[:-1] if not cond: if term in Parser.FUNCTION_KEYWORDS: cond.append(term) elif term in Parser.KEYWORDS: cond.append(term) cond.append(' ') else: cond.append(term) else: if cond[-1][-1] in (' ', '\t') and term in Parser.FUNCTION_KEYWORDS: cond.append(term) elif cond[-1][-1] not in (' ', '\t') and term in Parser.FUNCTION_KEYWORDS: cond.append(' ') cond.append(term) elif cond[-1][-1] in (' ', '\t') and term in Parser.KEYWORDS: cond.append(term) cond.append(' ') if condition_indents and term in ('and', 'or'): cond.append(indents) elif cond[-1][-1] not in (' ', '\t') and term in Parser.KEYWORDS: cond.append(' ') cond.append(term) cond.append(' ') if condition_indents and term in ('and', 'or'): cond.append(indents) elif cond[-1][-1] in (' ', '\t') and term == ':': cond.append(term) cond.append(' ') elif cond[-1][-1] not in (' ', '\t') and term == ':': cond.append(' ') cond.append(term) cond.append(' ') else: cond.append(term) cond.append(' ') fcondition = ''.join(cond).rstrip(' ') rule_condition = '\n\tcondition:{}{}'.format('\n\t\t', fcondition) else: rule_condition = str() formatted_rule = rule_format.format( imports=rule_imports, rulename=rule_name, tags=rule_tags, meta=rule_meta, scopes=rule_scopes, strings=rule_strings, condition=rule_condition, ) return formatted_rule plyara-2.2.8/src/plyara/exceptions.py0000775000175000017500000000344614751212104015744 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """plyara exceptions. This module contains the set of plyara's exceptions. """ class ParseError(Exception): """Base parsing error exception type. It stores also the line number and lex position as instance attributes 'lineno' and 'lexpos' respectively. """ def __init__(self, message, lineno, lexpos): """Initialize exception object.""" self.lineno = lineno self.lexpos = lexpos super().__init__(message) class ParseTypeError(ParseError): """Error emmited during parsing when a wrong token type is encountered. It stores also the line number and lex position as instance attributes 'lineno' and 'lexpos' respectively. """ def __init__(self, message, lineno, lexpos): """Initialize exception object.""" super().__init__(message, lineno, lexpos) class ParseValueError(ParseError): """Error emmited during parsing when a wrong value is encountered. It stores also the line number and lex position as instance attributes 'lineno' and 'lexpos' respectively. """ def __init__(self, message, lineno, lexpos): """Initialize exception object.""" super().__init__(message, lineno, lexpos) plyara-2.2.8/src/plyara/core.py0000775000175000017500000011607214751212104014513 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Parse YARA rules and operate over them more easily. Plyara is a script and library that lexes and parses a file consisting of one more YARA rules into a python dictionary representation. The goal of this tool is to make it easier to perform bulk operations or transformations of large sets of YARA rules, such as extracting indicators, updating attributes, and analyzing a corpus. Other applications include linters and dependency checkers. """ import enum import logging import re import string import ply.lex as lex import ply.yacc as yacc from plyara.exceptions import ParseTypeError, ParseValueError # Initialize the logger logger = logging.getLogger(__name__) class ElementTypes(enum.Enum): """An enumeration of the element types emitted by the parser to the interpreter.""" RULE_NAME = 1 METADATA_KEY_VALUE = 2 STRINGS_KEY_VALUE = 3 STRINGS_MODIFIER = 4 IMPORT = 5 TERM = 6 SCOPE = 7 TAG = 8 INCLUDE = 9 COMMENT = 10 MCOMMENT = 11 class StringTypes(enum.Enum): """String types found in a YARA rule.""" TEXT = 1 BYTE = 2 REGEX = 3 class Parser: """Interpret the output of the parser and produce an alternative representation of YARA rules.""" COMPARISON_OPERATORS = {'==', '!=', '>', '<', '>=', '<='} IMPORT_OPTIONS = { 'androguard', 'console', 'cuckoo', 'dex', 'dotnet', 'elf', 'hash', 'lnk', 'macho', 'magic', 'math', 'pe', 'string', 'time', 'vt' } KEYWORDS = { 'all', 'and', 'any', 'ascii', 'at', 'base64', 'base64wide', 'condition', 'contains', 'defined', 'endswith', 'entrypoint', 'false', 'filesize', 'for', 'fullword', 'global', 'icontains', 'iendswith', 'iequals', 'import', 'in', 'include', 'int16', 'int16be', 'int32', 'int32be', 'int8', 'int8be', 'istartswith', 'matches', 'meta', 'nocase', 'none', 'not', 'of', 'or', 'private', 'rule', 'startswith', 'strings', 'them', 'true', 'uint16', 'uint16be', 'uint32', 'uint32be', 'uint8', 'uint8be', 'wide', 'xor' } FUNCTION_KEYWORDS = {'uint8', 'uint16', 'uint32', 'uint8be', 'uint16be', 'uint32be'} def __init__(self, store_raw_sections=True, meta_as_kv=False, import_effects=False, testmode=False): """Initialize the parser object. Args: store_raw_sections: Enable attribute storage of raw section input. (default True) meta_as_kv: Enable alternate structure for meta section as dictionary. (default False) import_effects: Enable including imports in all rules affected by the import. (default False) testmode: Enable permanent accumulators for unit testing purposes. (default: False) """ self.rules = list() self.current_rule = dict() self.string_modifiers = dict() self.imports = set() self.includes = list() self.terms = list() self.scopes = list() self.tags = list() self.comments = list() # adds functionality to track attributes containing raw section data # in case needed (ie modifying metadata and re-constructing a complete rule # while maintaining original comments and padding) self.store_raw_sections = store_raw_sections self._raw_input = None self._meta_start = None self._meta_end = None self._strings_start = None self._strings_end = None self._condition_start = None self._condition_end = None self._rule_comments = list() self._stringnames = set() # Adds a dictionary representation of the meta section of a rule self.meta_as_kv = meta_as_kv # Includes imports in all rules affected by them whether or not the import is used in a condition self.import_effects = import_effects self.lexer = lex.lex(module=self, debug=False) self.parser = yacc.yacc(module=self, debug=False) self._testmode = testmode if testmode: self._comment_record = list() def clear(self): """Clear all information about previously parsed rules.""" self.rules.clear() self.current_rule.clear() self.string_modifiers.clear() self.imports.clear() self.includes.clear() self.terms.clear() self.scopes.clear() self.tags.clear() self.comments.clear() self._raw_input = None self._meta_start = None self._meta_end = None self._strings_start = None self._strings_end = None self._condition_start = None self._condition_end = None self._rule_comments.clear() self._stringnames.clear() self.lexer = lex.lex(module=self, debug=False) self.parser = yacc.yacc(module=self, debug=False) def _add_element(self, element_type, element_value): """Accept elements from the parser and uses them to construct a representation of the YARA rule. Args: element_type: The element type determined by the parser. Input is one of ElementTypes. element_value: This is the contents of the element as parsed from the rule. """ if element_type == ElementTypes.RULE_NAME: rule_name, start_line, stop_line = element_value self.current_rule['rule_name'] = rule_name self.current_rule['start_line'] = start_line self.current_rule['stop_line'] = stop_line if self.store_raw_sections: if self._meta_start: self.current_rule['raw_meta'] = self._raw_input[self._meta_start:self._meta_end] if self._strings_start: self.current_rule['raw_strings'] = self._raw_input[self._strings_start:self._strings_end] if self._condition_start: self.current_rule['raw_condition'] = self._raw_input[self._condition_start:self._condition_end] self._flush_accumulators() self.rules.append(self.current_rule) logger.debug('Adding Rule: {}'.format(self.current_rule['rule_name'])) self.current_rule = dict() self._stringnames.clear() elif element_type == ElementTypes.METADATA_KEY_VALUE: key, value = element_value if 'metadata' not in self.current_rule: self.current_rule['metadata'] = [{key: value}] if self.meta_as_kv: self.current_rule['metadata_kv'] = {key: value} else: self.current_rule['metadata'].append({key: value}) if self.meta_as_kv: self.current_rule['metadata_kv'][key] = value elif element_type == ElementTypes.STRINGS_KEY_VALUE: key, value, string_type = element_value string_dict = {'name': key, 'value': value, 'type': string_type.name.lower()} if any(self.string_modifiers): string_dict['modifiers'] = [k + v for k, v in self.string_modifiers.items()] self.string_modifiers.clear() if 'strings' not in self.current_rule: self.current_rule['strings'] = [string_dict] else: self.current_rule['strings'].append(string_dict) elif element_type == ElementTypes.STRINGS_MODIFIER: modifier, predicate = element_value self.string_modifiers[modifier] = predicate elif element_type == ElementTypes.IMPORT: self.imports.add(element_value) elif element_type == ElementTypes.INCLUDE: self.includes.append(element_value) elif element_type == ElementTypes.TERM: self.terms.append(element_value) elif element_type == ElementTypes.SCOPE: self.scopes.append(element_value) elif element_type == ElementTypes.TAG: self.tags.append(element_value) elif element_type == ElementTypes.COMMENT: self.comments.append(element_value) else: self.comments.append(element_value) def _flush_accumulators(self): """Add accumulated elements to the current rule and resets the accumulators.""" if any(self.terms): self.current_rule['condition_terms'] = self.terms self.terms = list() if any(self.scopes): self.current_rule['scopes'] = self.scopes self.scopes = list() if any(self.tags): self.current_rule['tags'] = self.tags self.tags = list() if any(self.comments): self.current_rule['comments'] = self.comments self.comments = list() self._meta_start = None self._meta_end = None self._strings_start = None self._strings_end = None self._condition_start = None self._condition_end = None @staticmethod def _find_imports_in_condition(imports, condition_terms): """Search a list of condition terms for any that use the given imports.""" used_imports = set() for imp in imports: for term in condition_terms: if term.startswith(f'{imp}.'): used_imports.add(imp) break return list(used_imports) def parse_string(self, input_string): """Take a string input expected to consist of YARA rules, and return list of dictionaries representing them. Args: input_string: String input expected to consist of YARA rules. Returns: dict: All the parsed components of a YARA rule. """ self._raw_input = input_string self.parser.parse(input_string, lexer=self.lexer) for rule in self.rules: if any(self.imports): if self.import_effects: rule['imports'] = list(self.imports) elif imports := self._find_imports_in_condition(self.imports, rule['condition_terms']): rule['imports'] = imports if any(self.includes): rule['includes'] = self.includes return self.rules class Plyara(Parser): """Define the lexer and the parser rules.""" STRING_ESCAPE_CHARS = {'"', '\\', 'r', 't', 'n', 'x'} tokens = [ 'BYTESTRING', 'STRING', 'REXSTRING', 'EQUALS', 'STRINGNAME', 'STRINGNAME_ARRAY', 'STRINGNAME_COUNT', 'STRINGNAME_LENGTH', 'LPAREN', 'RPAREN', 'LBRACK', 'RBRACK', 'LBRACE', 'RBRACE', 'ID', 'BACKSLASH', 'PIPE', 'PLUS', 'SECTIONMETA', 'SECTIONSTRINGS', 'SECTIONCONDITION', 'COMMA', 'GREATERTHAN', 'LESSTHAN', 'GREATEREQUAL', 'LESSEQUAL', 'RIGHTBITSHIFT', 'LEFTBITSHIFT', 'MODULO', 'TILDE', 'XOR_OP', # XOR operator token (from conditions section) 'PERIOD', 'COLON', 'STAR', 'HYPHEN', 'AMPERSAND', 'NEQUALS', 'EQUIVALENT', 'DOTDOT', 'HEXNUM', 'FILESIZE_SIZE', 'NUM', 'COMMENT', 'MCOMMENT' ] reserved = { 'all': 'ALL', 'and': 'AND', 'any': 'ANY', 'ascii': 'ASCII', 'at': 'AT', 'contains': 'CONTAINS', 'defined': 'DEFINED', 'entrypoint': 'ENTRYPOINT', 'false': 'FALSE', 'filesize': 'FILESIZE', 'for': 'FOR', 'fullword': 'FULLWORD', 'global': 'GLOBAL', 'import': 'IMPORT', 'in': 'IN', 'include': 'INCLUDE', 'int8': 'INT8', 'int16': 'INT16', 'int32': 'INT32', 'int8be': 'INT8BE', 'int16be': 'INT16BE', 'int32be': 'INT32BE', 'matches': 'MATCHES', 'nocase': 'NOCASE', 'none': 'NONE', 'not': 'NOT', 'of': 'OF', 'or': 'OR', 'private': 'PRIVATE', 'rule': 'RULE', 'them': 'THEM', 'true': 'TRUE', 'wide': 'WIDE', 'uint8': 'UINT8', 'uint16': 'UINT16', 'uint32': 'UINT32', 'uint8be': 'UINT8BE', 'uint16be': 'UINT16BE', 'uint32be': 'UINT32BE', 'xor': 'XOR_MOD', # XOR string modifier token (from strings section) 'base64': 'BASE64', 'base64wide': 'BASE64WIDE', 'icontains': 'ICONTAINS', 'endswith': 'ENDSWITH', 'iendswith': 'IENDSWITH', 'startswith': 'STARTSWITH', 'istartswith': 'ISTARTSWITH', 'iequals': 'IEQUALS' } tokens = tokens + list(reserved.values()) # Regular expression rules for simple tokens t_LPAREN = r'\(' t_RPAREN = r'\)' t_EQUIVALENT = r'==' t_NEQUALS = r'!=' t_EQUALS = r'=' t_LBRACE = r'{' t_PLUS = r'\+' t_PIPE = r'\|' t_BACKSLASH = r'\\' t_COMMA = r',' t_GREATERTHAN = r'>' t_LESSTHAN = r'<' t_GREATEREQUAL = r'>=' t_LESSEQUAL = r'<=' t_RIGHTBITSHIFT = r'>>' t_LEFTBITSHIFT = r'<<' t_MODULO = r'%' t_TILDE = r'~' t_XOR_OP = r'\^' t_PERIOD = r'\.' t_COLON = r':' t_STAR = r'\*' t_LBRACK = r'\[' t_RBRACK = r'\]' t_HYPHEN = r'\-' t_AMPERSAND = r'&' t_DOTDOT = r'\.\.' states = ( ('STRING', 'exclusive', ), ('BYTESTRING', 'exclusive', ), ('REXSTRING', 'exclusive', ), ) # Complex token handling def t_RBRACE(self, t): r'}' # noqa: D300, D400, D415 t.value = t.value self._condition_end = t.lexpos return t @staticmethod def t_NEWLINE(t): r'(\n|\r\n)+' # noqa: D300, D400, D415 t.lexer.lineno += len(t.value) t.value = t.value @staticmethod def t_COMMENT(t): r'(//[^\r\n]*)' # noqa: D300, D400, D415 return t @staticmethod def t_MCOMMENT(t): r'/\*(.|\n|\r\n)*?\*/' # noqa: D300, D400, D415 if '\r\n' in t.value: t.lexer.lineno += t.value.count('\r\n') else: t.lexer.lineno += t.value.count('\n') return t @staticmethod def t_HEXNUM(t): r'0x[A-Fa-f0-9]{1,15}' # noqa: D300, D400, D415 t.value = t.value return t def t_SECTIONMETA(self, t): r'meta\s*:' # noqa: D300, D400, D415 t.value = t.value self._meta_start = t.lexpos t.lexer.section = 'meta' return t def t_SECTIONSTRINGS(self, t): r'strings\s*:' # noqa: D300, D400, D415 t.value = t.value self._strings_start = t.lexpos self._meta_end = t.lexpos t.lexer.section = 'strings' return t def t_SECTIONCONDITION(self, t): r'condition\s*:' # noqa: D300, D400, D415 t.value = t.value self._condition_start = t.lexpos if self._meta_end is None: self._meta_end = t.lexpos self._strings_end = t.lexpos t.lexer.section = 'condition' return t @staticmethod def _process_string_with_escapes(t, escape_chars=None): if escape_chars is None: escape_chars = [t.value] if t.lexer.escape == 1 and t.value in escape_chars or t.value == '\\': t.lexer.escape ^= 1 if t.value == 'x': t.lexer.hex_escape = 2 elif t.lexer.hex_escape > 0: if t.value.lower() in string.hexdigits: t.lexer.hex_escape -= 1 else: raise ParseTypeError(f'Invalid hex character: {t.value!r}, at line: {t.lexer.lineno}', t.lexer.lineno, t.lexer.lexpos) elif t.lexer.escape == 1: raise ParseTypeError(f'Invalid escape sequence: \\{t.value}, at line: {t.lexer.lineno}', t.lexer.lineno, t.lexer.lexpos) # Text string handling @staticmethod def t_begin_STRING(t): r'"' # noqa: D300, D400, D415 t.lexer.escape = 0 t.lexer.string_start = t.lexer.lexpos - 1 t.lexer.begin('STRING') t.lexer.hex_escape = 0 # @staticmethod def t_STRING_value(self, t): r'.' # noqa: D300, D400, D415 if t.lexer.escape == 0 and t.value == '"': t.type = 'STRING' t.value = t.lexer.lexdata[t.lexer.string_start:t.lexer.lexpos] t.lexer.begin('INITIAL') return t else: self._process_string_with_escapes(t, escape_chars=self.STRING_ESCAPE_CHARS) t_STRING_ignore = '' @staticmethod def t_STRING_error(t): """Raise parsing error for illegal string character. Args: t: Token input from lexer. Raises: ParseTypeError """ msg = 'Illegal string character: {!r}, at line: {}' raise ParseTypeError(msg.format(t.value[0], t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) # Byte string handling @staticmethod def t_begin_BYTESTRING(t): r'\{' # noqa: D300, D400, D415 if hasattr(t.lexer, 'section') and t.lexer.section == 'strings': t.lexer.bytestring_start = t.lexer.lexpos - 1 t.lexer.begin('BYTESTRING') t.lexer.bytestring_group = 0 else: t.type = 'LBRACE' return t @staticmethod def t_BYTESTRING_NEWLINE(t): r'(\n|\r\n)+' # noqa: D300, D400, D415 t.lexer.lineno += t.value.count('\n') @staticmethod def t_BYTESTRING_pair(t): r'[\t ]*~?[a-fA-F0-9?]{2}[\t ]*' # noqa: D300, D400, D415 @staticmethod def t_BYTESTRING_comment(t): r'\/\/[^\r\n]*' # noqa: D300, D400, D415 t.type = 'COMMENT' return t @staticmethod def t_BYTESTRING_mcomment(t): r'/\*(.|\n|\r\n)*?\*/' # noqa: D300, D400, D415 if '\r\n' in t.value: t.lexer.lineno += t.value.count('\r\n') else: t.lexer.lineno += t.value.count('\n') t.type = 'MCOMMENT' return t @staticmethod def t_BYTESTRING_jump(t): r'\[\s*(\d*)\s*-?\s*(\d*)\s*\]' # noqa: D300, D400, D415 groups = t.lexer.lexmatch.groups() index = groups.index(t.value) lower_bound = groups[index + 1] upper_bound = groups[index + 2] if lower_bound and upper_bound: if not 0 <= int(lower_bound) <= int(upper_bound): raise ParseValueError(f'Illegal bytestring jump bounds: {t.value}, at line: {t.lexer.lineno}', t.lexer.lineno, t.lexer.lexpos) @staticmethod def t_BYTESTRING_group_start(t): r'\(' # noqa: D300, D400, D415 t.lexer.bytestring_group += 1 @staticmethod def t_BYTESTRING_group_end(t): r'\)' # noqa: D300, D400, D415 t.lexer.bytestring_group -= 1 @staticmethod def t_BYTESTRING_group_logical_or(t): r'\|' # noqa: D300, D400, D415 @staticmethod def t_BYTESTRING_end(t): r'\}' # noqa: D300, D400, D415 t.type = 'BYTESTRING' t.value = t.lexer.lexdata[t.lexer.bytestring_start:t.lexer.lexpos] if t.lexer.bytestring_group != 0: raise ParseValueError(f'Unbalanced group in bytestring: {t.value}, at line: {t.lexer.lineno}', t.lexer.lineno, t.lexer.lexpos) t.lexer.begin('INITIAL') return t t_BYTESTRING_ignore = ' \t' @staticmethod def t_BYTESTRING_error(t): """Raise parsing error for illegal bytestring character. Args: t: Token input from lexer. Raises: ParseTypeError """ raise ParseTypeError(f'Illegal bytestring character : {t.value[0]}, at line: {t.lexer.lineno}', t.lexer.lineno, t.lexer.lexpos) # Rexstring Handling @staticmethod def t_begin_REXSTRING(t): r'/' # noqa: D300, D400, D415 t.lexer.rexstring_start = t.lexer.lexpos - 1 t.lexer.begin('REXSTRING') t.lexer.escape = 0 t.lexer.hex_escape = 0 @staticmethod def t_REXSTRING_end(t): r'/(?:i?s?)' # noqa: D300, D400, D415 if t.lexer.escape == 0: t.type = 'REXSTRING' t.value = t.lexer.lexdata[t.lexer.rexstring_start:t.lexer.lexpos] t.lexer.begin('INITIAL') return t else: t.lexer.escape ^= 1 def t_REXSTRING_value(self, t): r'[\x20-\x7f]' # noqa: D300, D400, D415 self._process_string_with_escapes(t) t_REXSTRING_ignore = '' @staticmethod def t_REXSTRING_error(t): """Raise parsing error for illegal rexstring character. Args: t: Token input from lexer. Raises: ParseTypeError """ msg = 'Illegal rexstring character : {!r}, at line: {}' raise ParseTypeError(msg.format(t.value[0], t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) @staticmethod def t_STRINGNAME(t): r'\$[0-9a-zA-Z_]*[*]?' # noqa: D300, D400, D415 t.value = t.value return t @staticmethod def t_STRINGNAME_ARRAY(t): r'@[0-9a-zA-Z_]*[*]?' # noqa: D300, D400, D415 t.value = t.value return t @staticmethod def t_STRINGNAME_LENGTH(t): r'![0-9a-zA-Z_]*[*]?(?!=)' # noqa: D300, D400, D415 t.value = t.value return t @staticmethod def t_STRINGNAME_COUNT(t): r'\#([a-zA-Z][0-9a-zA-Z_]*[*]?)?' # noqa: D300, D400, D415 t.value = t.value return t @staticmethod def t_FILESIZE_SIZE(t): r"\d+[KM]B" # noqa: D300, D400, D415 t.value = t.value return t @staticmethod def t_NUM(t): r'\d{1,18}(\.\d+)?' # noqa: D300, D400, D415 t.value = t.value return t def t_ID(self, t): r'[a-zA-Z_][a-zA-Z_0-9.]*' # noqa: D300, D400, D415 t.type = self.reserved.get(t.value, 'ID') # Check for reserved words return t # A string containing ignored characters (spaces and tabs) t_ignore = ' \t' # Error handling rule @staticmethod def t_error(t): """Raise parsing error. Args: t: Token input from lexer. Raises: ParseTypeError """ msg = 'Illegal character {!r} at line {}' raise ParseTypeError(msg.format(t.value[0], t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) # Parsing rules precedence = ( ('right', 'NUM', ), ('right', 'ID', ), ('right', 'HEXNUM', ) ) @staticmethod def p_ruleset(p): '''ruleset : rules | imports | includes | ruleset ruleset''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 @staticmethod def p_rules(p): '''rules : rules rule | rule''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_rule(self, p): '''rule : scopes RULE ID tag_section LBRACE rule_body RBRACE | scopes RULE ID tag_section LBRACE rule_body RBRACE COMMENT | scopes RULE ID tag_section LBRACE rule_body RBRACE MCOMMENT''' # noqa: D300, D400, D403, D415 if '.' in p[3]: raise ParseTypeError(f'Invalid rule name {p[3]}, on line {p.lineno(1)}', p.lineno, p.lexpos) if len(p) == 9: if p.lineno(7) == p.lineno(8): logger.debug(f'Matched a trailing comment: {p[8]}') if p[8][:2] == '//': self._add_element(ElementTypes.COMMENT, p[8]) else: self._add_element(ElementTypes.MCOMMENT, p[8]) while self._rule_comments: comment = self._rule_comments.pop() if self._testmode: self._comment_record.append(comment) if p.lexpos(5) < comment.lexpos < p.lexpos(7): logger.debug(f'Matched a rule comment: {comment.value}') self._add_element(getattr(ElementTypes, comment.type), comment.value) element_value = (p[3], int(p.lineno(2)), int(p.lineno(7)), ) self._add_element(ElementTypes.RULE_NAME, element_value) logger.debug(f'Matched rule: {p[3]}') logger.debug(f'Rule start: {p.lineno(2)}, Rule stop: {p.lineno(7)}') @staticmethod def p_imports(p): '''imports : imports import | import''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 @staticmethod def p_includes(p): '''includes : includes include | include''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 @staticmethod def p_scopes(p): '''scopes : scopes scope | scope | ''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_import(self, p): '''import : IMPORT STRING''' # noqa: D300, D400, D403, D415 import_value = p[2].replace('"', '') logger.debug(f'Matched import: {import_value}') self._add_element(ElementTypes.IMPORT, import_value) def p_include(self, p): '''include : INCLUDE STRING''' # noqa: D300, D400, D403, D415 include_value = p[2].replace('"', '') logger.debug(f'Matched include: {include_value}') self._add_element(ElementTypes.INCLUDE, include_value) def p_scope(self, p): '''scope : PRIVATE | GLOBAL''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 logger.debug(f'Matched scope identifier: {p[1]}') self._add_element(ElementTypes.SCOPE, p[1]) @staticmethod def p_tag_section(p): '''tag_section : COLON tags | COLON tags comments | comments | ''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 @staticmethod def p_tags(p): '''tags : tags tag | tag''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_tag(self, p): '''tag : ID''' # noqa: D300, D400, D403, D415 if '.' in p[1]: raise ParseTypeError(f'Invalid rule tag {p[1]}, on line {p.lineno(1)}', p.lineno, p.lexpos) logger.debug(f'Matched tag: {p[1]}') self._add_element(ElementTypes.TAG, p[1]) @staticmethod def p_rule_body(p): '''rule_body : sections | comments sections''' # noqa: D300, D400, D403, D415 logger.debug('Matched rule body') @staticmethod def p_rule_sections(p): '''sections : sections section | section''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 @staticmethod def p_rule_section(p): '''section : meta_section | strings_section | condition_section''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 @staticmethod def p_meta_section(p): '''meta_section : SECTIONMETA meta_kvs | SECTIONMETA comments meta_kvs''' # noqa: D300, D400, D403, D415 logger.debug('Matched meta section') @staticmethod def p_strings_section(p): '''strings_section : SECTIONSTRINGS strings_kvs | SECTIONSTRINGS comments strings_kvs''' # noqa: D300, D400, D403, D415 @staticmethod def p_condition_section(p): '''condition_section : SECTIONCONDITION expression | SECTIONCONDITION comments expression''' # noqa: D300, D400, D403, D415 # Meta elements @staticmethod def p_meta_kvs(p): '''meta_kvs : meta_kvs meta_kv | meta_kvs meta_kv comments | meta_kv comments | meta_kv''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 logger.debug('Matched meta kvs') def p_meta_kv(self, p): '''meta_kv : ID EQUALS STRING | ID EQUALS ID | ID EQUALS TRUE | ID EQUALS FALSE | ID EQUALS NUM | ID EQUALS HYPHEN NUM''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 key = p[1] value = p[3] if re.match(r'".*"', value): value = p[3].removeprefix('"').removesuffix('"') elif value in ('true', 'false'): value = True if value == 'true' else False elif value == '-': num_value = p[4] value = -int(num_value) else: value = int(value) logger.debug(f'Matched meta kv: {key} equals {value}') self._add_element(ElementTypes.METADATA_KEY_VALUE, (key, value, )) # Strings elements @staticmethod def p_strings_kvs(p): '''strings_kvs : strings_kvs strings_kv | strings_kv''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 logger.debug('Matched strings kvs') def _parse_string_kv(self, p, string_type): """Perform parsing for all string types. Args: p: Parser object. string_type: StringTypes enum. """ key = p[1] value = p[3] match = re.match('"(.+)"', value) if match: value = match.group(1) if key != '$' and key in self._stringnames: raise ParseTypeError(f'Duplicate string name key {key} on line {p.lineno(1)}', p.lineno, p.lexpos) self._stringnames.add(key) logger.debug(f'Matched strings kv: {key} equals {value}') self._add_element(ElementTypes.STRINGS_KEY_VALUE, (key, value, string_type, )) def p_byte_strings_kv(self, p): '''strings_kv : STRINGNAME EQUALS BYTESTRING | STRINGNAME EQUALS BYTESTRING comments | STRINGNAME EQUALS BYTESTRING byte_string_modifiers | STRINGNAME EQUALS BYTESTRING byte_string_modifiers comments''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 self._parse_string_kv(p, StringTypes.BYTE) def p_text_strings_kv(self, p): '''strings_kv : STRINGNAME EQUALS STRING | STRINGNAME EQUALS STRING comments | STRINGNAME EQUALS STRING text_string_modifiers | STRINGNAME EQUALS STRING text_string_modifiers comments''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 self._parse_string_kv(p, StringTypes.TEXT) def p_regex_strings_kv(self, p): '''strings_kv : STRINGNAME EQUALS REXSTRING | STRINGNAME EQUALS REXSTRING comments | STRINGNAME EQUALS REXSTRING regex_string_modifiers | STRINGNAME EQUALS REXSTRING regex_string_modifiers comments''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 self._parse_string_kv(p, StringTypes.REGEX) def _parse_string_modifier(self, mod, pred, mtype, lineno, lexpos): """Processes one string modifier after checking if it is a duplicate.""" incompatible = { 'nocase': {'xor', 'base64', 'base64wide'}, 'xor': {'nocase', 'base64', 'base64wide'}, 'base64': {'nocase', 'xor', 'fullword'}, 'base64wide': {'nocase', 'xor', 'fullword'}, 'fullword': {'base64', 'base64wide'} } if mod in self.string_modifiers: raise ParseTypeError(f'Duplicate {mtype} {mod} on line {lineno}', lineno, lexpos) if mod in incompatible: if bad_mod := incompatible[mod].intersection(set(self.string_modifiers.keys())): message = f'Incompatible modifiers [{mod}, {bad_mod.pop()}] on line {lineno}' raise ParseTypeError(message, lineno, lexpos) if mod in ['base64', 'base64wide'] and pred: modlen = len(re.findall(r'(?:\\x[a-fA-F0-9]{2}|.)', pred[2:-2])) if modlen > 64: raise ParseTypeError(f'Modifier {mod} predicate too long: {modlen} on line {lineno}', lineno, lexpos) if modlen < 64: raise ParseTypeError(f'Modifier {mod} predicate too short: {modlen} on line {lineno}', lineno, lexpos) logger.debug(f'Matched {mtype}: {mod}') self._add_element(ElementTypes.STRINGS_MODIFIER, (mod, pred, )) @staticmethod def p_text_string_modifiers(p): '''text_string_modifiers : text_string_modifiers text_string_modifier | text_string_modifier''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_text_string_modifier(self, p): '''text_string_modifier : NOCASE | WIDE | ASCII | XOR_MOD | XOR_MOD LPAREN NUM RPAREN | XOR_MOD LPAREN HEXNUM RPAREN | XOR_MOD LPAREN NUM HYPHEN NUM RPAREN | XOR_MOD LPAREN HEXNUM HYPHEN HEXNUM RPAREN | XOR_MOD LPAREN HEXNUM HYPHEN NUM RPAREN | XOR_MOD LPAREN NUM HYPHEN HEXNUM RPAREN | BASE64 | BASE64WIDE | BASE64 LPAREN STRING RPAREN | BASE64WIDE LPAREN STRING RPAREN | FULLWORD | PRIVATE''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 if len(p) == 2: pred = str() mtype = 'simple text string modifier' else: pred = ''.join(p[2:]) if p[1] == 'xor': mtype = 'complex xor text string modifier' elif p[1] == 'base64': mtype = 'complex base64 text string modifier' else: mtype = 'complex base64wide text string modifier' mod = p[1] self._parse_string_modifier(mod, pred, mtype, p.lineno(1), p.lexpos(1)) @staticmethod def p_regex_string_modifiers(p): '''regex_string_modifiers : regex_string_modifiers regex_string_modifer | regex_string_modifer''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_regex_string_modifer(self, p): '''regex_string_modifer : NOCASE | WIDE | ASCII | FULLWORD | PRIVATE''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 self._parse_string_modifier(p[1], str(), 'simple regex string modifier', p.lineno(1), p.lexpos(1)) @staticmethod def p_byte_string_modifiers(p): '''byte_string_modifiers : byte_string_modifiers byte_string_modifer | byte_string_modifer''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_byte_string_modifer(self, p): '''byte_string_modifer : PRIVATE''' # noqa: D300, D400, D403, D415 self._parse_string_modifier(p[1], str(), 'simple byte string modifier', p.lineno(1), p.lexpos(1)) def p_comments(self, p): '''comments : COMMENT | MCOMMENT''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 logger.debug(f'Matched a comment: {p[1]}') if p[1][:2] == '//': self._add_element(ElementTypes.COMMENT, p[1]) else: self._add_element(ElementTypes.MCOMMENT, p[1]) # Condition elements @staticmethod def p_expression(p): '''expression : expression term | expression term comments | term | term comments''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 def p_condition(self, p): '''term : FILESIZE_SIZE | ID | NUM | HEXNUM | STRING | LPAREN | RPAREN | LBRACK | RBRACK | DOTDOT | EQUIVALENT | EQUALS | NEQUALS | PLUS | PIPE | BACKSLASH | COMMA | GREATERTHAN | LESSTHAN | GREATEREQUAL | LESSEQUAL | RIGHTBITSHIFT | LEFTBITSHIFT | MODULO | TILDE | XOR_OP | PERIOD | COLON | STAR | HYPHEN | AMPERSAND | ALL | AND | ANY | AT | CONTAINS | DEFINED | ENTRYPOINT | FALSE | FILESIZE | FOR | IN | INT8 | INT16 | INT32 | INT8BE | INT16BE | INT32BE | MATCHES | NONE | NOT | OR | OF | THEM | TRUE | UINT8 | UINT16 | UINT32 | UINT8BE | UINT16BE | UINT32BE | STRINGNAME | STRINGNAME_ARRAY | STRINGNAME_LENGTH | STRINGNAME_COUNT | REXSTRING | ICONTAINS | ENDSWITH | IENDSWITH | STARTSWITH | ISTARTSWITH | IEQUALS''' # noqa: D205, D208, D209, D300, D400, D401, D403, D415 logger.debug(f'Matched a condition term: {p[1]}') if p[1] == '$': logger.info(f'Potential wrong use of anonymous string on line {p.lineno(1)}') self._add_element(ElementTypes.TERM, p[1]) # Error rule for syntax errors def p_error(self, p): """Raise syntax errors. Args: p: Data from the parser. Raises: ParseTypeError """ if not p: # This happens when we try to parse an empty string or file, or one with no actual rules. pass elif p.type in ('COMMENT', 'MCOMMENT'): # Only bytestring internal comments should fall through to here self._rule_comments.append(p) self.parser.errok() # This is a method from PLY to reset the error state from parsing a comment else: message = f'Unknown text {p.value} for token of type {p.type} on line {p.lineno}' raise ParseTypeError(message, p.lineno, p.lexpos) plyara-2.2.8/src/plyara/__init__.py0000775000175000017500000000006414751212104015313 0ustar rharha__all__ = ['Plyara'] from plyara.core import Plyara plyara-2.2.8/examples/0000775000175000017500000000000014751212104012736 5ustar rharhaplyara-2.2.8/examples/corpus_stats.py0000775000175000017500000000324614751212104016051 0ustar rharha"""Example script that demonstrates using plyara.""" import argparse import operator import plyara def example(): """Execute the example code.""" parser = argparse.ArgumentParser() parser.add_argument('file', metavar='FILE', help='File containing YARA rules to parse.') args = parser.parse_args() print('Parsing file...') with open(args.file, 'r') as fh: data = fh.read() parser = plyara.Plyara() rules_dict = parser.parse_string(data) print('Analyzing dictionary...') imps = {} tags = {} rule_count = 0 for rule in rules_dict: rule_count += 1 # Imports if 'imports' in rule: for imp in rule['imports']: imp = imp.replace('"', '') if imp in imps: imps[imp] += 1 else: imps[imp] = 1 # Tags if 'tags' in rule: for tag in rule['tags']: if tag in tags: tags[tag] += 1 else: tags[tag] = 1 print('\n======================\n') print('Number of rules in file: {}'.format(rule_count)) ordered_imps = sorted(imps.items(), key=operator.itemgetter(1), reverse=True) ordered_tags = sorted(tags.items(), key=operator.itemgetter(1), reverse=True) print('\n======================\n') print('Top imports:') for i in range(5): if i < len(ordered_imps): print(ordered_imps[i]) print('\n======================\n') print('Top tags:') for i in range(5): if i < len(ordered_tags): print(ordered_tags[i]) if __name__ == '__main__': example() plyara-2.2.8/LICENSE0000664000175000017500000002616614751212104012140 0ustar rharha Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2014 Christian Buia Copyright 2025 plyara Maintainers Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. plyara-2.2.8/pyproject.toml0000664000175000017500000000255114751212104014037 0ustar rharha[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "plyara" version = "2.2.8" requires-python = ">=3.10" authors = [{ name = "plyara Maintainers" }] description = "Parse YARA rules" readme = "README.md" keywords = ["malware", "analysis", "yara"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Information Technology", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Security", "Topic :: Utilities", ] [project.urls] Homepage = "https://github.com/plyara/plyara" Issues = "https://github.com/plyara/plyara/issues" [project.scripts] plyara = "plyara.command_line:main" [project.optional-dependencies] tests = ['pycodestyle', 'pydocstyle', 'pyflakes'] [tool.pycodestyle] max-line-length = 120 [tool.pydocstyle] ignore = ["D104", "D107", "D203", "D213", "D406", "D407", "D413"] [tool.coverage.run] branch = true source = ["src"] plyara-2.2.8/.gitmodules0000664000175000017500000000013714751212104013276 0ustar rharha[submodule "third_party/ply"] path = third_party/ply url = https://github.com/plyara/ply.git plyara-2.2.8/.gitignore0000664000175000017500000000726114751212104013116 0ustar rharha## Python Section # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. Pipfile.lock # UV # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm .idea/ # Test files test.py test.yar # Files created by plyara at runtime parsetab.py ## macOS Section # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ## VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets # Local History for Visual Studio Code .history/ # Built Visual Studio Code Extensions *.vsixplyara-2.2.8/.github/0000775000175000017500000000000014751212104012460 5ustar rharhaplyara-2.2.8/.github/workflows/0000775000175000017500000000000014751212104014515 5ustar rharhaplyara-2.2.8/.github/workflows/test-action.yaml0000664000175000017500000000153514751212104017637 0ustar rharha# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: plyara testing on: push: branches: [ "**" ] pull_request: branches: [ "master" ] permissions: contents: read jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 with: submodules: 'true' - uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install testing dependencies run: | python -m pip install --upgrade pip pip install .[tests] - name: Test with unittest run: python -m unittest discover plyara-2.2.8/README.md0000664000175000017500000001610314751212104012400 0ustar rharha# plyara [![PyPi Version](http://img.shields.io/pypi/v/plyara.svg)](https://pypi.python.org/pypi/plyara) Parse [YARA](https://virustotal.github.io/yara/) rules into a dictionary representation. Plyara is a script and library that lexes and parses a file consisting of one more YARA rules into a python dictionary representation. The goal of this tool is to make it easier to perform bulk operations or transformations of large sets of YARA rules, such as extracting indicators, updating attributes, and analyzing a corpus. Other applications include linters and dependency checkers. Plyara leverages the Python module [PLY](https://ply.readthedocs.io/en/latest/) for lexing YARA rules. This is a community-maintained fork of the [original plyara](https://github.com/8u1a/plyara) by [8u1a](https://github.com/8u1a). The "plyara" trademark is used with permission. ## Installation Plyara requires Python 3.10+. Install with pip: ```sh pip install plyara ``` ## Usage Use the plyara Python library in your own applications: ``` python >>> import plyara >>> parser = plyara.Plyara() >>> mylist = parser.parse_string('rule MyRule { strings: $a="1" \n condition: false }') >>> >>> import pprint >>> pprint.pprint(mylist) [{'condition_terms': ['false'], 'raw_condition': 'condition: false ', 'raw_strings': 'strings: $a="1" \n ', 'rule_name': 'MyRule', 'start_line': 1, 'stop_line': 2, 'strings': [{'name': '$a', 'type': 'text', 'value': '1'}]}] >>> ``` Or, use the included `plyara` script from the command line: ```sh $ plyara -h usage: plyara [-h] [--log] FILE Parse YARA rules into a dictionary representation. positional arguments: FILE File containing YARA rules to parse. optional arguments: -h, --help show this help message and exit --log Enable debug logging to the console. ``` The command-line tool will print valid JSON output when parsing rules: ```yara rule silent_banker : banker { meta: description = "This is just an example" threat_level = 3 in_the_wild = true strings: $a = {6A 40 68 00 30 00 00 6A 14 8D 91} $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" condition: $a or $b or $c } ``` Command-line tool: ```sh plyara example.yar ``` JSON Output: ```json [ { "condition_terms": [ "$a", "or", "$b", "or", "$c" ], "metadata": [ { "description": "This is just an example" }, { "threat_level": 3 }, { "in_the_wild": true } ], "raw_condition": "condition:\n $a or $b or $c\n", "raw_meta": "meta:\n description = \"This is just an example\"\n threat_level = 3\n in_the_wild = true\n ", "raw_strings": "strings:\n $a = {6A 40 68 00 30 00 00 6A 14 8D 91}\n $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9}\n $c = \"UVODFRYSIHLNWPEJXQZAKCBGMT\"\n ", "rule_name": "silent_banker", "start_line": 1, "stop_line": 13, "strings": [ { "name": "$a", "type": "byte", "value": "{6A 40 68 00 30 00 00 6A 14 8D 91}" }, { "name": "$b", "type": "byte", "value": "{8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9}" }, { "name": "$c", "type": "text", "value": "UVODFRYSIHLNWPEJXQZAKCBGMT" } ], "tags": [ "banker" ] } ] ``` # Additional Information ## Reusing The Parser If you want to reuse a single instance of the parser object for efficiency when parsing large quantities of rule or rulesets, the new clear() method must be used. ``` python rules = list() parser = plyara.Plyara() for file in files: with open(file, 'r') as fh: yararules = parser.parse_string(fh.read()) rules += yararules parser.clear() ``` ## Breaking Change: Import Effects ### Background Imports are available to be used in a rule even if not used in a condition. Also, any module which is imported at all is used in processing all files scanned using the ruleset regardless if the import is used anywhere. Some users require that all rules affected by a particular import include that import in the dictionary output of plyara. At the same time, many users expect that a particular rule not include an import if that import is not used in the rule. ### New Parameter: Import Effects A new class constructor parameter called `import_effects` has been added to the parser. This parameter defaults to `False` which is a breaking change. Users who wish to retain the behavior from versions before 2.2, will need to set this parameter to `True` like so: ```python parser = plyara.Plyara(import_effects=True) ``` ### Note When reusing a `parser` for multiple rules and/or files and `import_effects` is enabled, be aware that imports are now shared across all rules - if one rule has an import, that import will be added to all rules in your parser object. ## Breaking Change: Logic Hash Versions Logic hashes now prepend a version number as well as the algorithm used to the hash itself. This will make future changes and revisions easier for uses to track. The old behavior is accessed using the `legacy` parameter on the `utils.generate_hash()` utility function. ```python rules = Plyara().parse_string(input_string) rulehash = generate_hash(rules[0], legacy=True) ``` ## Pre-Processing If the output of a particular rule looks incorrect after parsing by Plyara, you may be able to mitigate the problem by using YARA-X's `fmt` command for pre-processing. If you do notice a problem that requires pre-processing, please also open an issue. ```bash yr fmt foo.yar ``` # Contributing - If you find a bug, or would like to see a new feature, [Pull Requests](https://github.com/plyara/plyara/pulls) and [Issues](https://github.com/plyara/plyara/issues) are always welcome. - By submitting changes, you agree to release those changes under the terms of the [LICENSE](https://github.com/plyara/plyara/blob/master/LICENSE). - Writing passing unit tests for your changes, while not required, is highly encouraged and appreciated. - Please run all code contributions through each of the linters that we use for this project: - pycodestyle - pydocstyle - pyflakes - For more information on these linters, please refer to the [Python Code Quality Authority](https://pycqa.org/) ## Cloning To properly clone this repository including the git submodule for PLY, use the following command: ```bash git clone --recurse-submodules https://github.com/plyara/plyara.git ``` ## Installing for Development For coverage to work properly, the package must be installed in editable mode with the optional dependencies for `tests`. ```bash pip install -e '.[tests]' ``` ## Unit Tests ```bash python -m unittest discover ``` ## Coverage ```bash coverage run -m unittest discover coverage report -m ``` plyara-2.2.8/third_party/0000775000175000017500000000000014751212104013451 5ustar rharhaplyara-2.2.8/third_party/ply/0000775000175000017500000000000014751212104014255 5ustar rharhaplyara-2.2.8/tests/0000775000175000017500000000000014751212104012262 5ustar rharhaplyara-2.2.8/tests/test_command_line.py0000775000175000017500000000653014751212104016327 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for plyara command line functionality.""" import contextlib import hashlib import importlib.resources import io import os import pathlib import tempfile import unittest import unittest.mock import plyara.command_line class TestCLI(unittest.TestCase): """Checks command line scripts.""" def setUp(self): data = importlib.resources.files('tests.data.command_line').joinpath('test_file.yar').read_text() self.td = tempfile.TemporaryDirectory() os.chdir(self.td.name) self.target = pathlib.Path(self.td.name).joinpath('test_file.yar').write_text(data) self.output_hashes = [ '9d1991858f1b48b2485a9cb45692bc33c5228fb5acfa877a0d097b1db60052e3', '18569226a33c2f8f0c43dd0e034a6c05ea38f569adc3ca37d3c975be0d654f06', 'b9b64df222a91d5b99b0099320134e3aecd532513965d1cf7b5a0b58881bcccc' ] self.error_hash = '15cca23e71c5307424bb71830627de444ad382479cf0c7818d65395c87770580' @unittest.mock.patch('argparse._sys.argv', ['plyara', 'test_file.yar']) def test_plyara_cli_nolog(self): """Check that the output hash of CLI output matches the expected hash without logging.""" with contextlib.redirect_stdout(io.StringIO()) as out: with contextlib.redirect_stderr(io.StringIO()) as err: plyara.command_line.main() output = out.getvalue() error = err.getvalue() output_hash = hashlib.sha256(output.encode()).hexdigest() self.assertTrue(output_hash in self.output_hashes) self.assertEqual(error, str()) @unittest.mock.patch('argparse._sys.argv', ['plyara', '--log', 'test_file.yar']) def test_plyara_cli_withlog(self): """Check that the output hash of CLI output matches the expected hash with logging.""" with contextlib.redirect_stdout(io.StringIO()) as out: with contextlib.redirect_stderr(io.StringIO()) as err: plyara.command_line.main() output = out.getvalue() output_hash = hashlib.sha256(output.encode()).hexdigest() error = err.getvalue() error_hash = hashlib.sha256(error.encode()).hexdigest() self.assertTrue(output_hash in self.output_hashes) self.assertEqual(error_hash, self.error_hash) @unittest.mock.patch('argparse._sys.argv', ['plyara', 'doesnotexist.yar']) def test_plyara_cli_filenotfound(self): """Check that the error output is correct for a file not found exception.""" with self.assertRaisesRegex(SystemExit, r"\[Errno 2\] No such file or directory: 'doesnotexist\.yar'"): plyara.command_line.main() def tearDown(self): """Cleanup the temporary directory.""" self.td.cleanup() if __name__ == '__main__': unittest.main() plyara-2.2.8/tests/test_third_party_repositories.py0000664000175000017500000000522514751212104021037 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Test plyara against rule repositories.""" import subprocess import unittest from pathlib import Path from tempfile import TemporaryDirectory from plyara.core import Plyara class TestPublicYaraRules(unittest.TestCase): """Check parsing of third-party YARA rules.""" def test_third_party_rules(self): # Perform testing against a set of public YARA rule repositories to assess parsing capability projects = [ # "AlienVault-Labs/AlienVaultLabs", issue: https://github.com/plyara/plyara/issues/155 "bartblaze/Yara-rules", "The-DFIR-Report/Yara-Rules", "ditekshen/detection", "elastic/protections-artifacts", "eset/malware-ioc", "Neo23x0/signature-base", "intezer/yara-rules", "JPCERTCC/jpcert-yara", "malpedia/signator-rules", "kevoreilly/CAPE", "reversinglabs/reversinglabs-yara-rules", "stratosphereips/yara-rules", "advanced-threat-research/Yara-Rules", "volexity/threat-intel", ] for project in projects: with TemporaryDirectory() as rules_directory: # Fetch the most recent commit from project for testing subprocess.run( [ "git", "clone", "--depth", "1", f"https://github.com/{project}.git", ], cwd=rules_directory, capture_output=True ) # Traverse the project in search of YARA rules to test with for yara_file in Path(rules_directory).rglob("*.yar*"): if ".yar" in yara_file.suffix: with self.subTest(msg=project, yara_file=yara_file): # Check to see if we run into a parsing error plyara = Plyara() plyara.parse_string(yara_file.read_text()) plyara-2.2.8/tests/test_utils.py0000775000175000017500000001063314751212104015041 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for plyara utility functions.""" import importlib.resources import unittest import plyara.core from plyara.utils import generate_hash from plyara.utils import rebuild_yara_rule class TestUtilities(unittest.TestCase): """Check the various utility functions.""" def setUp(self): """Prepare for utility unit testing.""" self.parser = plyara.core.Plyara() self.data = importlib.resources.files('tests.data.utils') self.imports = importlib.resources.files('tests.data.imports') self.common = importlib.resources.files('tests.data.common') # self.maxDiff = None def test_generate_hash(self): input_string = self.data.joinpath('logic_collision_ruleset.yar').read_text() result = plyara.core.Plyara().parse_string(input_string) rule_mapping = {} for entry in result: rulename = entry['rule_name'] setname, _ = rulename.split('_') rulehash = generate_hash(entry) if setname not in rule_mapping: rule_mapping[setname] = [rulehash] else: rule_mapping[setname].append(rulehash) for setname, hashvalues in rule_mapping.items(): self.assertTrue(len(set(hashvalues)) == 1, 'Collision detection failure for {}'.format(setname)) def test_generate_hash_output_legacy(self): rule_hashes = self.data.joinpath('rulehashes_legacy.txt').read_text().splitlines() # Rules containing "(1..#)" or similar iterators cause Unhandled String Count Condition errors input_string = self.common.joinpath('test_rules_from_yara_project.yar').read_text() results = plyara.core.Plyara().parse_string(input_string) for index, result in enumerate(results): rulehash = generate_hash(result, legacy=True) self.assertEqual(rulehash, rule_hashes[index]) def test_generate_hash_output(self): rule_hashes = self.data.joinpath('rulehashes.txt').read_text().splitlines() # Rules containing "(1..#)" or similar iterators cause Unhandled String Count Condition errors input_string = self.common.joinpath('test_rules_from_yara_project.yar').read_text() results = plyara.core.Plyara().parse_string(input_string) for index, result in enumerate(results): rulehash = generate_hash(result) self.assertEqual(rulehash, rule_hashes[index]) def test_rebuild_yara_rule(self): input_string = self.data.joinpath('rebuild_ruleset.yar').read_text(encoding='utf-8') test_result = self.data.joinpath('rebuild_ruleset_good_enough.yar').read_text(encoding='utf-8') result = plyara.core.Plyara().parse_string(input_string) rebuilt_rules = str() for rule in result: rebuilt_rules += rebuild_yara_rule(rule) self.assertEqual(test_result, rebuilt_rules) def test_rebuild_yara_rule_metadata(self): test_rule = """ rule check_meta { meta: string_value = "TEST STRING" string_value = "DIFFERENT TEST STRING" bool_value = true bool_value = false digit_value = 5 digit_value = 10 condition: true } """ parsed = plyara.core.Plyara().parse_string(test_rule) for rule in parsed: unparsed = rebuild_yara_rule(rule) self.assertIn('string_value = "TEST STRING"', unparsed) self.assertIn('string_value = "DIFFERENT TEST STRING"', unparsed) self.assertIn('bool_value = true', unparsed) self.assertIn('bool_value = false', unparsed) self.assertIn('digit_value = 5', unparsed) self.assertIn('digit_value = 10', unparsed) if __name__ == '__main__': unittest.main() plyara-2.2.8/tests/test_linters.py0000775000175000017500000000713514751212104015364 0ustar rharha# Copyright 2025 Malwarology LLC # # Use of this source code is governed by an MIT-style # license that can be found in the LICENSE file or at # https://opensource.org/licenses/MIT. """Unit tests for linting the project modules and the unit test modules. Imported From: https://gist.github.com/utkonos/9c9ac127d2d08e648c58c4e07bf68a25 """ import contextlib import io import pathlib import unittest import pycodestyle import pydocstyle import pyflakes.api exclude_paths = {'build', 'venv', 'ply'} max_line_length = 120 pydocstyle_ignore = ['D102', 'D104', 'D107', 'D203', 'D213', 'D406', 'D407', 'D413'] ply_ignore = ['D205', 'D207', 'D208', 'D209', 'D300', 'D400', 'D401', 'D403', 'D415'] ply_files = ['core.py'] class BaseTest(unittest.TestCase): """Bass class for all test cases.""" def setUp(self): """Set path object to package directory and build list of Python files.""" self.package_dir = pathlib.Path(__file__).parent.parent self.to_check = list() for path in self.package_dir.rglob('*.py'): if not exclude_paths.intersection(set(path.parts)): self.to_check.append(path) class TestPyCodeStyle(BaseTest): """Check formatting of code using pycodestyle linter.""" def setUp(self): """Initialize the test fixture.""" super().setUp() self.style = pycodestyle.StyleGuide(show_source=True, max_line_length=max_line_length) def test_pycodestyle(self): """Test that code conforms to PEP-8.""" for path in self.to_check: with self.subTest(file=path.relative_to(self.package_dir).as_posix()): with contextlib.redirect_stdout(io.StringIO()) as f: errors = self.style.input_file(str(path)) msg = f.getvalue() self.assertIs(errors, 0, f'\n{msg}') class TestPyDocStyle(BaseTest): """Check documentation strings using pydocstyle linter.""" def setUp(self): """Initialize the test fixture.""" super().setUp() self.cc = pydocstyle.checker.ConventionChecker() def test_pydocstyle(self): """Test that docstrings conform to PEP-257.""" for path in self.to_check: relative_path = path.relative_to(self.package_dir).as_posix() with self.subTest(file=relative_path): msg = str() source = path.read_text() try: for error in self.cc.check_source(source, path.name): if error.code in pydocstyle_ignore: continue if error.code in ply_ignore and path.name in ply_files: continue msg += f'\n{error}' except pydocstyle.parser.ParseError: self.skipTest(f'Cannot parse file: {relative_path}') self.assertFalse(any(msg), msg) class TestPyflakes(BaseTest): """Check source code files for errors using pyflakes linter.""" def test_pyflakes(self): """Test source files for errors.""" for path in self.to_check: with self.subTest(file=path.relative_to(self.package_dir).as_posix()): source = path.read_text() with contextlib.redirect_stdout(io.StringIO()) as fo: with contextlib.redirect_stderr(io.StringIO()) as fe: errors = pyflakes.api.check(source, path.name) msg = fo.getvalue() msg += fe.getvalue() self.assertIs(errors, 0, f'\n{msg}') if __name__ == '__main__': unittest.main(verbosity=2) plyara-2.2.8/tests/data/0000775000175000017500000000000014751212104013173 5ustar rharhaplyara-2.2.8/tests/data/command_line/0000775000175000017500000000000014751212104015620 5ustar rharhaplyara-2.2.8/tests/data/command_line/test_file.yar0000664000175000017500000000115614751212104020316 0ustar rharharule FirstRule { // test comment meta: author = "Andrés Iniesta" date = "2015-01-01" strings: $a = "hark, a \"string\" here" fullword ascii $b = { 00 22 44 66 88 aa cc ee } condition: all of them } import "bingo" import "bango" rule SecondRule : aTag { meta: author = "Ivan Rakitić" date = "2015-02-01" strings: /* test multiline comment */ $x = "hi" $y = /state: (on|off)/ wide $z = "bye" condition: for all of them : ( # > 2 ) } rule ThirdRule {condition: false} rule ForthRule { condition: uint8(0) ^ unit8(1) == 0x12 } plyara-2.2.8/tests/data/command_line/__init__.py0000775000175000017500000000000014751212104017722 0ustar rharhaplyara-2.2.8/tests/data/issues/0000775000175000017500000000000014751212104014506 5ustar rharhaplyara-2.2.8/tests/data/issues/issue118.yar0000664000175000017500000000011314751212104016600 0ustar rharharule test { strings: strings: $s1 = "test" condition: all of them } plyara-2.2.8/tests/data/issues/issue156.json0000664000175000017500000000146414751212104016772 0ustar rharha[ { "value": "// bytestringinternal1", "lineno": 4, "lexpos": 60, "type": "COMMENT" }, { "value": "// bytestringinternal2", "lineno": 6, "lexpos": 152, "type": "COMMENT" }, { "value": "/* bytestringinternal3a\n bytestringinternal3b\n bytestringinternal3c */", "lineno": 7, "lexpos": 209, "type": "MCOMMENT" }, { "value": "// bytestringinternal4", "lineno": 10, "lexpos": 386, "type": "COMMENT" }, { "value": "/* bytestringinternal5a\n bytestringinternal5b */", "lineno": 12, "lexpos": 472, "type": "MCOMMENT" } ]plyara-2.2.8/tests/data/issues/issue99_2.yar0000664000175000017500000000005414751212104016755 0ustar rharharule test2 { condition: false } plyara-2.2.8/tests/data/issues/issue143.yar0000664000175000017500000000016514751212104016605 0ustar rharharule test { strings: $xbug = "CatalogChangeListener-##-##" xor(0x01-0xff) condition: $xbug } plyara-2.2.8/tests/data/issues/issue141.yar0000664000175000017500000000017614751212104016605 0ustar rharhaimport "pe" rule minimal_test { condition: True } rule minimal_test2 { condition: True and pe.is_pe } plyara-2.2.8/tests/data/issues/issue109_good_enough.yar0000664000175000017500000000032314751212104021160 0ustar rharharule sanity_check_external_variables { meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" strings: $a = "test" condition: for count_of_test i in ( 1 .. #a ) : ( @a [ i ] < 100 ) } plyara-2.2.8/tests/data/issues/issue156.yar0000664000175000017500000000114014751212104016603 0ustar rharharule test1 { strings: $op = { ABABABABABAB // bytestringinternal1 CDCDCDCDCDCD EFEFEFEFEFEF // bytestringinternal2 A1A1A1A1A1A1 /* bytestringinternal3a bytestringinternal3b bytestringinternal3c */ B1B1B1B1B1B1 // bytestringinternal4 C1C1C1C1C1C1 D1D1D1D1D1D1 /* bytestringinternal5a bytestringinternal5b */ ~EF } condition: $op } plyara-2.2.8/tests/data/issues/issue145.yar0000664000175000017500000000050514751212104016605 0ustar rharharule test1 { strings: $op = { AA AA ~AA } condition: $op } rule test2 { strings: $op = { AA AA~AA } condition: $op } rule test3 { meta: one = -0 condition: true } rule test4 { condition: -0.5 } rule test5 { condition: -1.5 } plyara-2.2.8/tests/data/issues/issue115.yar0000664000175000017500000000153714751212104016610 0ustar rharharule bad_parsed_subtraction{ meta: author = "Rakovskij Stanislav / disasm.me" date = "09.03.2021" description = "test rule in which we have bad parsing of minus sign between two variables" strings: $a = "Test" $b = "Test 2" condition: @a-@b<128 } rule good_parsed_addition{ meta: author = "Rakovskij Stanislav / disasm.me" date = "09.03.2021" description = "test rule in which we have bad parsing of minus sign between two variables" strings: $a = "Test" $b = "Test 2" condition: @a+@b<128 } rule rule_extra_empty_line { meta: author = "Rakovskij Stanislav / disasm.me" date = "09.03.2021" description = "actually magic" strings: $a = "hello" $b = "world" condition: @b-@a<128 } plyara-2.2.8/tests/data/issues/issue153.yar0000664000175000017500000000027714751212104016612 0ustar rharharule test1 { strings: $op = { ABABABABABAB // bytestringinternal1 CDCDCDCDCDCD // bytestringinternal2 ~EF } condition: $op } plyara-2.2.8/tests/data/issues/comment_only.yar0000664000175000017500000000004314751212104017723 0ustar rharha// This rule file has been deleted plyara-2.2.8/tests/data/issues/__init__.py0000775000175000017500000000000014751212104016610 0ustar rharhaplyara-2.2.8/tests/data/issues/issue112.yar0000664000175000017500000000510314751212104016576 0ustar rharharule minus_bad{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have bad parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after-512 .. @str_after) } rule minus_good{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have good parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after - 512 .. @str_after) } rule minus_very_bad{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have bad parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after- -512 .. @str_after) } rule minus_very_very_bad{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have bad parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after--512 .. @str_after) } rule minus_bad_hexnum{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have bad parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after-0x200 .. @str_after) } rule minus_good_hexnum{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have good parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after - 0x200 .. @str_after) } rule minus_very_bad_hexnum{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have bad parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after- -0x200 .. @str_after) } rule minus_very_very_bad_hexnum{ meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" description = "test rule in which we have bad parsing of minus sign" strings: $str_after = "END_TAG" $str_bef = "START_TAG" condition: $str_bef in (@str_after--0x200 .. @str_after) } plyara-2.2.8/tests/data/issues/issue99_1.yar0000664000175000017500000000005314751212104016753 0ustar rharharule test1 { condition: true } plyara-2.2.8/tests/data/issues/issue107.yar0000664000175000017500000000020514751212104016600 0ustar rharharule test{ strings: $TEST1 = "testy" $test2 = "tasty" condition: ( #TEST1 > 5 ) and ( #test2 > 5 ) } plyara-2.2.8/tests/data/issues/issue150.yar0000664000175000017500000000021614751212104016600 0ustar rharha// Comment1 rule test1 { condition: true } // Comment2 rule test2 { condition: false } /* Comment 3 next line */ plyara-2.2.8/tests/data/issues/issue144.yar0000664000175000017500000000546514751212104016616 0ustar rharha/* Example rule with negative quality from https://github.com/YARAHQ/yara-forge */ rule SIGNATURE_BASE_WEBSHELL_ASP_Encoded_Aspcoding : FILE { meta: description = "ASP Webshell encoded using ASPEncodeDLL.AspCoding" author = "Arnim Rupp (https://github.com/ruppde)" id = "788a8dae-bcb8-547c-ba17-e1f14bc28f34" date = "2021-03-14" modified = "2023-07-05" reference = "Internal Research" source_url = "https://github.com/Neo23x0/signature-base/blob/6b8e2a00e5aafcfcfc767f3f53ae986cf81f968a/yara/gen_webshells.yar#L3770-L3876" license_url = "https://github.com/Neo23x0/signature-base/blob/6b8e2a00e5aafcfcfc767f3f53ae986cf81f968a/LICENSE" hash = "7cfd184ab099c4d60b13457140493b49c8ba61ee" hash = "f5095345ee085318235c11ae5869ae564d636a5342868d0935de7582ba3c7d7a" logic_hash = "a0f0b8585b28b13a90c5d112997cacea00af8c89c81eda5edf05508ad41459ab" score = 60 quality = -5 tags = "FILE" license = "Detection Rule License 1.1 https://github.com/Neo23x0/signature-base/blob/master/LICENSE" importance = 70 strings: $encoded1 = "ASPEncodeDLL" fullword nocase wide ascii $encoded2 = ".Runt" nocase wide ascii $encoded3 = "Request" fullword nocase wide ascii $encoded4 = "Response" fullword nocase wide ascii $data1 = "AspCoding.EnCode" wide ascii $tagasp_short1 = /<%[^"]/ wide ascii $tagasp_short2 = "%>" wide ascii $tagasp_classid1 = "72C24DD5-D70A-438B-8A42-98424B88AFB8" nocase wide ascii $tagasp_classid2 = "F935DC22-1CF0-11D0-ADB9-00C04FD58A0B" nocase wide ascii $tagasp_classid3 = "093FF999-1EA0-4079-9525-9614C3504B74" nocase wide ascii $tagasp_classid4 = "F935DC26-1CF0-11D0-ADB9-00C04FD58A0B" nocase wide ascii $tagasp_classid5 = "0D43FE01-F093-11CF-8940-00A0C9054228" nocase wide ascii $tagasp_long10 = "<%@ " wide ascii $tagasp_long11 = /<% \w/ nocase wide ascii $tagasp_long12 = "<%ex" nocase wide ascii $tagasp_long13 = "<%ev" nocase wide ascii $tagasp_long20 = /<(%|script|msxsl:script).{0,60}language="?(vb|jscript|c#)/ nocase wide ascii $tagasp_long32 = /0 and (#jsp4+#jsp5+#jsp6+#jsp7)>0))) and all of ($encoded*) and any of ($data*) }plyara-2.2.8/tests/data/issues/issue109.yar0000664000175000017500000000031214751212104016601 0ustar rharharule sanity_check_external_variables { meta: author = "Rakovskij Stanislav / disasm.me" date = "22.08.2020" strings: $a = "test" condition: for count_of_test i in (1..#a) : ( @a[i] < 100 ) } plyara-2.2.8/tests/data/utils/0000775000175000017500000000000014751212104014333 5ustar rharhaplyara-2.2.8/tests/data/utils/rebuild_ruleset_good_enough.yar0000664000175000017500000000105614751212104022620 0ustar rharharule FirstRule { strings: $a = "hark, a \"string\" here" fullword ascii $b = { 00 22 44 66 88 aa cc ee } condition: all of them } rule SecondRule : aTag { strings: $x = "hi" $y = /state: (on|off)/ wide $z = "bye" condition: for all of them : ( # > 2 ) } rule ForthRule { condition: uint8( 0 ) ^ uint8( 1 ) == 0x12 } rule FifthRule { condition: file_name icontains "filename" and file_name endswith ".exe" and file_name iendswith ".exe" } rule SixthRule { condition: file_name startswith "file" and file_name istartswith "file" } plyara-2.2.8/tests/data/utils/detect_dependencies_ruleset.yar0000664000175000017500000000622714751212104022600 0ustar rharha/* License: This file contains rules licensed under the GNU-GPLv2 license (http://www.gnu.org/licenses/gpl-2.0.html) Version 1-20180211, author:unixfreaxjp */ private rule is__osx { meta: date = "2018-02-12" author = "@unixfreaxjp" condition: uint32(0) == 0xfeedface or uint32(0) == 0xcafebabe or uint32(0) == 0xbebafeca or uint32(0) == 0xcefaedfe or uint32(0) == 0xfeedfacf or uint32(0) == 0xcffaedfe } private rule priv01 { meta: date = "2018-02-11" author = "@unixfreaxjp" strings: $vara01 = { 73 3A 70 3A 00 } $vara02 = "Usage: %s" fullword nocase wide ascii $vara03 = "[ -s secret ]" fullword nocase wide ascii $vara04 = "[ -p port ]" fullword nocase wide ascii condition: all of them } private rule priv03 { meta: date = "2018-02-10" author = "@unixfreaxjp" strings: $varb01 = { 41 57 41 56 41 55 41 54 55 53 0F B6 06 } $varb02 = { 48 C7 07 00 00 00 00 48 C7 47 08 00 00 } $vard01 = { 55 48 89 E5 41 57 41 56 41 55 41 54 53 } $vard02 = { 55 48 89 E5 48 C7 47 08 00 00 00 00 48 } // can be added condition: (2 of ($varb*)) or (2 of ($vard*)) } rule MALW_TinyShell_backconnect_OSX { meta: date = "2018-02-10" author = "@unixfreaxjp" condition: is__osx and priv01 and priv02 and priv03 and priv04 and filesize < 100KB } rule MALW_TinyShell_backconnect_ELF { meta: date = "2018-02-10" author = "@unixfreaxjp" condition: is__elf and priv01 and ((priv02) or ((priv03) or (priv04))) and filesize < 100KB } rule MALW_TinyShell_backconnect_Gen { meta: date = "2018-02-11" author = "@unixfreaxjp" condition: ((is__elf) or (is__osx)) and priv01 and priv02 and filesize < 100KB } rule MALW_TinyShell_backdoor_Gen { meta: date = "2018-02-11" author = "@unixfreaxjp" condition: ((is__elf) or (is__osx)) and priv01 and filesize > 20KB } rule test_rule_01 { condition: (is__elf) } rule test_rule_02 { condition: is__osx and is__elf } rule test_rule_03 { condition: is__osx } rule test_rule_04 { condition: (is__elf or is__osx) } rule loop1 { strings: $a = "dummy1" $b = "dummy2" condition: is__osx and for all i in (1,2,3) : ( @a[i] + 10 == @b[i] ) } rule ExternalVariableExample3 { condition: string_ext_var contains "text" } rule ExternalVariableExample4 { condition: string_ext_var matches /[a-z]+/ } rule ExternalVariableExample3 { condition: is__osx and string_ext_var contains "text" } rule ExternalVariableExample4 { condition: is__osx and string_ext_var matches /[a-z]+/ } private rule WINDOWS_UPDATE_BDC { condition: (uint32be(0) == 0x44434d01 and // magic: DCM PA30 uint32be(4) == 0x50413330) or (uint32be(0) == 0x44434401 and uint32be(12)== 0x50413330) // magic: DCD PA30 } rule SndVol_ANOMALY { strings: $s1 = "Volume Control Applet" fullword wide condition: filename == "sndvol.exe" and uint16(0) == 0x5a4d and not 1 of ($s*) and not WINDOWS_UPDATE_BDC } plyara-2.2.8/tests/data/utils/logic_collision_ruleset.yar0000664000175000017500000000234514751212104021767 0ustar rharha// This ruleset is used for unit tests for hashing strings and condition - Modification will require test updates rule Set001_Rule001 { strings: $a = "foobar" condition: $a } rule Set001_Rule002 { strings: $b = "foobar" condition: $b } /* // Although they match identical content as the above two rules, // the following four rules do not yet return the same hash. rule Set001_Rule003 { strings: $aaa = "foobar" condition: any of ($*) } rule Set001_Rule004 { strings: $ = "foobar" condition: any of them } rule Set001_Rule005 { strings: $ = "foobar" condition: all of ($*) } rule Set001_Rule006 { strings: $ = "foobar" condition: all of them } */ rule Set002_Rule001 { strings: $b = "foo" $a = "bar" condition: all of them } rule Set002_Rule002 { strings: $b = "bar" $a = "foo" condition: all of ($*) } rule Set002_Rule003 { strings: $ = "bar" $ = "foo" condition: all of ($*) } rule Set002_Rule004 { strings: $ = "bar" $ = "foo" condition: all of them } plyara-2.2.8/tests/data/utils/rebuild_ruleset.yar0000664000175000017500000000104214751212104020236 0ustar rharharule FirstRule { strings: $a = "hark, a \"string\" here" fullword ascii $b = { 00 22 44 66 88 aa cc ee } condition: all of them } rule SecondRule : aTag { strings: $x = "hi" $y = /state: (on|off)/ wide $z = "bye" condition: for all of them : (#>2) } rule ForthRule { condition: uint8(0)^uint8(1)==0x12 } rule FifthRule { condition: file_name icontains "filename" and file_name endswith ".exe" and file_name iendswith ".exe" } rule SixthRule { condition: file_name startswith "file" and file_name istartswith "file" } plyara-2.2.8/tests/data/utils/__init__.py0000775000175000017500000000000014751212104016435 0ustar rharhaplyara-2.2.8/tests/data/utils/rulehashes.txt0000664000175000017500000005272714751212104017254 0ustar rharhav1_sha256_b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b v1_sha256_d625c96fb568e0c09a1dd30ffb7131fdc3283beb3765efabb1550b7749534f28 v1_sha256_6e4235b85c28a5588e4aee487208fffe3396252ca3b8fbd8b380be2aa7ffaba8 v1_sha256_48d5066ef3a8892e8b46d265c7965c7d7e32c94eda6fda9a22bb5ba8511c9ba5 v1_sha256_fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa v1_sha256_35c7123f53f3ff008f2c2b4a597dfb21a201d42e231f5c4d079858f1c107097a v1_sha256_7e60301adaa02cff63834e821e2fba8e13aeb0cbc3d40f35911a71598e844657 v1_sha256_337fedbc074ec447a95ba4b50e36f11dbbca513821e939a7f5779a0bdb980032 v1_sha256_cafcf706500f101c7c2c3361c43e7ce85d3aa839e73443f0b6c943041c2c52a7 v1_sha256_6e678a811f3695e52111271d3aa97189cfcf8d230ca67dc2d442ff851e417cb5 v1_sha256_233ab605c528c99f351f0a70b7b5b685fe78fa3ee7629ea041cf45343173624c v1_sha256_ab28b5e9c550a0c41f837544c56de18696c363a3b7d5f80f82a28783c573f167 v1_sha256_31497df25b6b1557dccb9fe11b20ee00b867d30f5a4e9b5a83275a71371cdabc v1_sha256_21a4f89e07b0d233a4e536e600b22740da6e87ebac4efe9221561f763d20347d v1_sha256_d5ed2224d36719855f93d535a6305dd80a2e585211b82504efcd5464b17d2e3d v1_sha256_24e897d1960c73fc2c0ccb8539189123381fa72e65981468c5cde4c6e7f44f79 v1_sha256_23414016f63d10cf38e28a8a7853b0be821984f2eb3b3e801abbdcb7413e31e4 v1_sha256_3f0459a62a3437d3da09fa05491214c33360e90da77416bb0eaaa4bd3ad73b4a v1_sha256_744c817b4cfb184693f02e5d320f5fea20c06027b9501c34c9ca2c138c8f1719 v1_sha256_eec9e3a8fe93a554edad5559bf73faa2ea8448e416e50af063807cdbea1282d6 v1_sha256_850f7b8ab28ae4fd099de78cb5f652d212bb34e20be0d4c4297202993cd90686 v1_sha256_fa69df58aff909bcb74bf01b0211e775f4a18b33f010368f9dde8339a306d274 v1_sha256_3390d9d8d6d030dab5d1f9910985d75e02bf168adfbe0e131c447603b21a8dba v1_sha256_c7c6d5a6cd37fbc61bb0273aa3cae578855f1695fe8489e35572ecda1644e153 v1_sha256_9ad757632d401c10e85117cda6a91e1243bf33fe65822db7788ed31ba17979d2 v1_sha256_52f4dcdbbe287eada284a7ac870d7850455f9316692d2a783cd85dfac89e7f21 v1_sha256_dd0aa31717bacb2f692a72a6f58088a7c97d35b4b0f35c1a5cfed41c7d4bc8e0 v1_sha256_2caa7c62df6c10bc55dfcbca4df69bbb035101bd5e90f2db95a92b952ad72743 v1_sha256_e50919daa2cbfd3410a2cf936804dbebf5ef8e26e06e036f93c6e781e410e5d4 v1_sha256_978521088871b4476b78edc63ce97d323e53b7e63036c69ee915c9b627062106 v1_sha256_fece85a1563e5d9c30ab923148f7332a779212184ac06b406d03bd9b31df3507 v1_sha256_a46a61c2c9004858a99c68e1d8b417e582ed9eccd64d3b066ef6a182abdfd6ee v1_sha256_b6a7c7c3b8a87d2238d1f678c2e77affd6ddd59aed0c343061da4c3d2e5a5c7b v1_sha256_b6058483da2793a09cdaefa90ac4f678849d46f7ef8972a06f8f10000e6c7da8 v1_sha256_f1e2aee6541de036c6e5de385798a2cf3ff1c74b6b89ae5b65d92c884cbd0b81 v1_sha256_93ce1adb7bceb7a0852c4de97b293a9d8c770d676b2ecd7c38059d80a77fbb8a v1_sha256_a2bd69c2b74a634b8e596a2ced695f4ecf84facef2201d5c48321f3a6b82db73 v1_sha256_d0d7621722b19407c20ad8c8cfbc7ffc926e68ca6bc18b31da2b0a8119264665 v1_sha256_14b3670ce59c25a7af65d7d043530028da62badaa5f44a448a9a19491972a720 v1_sha256_b49fa4eadfba58752c83a7aed680d48498febcb945c1fd75c45d6addbfa328da v1_sha256_59233c200a123f105877fe3b4497b68ef744594e58295f43bf37bdea3bed7af0 v1_sha256_f7fd2c110d24c4d4f5a78f03fde32a4833d3ffc7443171268d46da8fa910ac68 v1_sha256_6d36773a867e51b78dc2c48f1332d9a4041fe3f2665ad7698bc63f6df6925d9d v1_sha256_a6565988a60034e0e58a2515f27502573569ae0d6de0aaccd7ff61f9c6742999 v1_sha256_77110ef9e36e7ecbcdc7c94c6a94740c48d5d1fdc9f50d372b4b4fea7b4db0bd v1_sha256_0937e7c5e5be2fcd0923aa9a964ddb4b197c24c3c3c212242bd2eae41e8c07dc v1_sha256_858bc0c9ab0b5eeae5bcd9f9e23c230bddcb6c57e639f8677c564b7cb82d1f37 v1_sha256_34ed4ea989005f3a52429c683ff3885c62cd61377006cf3e516485cf71b3e435 v1_sha256_454320053a83d8cf3fed72ccddf8a8c3ebcb4b9c88893c3d182281081fda5352 v1_sha256_233918f5b8c0992b20ef5b6508cb4e0c9a777528a7c86150c75bcdbaa1200c0f v1_sha256_f7d8471b7b6cebabf76bcace1a3d984b067444d9ee4cc01b9eebc6b94b61d62c v1_sha256_4eefab5519d908aa13b9f66ad161c3f555e395c17605262bb08c618017aa3ba8 v1_sha256_7a344f3f20d58c33df3ab2803c57c13e4b03a52537819aee002a7798d2f3c051 v1_sha256_8fc690c1356029c213d06ad0b12a161b019ba4fe49f559b0827251a91a8977eb v1_sha256_622b4b2cdab5a85f10562b12f93c19b6a7e8c9795aab0a02602c4f77e4f1f97a v1_sha256_5867a8cd0d5d2ff73ea4262eff75e032f0143ee96f23d1a8ac11f14afa50d228 v1_sha256_33f391851dc7dbd13e244e65d7e4b03f37a0b953e28cb3ac2cd7e12605a18df2 v1_sha256_1332a1c0f8bdb7856c1eaaac3e9232fcf85d3ebb0278207f68c0f2022e19c170 v1_sha256_d29abf9dc66fc3e211078947f10bd8de5df1e841e3c5eacd1ddb23ae27cc9588 v1_sha256_8344a0ee5758dcbb11f3324889b000fce105c3fd751d3b4630681db0e6c5c797 v1_sha256_438b7e7ba068932af2347af0caf7642fc7614dee240323f81e0db8b71af5531e v1_sha256_b4c70e39cbfae94108c3f6817301e46a8d5d5175988760739a38b2591ec4e73c v1_sha256_855f0f0b68cd069648114327dc7e46e4675b9bfaefa6fdae5577b521dbdb3a5d v1_sha256_59c0b5e5001a5b5970783081cf2fb82251e8020300e5571e970160eedce9d11a v1_sha256_ea6ba6b705a6cddf11fb0af4f52cea3ec45c2aead28dbd16f20b0e2b54bff2fd v1_sha256_9c744cafd13de55ef58c031ccb1695425e33cc3f6eeee7618cefefc62fd565d7 v1_sha256_94c5b8876151ce981171db9bd5b305fc69aac566403601714f12dcd4791b6e51 v1_sha256_bc4aba77900a563f21975e3be936786a4bbe9eec2d61546ccba548ad973dcee5 v1_sha256_80fd1b14e764bc6181b4920cd8e3f7c0d567bce37b818d067e66341026b0c3f2 v1_sha256_4b65099b1bcbdd2aaecd3e9b917c6d76dbeb9a367ab6e15c73ad624f15efbb1b v1_sha256_1b5146472ca54388a973229defce53f0319b90d2ca73ff6c739c24ff2b2a5fe0 v1_sha256_b61e5fcd412651889aafdc83a6a36195ef8806cee320dfef3a28d5b057707424 v1_sha256_56f31be82f5fa1ed932b42574c91b5d57866eece83193afe01551033d340d5af v1_sha256_8a562ac9831e27743fa69622072726c91cf593ef8cd65380e2b6208045218e03 v1_sha256_76c38b6b9c91c970140eb5aca4ce7aa1475f55d9e8b2f1fc04b51f5eb96bab63 v1_sha256_cb941ffac208d3617d17f2ffe812808eb00f7533b1bd817388bbb64c5cef6fbf v1_sha256_e5527ad7feda7128a8da438e1b44b4e56ff2db621f1b2d10c98e006614cdd3a9 v1_sha256_823ce68a445823098e6b7acb18c70318c95f7853de2d8c5968a530b53c266464 v1_sha256_6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 v1_sha256_28d94acbee4119701830f2a5bde19c0b4e62157dcf7b526a9a16d96bedc6e861 v1_sha256_2cb2837744913f3504de915e72cd4905672c4f648f3a82954d6cb3213cd7d88b v1_sha256_eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 v1_sha256_448c13819aefbd6cb88a0baef7d195cdc299c766c98ef65fa2d26cf9103886a3 v1_sha256_0ee55483f4da8116aa12790859016fc499ff139e5c3bbeaf58936780bb1d5194 v1_sha256_42c5c50301f7a09feccbc6733f8258d377112c8a6194a0f1a152a0c02f025742 v1_sha256_a3fa72ced02134135a4b597e3e1b4e8b1509c63d7cc7875b3815ec861401c587 v1_sha256_b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 v1_sha256_0f083d0561f6293c712b219dd7876101bc35622366715967277b3fea9e796677 v1_sha256_d7d55842bb7847ea099d3ccb9ad7d4f9ea5005a6b54c1cfba08223af81bea44e v1_sha256_2d900fe9142d858d025e58bce6777839c3ac56b08f91db1df01ffb56c00ce78b v1_sha256_61c54d7eab99ca2b145279c4cdc79cb7a7362f09b998271cf8b4aa851cff7e25 v1_sha256_6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 v1_sha256_6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 v1_sha256_6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 v1_sha256_6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 v1_sha256_b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 v1_sha256_3b7d00c8bc4d8d9927ab67cd945bf6a06ab2b35e53f7213612db44bcb2211c52 v1_sha256_57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 v1_sha256_57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 v1_sha256_eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 v1_sha256_eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 v1_sha256_b4b177dddaa32cefe81e39074ec09e9863613a48589d20289ffe1e48c6475f59 v1_sha256_eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 v1_sha256_eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 v1_sha256_1d9857c194ac02b204ab40f6e410597b88c8e9fea398977d243832d3f27af8cc v1_sha256_79535bcef9252a3f988515bd33a963ff5db119b3b42c6c6b263f60fa6bf0fff7 v1_sha256_a8c95988f4ee0d3a8347bc71347d8d3e0144663034ffd4ce3a61c96c7378c247 v1_sha256_61507c8074e56e0bb9dc6e17b87dfc0f27cddec39f8ee5fea66da282af5dbd7f v1_sha256_ff0159a9e24f5bed4486fc206b87e6861ace224b69cbb2a15a3660d6c1c78ab2 v1_sha256_7311ff08867cfc0c21f65fde14e2d74cb1ec4a530004e7e1134fa638a6885aad v1_sha256_534e2bbec22b7fdc5bcf64d1aa9c4ba18511b3bc64af76c333954c663c166f53 v1_sha256_373e1cca8482e2f91fc8c7f24871de37a20cd0d0157957668a17f518adda954a v1_sha256_7055d18035295d7e79f5fb7d48cec88241006f26cdd3a4a56e38634f03caed4b v1_sha256_b98960cdf0dc1e4341ed411bf35ca9d2a5d5930a0f6fe9339b1777a475e3b671 v1_sha256_6f7e951df4257cd339ee5d0dc753bc4b5c84d4ef377a2abb429e90bd2bc249c0 v1_sha256_4a5802a54145b5c528f28060ebba76f99b09e38d5c85453b41bd39333f548dc0 v1_sha256_ea1fbccbc92d8a893a08b8725eda9708fff27201809c4bb54c584bd4590053f2 v1_sha256_e9aecb612643021ba59416b8acba38b18311f33507e9de4290ec73b17ac79f6d v1_sha256_f77b3e9a82e7d8fee7e90a441946cde5bb34e9ca140dfd9867ae1969df132e8f v1_sha256_4137b404f831ad09946d9670aab06eaa79efb5a1f8e5d9bec788244a8b91b416 v1_sha256_0ee02a3dd03826273764d6c8d99e084f0e43d0a9a8c1f4fef9e7be6ebf076235 v1_sha256_d2c6532a650dfdb5add9ac35ac2812abc79901bb53efceeda811f66f6f36e6bc v1_sha256_bdd70e3a96a094b10114cf2de544513221bdf4817b372d3673d9812bc3014706 v1_sha256_602acca95d424aa7b3bc303ea6f48468f150bf3446237afadb664f9e5d43ea96 v1_sha256_0ee02a3dd03826273764d6c8d99e084f0e43d0a9a8c1f4fef9e7be6ebf076235 v1_sha256_07b4f1381e939ff4863946df80415382b5571d61e5802b4fa22f94b262837698 v1_sha256_a38b2b1f0d8743d7e7842d2e828ea00c491b4a2fec94a4964e3fc04455716e30 v1_sha256_a3427bc7cad83a0310619dc84d6ab03170424a24c96c957950ba8a86044d1fa4 v1_sha256_efd56f96a8a9280e841cfc54773b53e88a7654855f1d14224f1367114cda01f8 v1_sha256_fdd9520246f00efb95b1dde38571e56f2fe636bface44d5e99da73317a536031 v1_sha256_10733fffd6cb51f75729733b160fbb6eb95ef10b6ad63f0dc83717df0d4f3645 v1_sha256_8b45632c84dffa3b00c1b5165c71f88d83535570e0b64bed51885b7db937e189 v1_sha256_bcb52f695698f6d87c4028588b60e87d7fd3e4ec033a9752d1300e3aa170826a v1_sha256_e2aec810a7e0fd6c7bf95777699258c1be5032ca037c1ecc9c21ff0e58f9815e v1_sha256_1f147d73dc5b2a432c26a4b2b8bc4f59000556e343313232d7dd8f41c0c7c9f1 v1_sha256_a82c5f118c138990383dc8385dd645809b9bb9f4fa13e51196a0572b8103b582 v1_sha256_1a12f768609126da3937b4aaf53ea0d2f1c7945cff1f8371ae9ac8128eba82ab v1_sha256_87df8988524de69102e410b5e33bd5a2333762994963fb6ee2b731c8aec9f6f5 v1_sha256_e51821108d9312a616571a24bac528dd67f7ec625a2fd28509f4690fd7512baa v1_sha256_f20a932dbf6502ccef7ec8494aa226ade59c1f9a988c01c7a8dcfb5aae3ea159 v1_sha256_93d9037e7529cd7e50573d7d36222ae2db2baab6173f95774c4f1fcd2644d565 v1_sha256_dba3ea7749ce8afa934d7d3dd55eff8afe095f09e33e69836118d494eac0df9b v1_sha256_373ef4ea5952433e6ec8a1ea7c5bda6e4edd69d62d6f2dbee68d2c8b9a760713 v1_sha256_000fd941376b243d4ce19839aa6e0798c610da99c47bc0cbbea3a81b967a4ace v1_sha256_141b164cab2d321f3f07a1e2ea68bc04bf9073aa5c2ad701c212e415eb2dc65c v1_sha256_77ae236316021cf25914361fbb16ff4d3f24951edebd6cf6aa652c26654d1d69 v1_sha256_e462fcbef2160111b45e2de3aaf99c4c1daa2aa71c5e64ff307702e453d1a5b5 v1_sha256_5584764a0fbdddde431909b5e84568c0ec4308a27bf6b93230984caff3cc05c4 v1_sha256_e5ac97648e6fcb58cae4be7fcf39229a5700cb95086098faa5eadc4a8ba0a238 v1_sha256_777bf0f5bcfc3bd8233eb93dd6ec29a03c9fd780c706234a9cb856a2306d29c2 v1_sha256_e0379d82f6be001031c63966c98cd9e6a40fe4e4573b80b79949b4245aac65dd v1_sha256_98c6a60db8d5638e36acb79d36a0ecd759e238b051eba2f5cb2968d593ea8b84 v1_sha256_36ffd44421550f5d49155500ac4c3ea949eba8926aa6c28a12708b73df4d2dbe v1_sha256_2096e4cd078307e9f6dfd5896f7983afc84810332c43a0e13f9c75ab4a5e1fff v1_sha256_2a2ca2716342acde21cdb2139ba341d8cd5d1ad19bf86577fe2478ac4c9f75a4 v1_sha256_75190af954fbb55013a946d5c89c362a7f8893f0277ce97187eb5c95995e6f2f v1_sha256_25fb21923178b636cf351ca8cf1db0bb402a44b539179a0a8bf82fd86bb61f7f v1_sha256_90255ab5a0bf48636e14bfbf5ef457fb7bdb7f3b852b97f6296c0d0aa5d597c6 v1_sha256_65c86b2d669db528d4899bf28b842557403bee2b4caadaa97856fd9fad51cddf v1_sha256_82e13ec666ef22eafac1e0fc9819256c9b2e54c6144e1195c40e9f273116825d v1_sha256_08eefdf3eb717d2da91eea6578dd13aeee6b91f60160a1476db39e59a23c9b89 v1_sha256_cb43bd091f9c115755381f011d55758b9cfbb168252a9cf214082113d352d34f v1_sha256_90731b250381c7f5fd78a1d0ab090f84c2771490209a6aa15379a2a2cbfdbfd0 v1_sha256_9c4759f03806f3e1fb335a842f42aa5b95f2f99662bb1b05996f81644b7a002c v1_sha256_e7c1f9c8fcb2d700334ad4233ddbe938a722a9db34817fa30f17d392d1feeac6 v1_sha256_5bf4290172017a28c9bad170d64e194281e166afb24cd9940ebc3f5d248b002c v1_sha256_60218558c22a6ae24f7be02c25fd926f2d24727173e5359cf083c762e369bb72 v1_sha256_c9e6e99561727771205c1b2c2f59d20eb9ad8f390f1556c7621720731ab945e4 v1_sha256_fc023deca888bf41e3625e570a7d6ab4ded1fa7ec40c88a221931f77a2632820 v1_sha256_1951b49eb53765753bdedfed9802cff70701134ddf661f5d45487fa1bf6ed97f v1_sha256_d1b3bf70935250b7551d15ff12dc799166e6cc2a7cf26f0e6c90fd01225035e1 v1_sha256_a063a5852652ce4303c6ac4e0db71aa1fc28e06276f2945edbe0eca482892301 v1_sha256_2253d4c27f53a5376da2f59beb5268d8557b9ed80e258fde0406263af17f4b90 v1_sha256_ea5534cbedbf39fb4b0d025a2cc1b2eac486d8139616eef224946a1265765a53 v1_sha256_7daafcb0a11658fb619fc091fb7a9ddc8896e2275bf4c7389bfc1763ec2092a8 v1_sha256_227f854c373fd03ad8c9ba9eb6492c012e7b73c81da20c1a32b3d958f765627b v1_sha256_7c16dc84fb0976c8a4a1f0d4d0709d778fc03e0d7bdb18086b2945e5aac9fdd5 v1_sha256_393cae7c30d75c6b2636bf7ed4ecda0f56c23ba0a3b662b3419b1e69aeb3c3e8 v1_sha256_cb0640a3c09db87889bdcb3fa96dbb2906e685bf04488501b0b90f977b63f72b v1_sha256_83acd4899678786dbbf0873134f7ddde8594ccdf2779727fe3786c041976ac58 v1_sha256_a9528ef5de417405b51299592fa4d6ca5ce82ca4eb2ca6cee53dcce7f1924d45 v1_sha256_b3f685e297ed0f1ca40c56af73a404390d0a696e2795b8ae9f689d4a64bb6a05 v1_sha256_34a1a7ddb9305549582f643a8d52dcd8749ebb80e8aba7f65d725252dc197afb v1_sha256_dfb967a004d4406ca866b81d4e20ea0e0d4052f4be24f530f28316c0c92e0890 v1_sha256_c2017f3c2c3ee624c3cac69a0f7b4c395403a03ac3c31ef8e12ada0db3cbc07b v1_sha256_00a3df13f1738e43765dd7bf0b267938cd5f4ae7731c71cd1dce22449ea4aacc v1_sha256_3ba991991f4268c4879000d0c507861544d33c2456ae8d228db2134a08226802 v1_sha256_678c8b3a894fa60fb9d068e32701457f5c4f9c5bc9e5a2b84bca66c13a796cfe v1_sha256_9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb v1_sha256_9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb v1_sha256_9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb v1_sha256_9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb v1_sha256_9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb v1_sha256_b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 v1_sha256_b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 v1_sha256_b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 v1_sha256_b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 v1_sha256_b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 v1_sha256_84f26476b729bca519d3377ddfcac1d39ef452dee83c812cd4a8b606fab28f03 v1_sha256_abc532eb35417b44d41bbfc290ca648b5f4c9f0441cb5ab24e7fe13765c58fd3 v1_sha256_8049e401e74c66f3ad7d694e1cbb7c304509560d63645cab433dcad8955c74a9 v1_sha256_647fe40400cf861e6bff4c5d2456aca94c118d6aad82c2f3e1a35339b7807661 v1_sha256_9bbe09f5cf618f97a90e8ab1c481e320e9467f7133fd7d9e488aac1988f664d9 v1_sha256_7725771151d04ff2a1220b0b894da0a75b879a2a2b14610c1c386d82f3f06778 v1_sha256_543228495ffa272921c5c0b0f3954a59aa172f3a1da885aa5923eb41b98ba21d v1_sha256_0a87829d3c185186b86409f94105c1e934c18d0ab3c7075e97e6c517591d264e v1_sha256_6c1b9842ffb35a63d75a69a04108bf981556f5aec700210d11b16ecbe17e67f7 v1_sha256_ac9a31d1154848e295e2e84981aca08af87c7ba314bff33e637d634085edfd4d v1_sha256_15313b49f2ba25f02d15f0923cc3b87751956b9722b001d809413ca328cdc872 v1_sha256_7e630d99a4428479116d0adc203ad2154c4c8f70f5cd5e09188caea9d3fa5c9c v1_sha256_b469368104f3366ee79762a2727cd0be07cb54949ecbd9823e2bc4b0a138772e v1_sha256_9337c0094e1ffc38ac06dc78dada7b64454b70b2d9e762233a67402cc74cda99 v1_sha256_9337c0094e1ffc38ac06dc78dada7b64454b70b2d9e762233a67402cc74cda99 v1_sha256_cae0929cfe6df92972e3c5af3a9391e2b233fb67e66d7a0678b71c5a13a5aeaf v1_sha256_22ec92647bf126cb5cb56ba6bd0a26cd7e2dba2e10885c5196127b251d299304 v1_sha256_b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b v1_sha256_83495ded3182c806af4bcabbf0f88d3492512ec6644b6e61a66b55ec275c8356 v1_sha256_6a1b453172b5c6ddf5bb7aac5b779d54984fff7aabc3ecc7bc77374bd5a05ed9 v1_sha256_3f4b79f4acd358ee7ac0d65dc9aa72fc10121aa44eca1557ab68d29d93192098 v1_sha256_e493d84edd738a7747563514ed9cf887f52ef650e3b0927b2065faeeb5ba3963 v1_sha256_bf7f60162b81afeba583a859778e09bb5e25e663f2d6289185452d587065ad44 v1_sha256_8fef447823662ec28d860ded8a176662333c7bce7b465badbad21ebc017e0ef2 v1_sha256_d57401bd04eb69dac0788c8b97dfdb221769449d9ed9da80c6bf9faeecd21a2e v1_sha256_f79a895121a165a0ba07aca991675fcd4ef63b554fef3e7976893dea15896cc9 v1_sha256_08c7b537f3751506b66ba75f70abb52081d13baa047666c0c4e8faa1091496cf v1_sha256_b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b v1_sha256_b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b v1_sha256_fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa v1_sha256_b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b v1_sha256_195a92c503f1a8d4465d051363df5746cfa02a188ce2e8683b7502832706781b v1_sha256_6b0439d2fbe124cfdef39cbc2badfd9f4e6d87321dba765bce860fb50c509d40 v1_sha256_9e22d038b1a14e227518a3efd692dc9c2c01bca8e7490828086c69b03ef781bc v1_sha256_9bd63d1d658e7ba00c2400bf927ab5e21e65d0c1d84b2d713ffe124766edda3b v1_sha256_07e920470ed49261940474cdf52868ab6785f1365db988ac8d01a121bf2c0951 v1_sha256_9cf7def3df501e4531cb12a6168e1bb17ec5388cc146a7bdb5eb789de98f45de v1_sha256_74c03afc643e23d4c252132174feba2048f16a24c9dabf1c76ec189209fda5b7 v1_sha256_048852e5cb9b794a0cb60478628b4fa496f384f4586321bf5e7e9426f608f9d7 v1_sha256_650a28cca27d6d9f1369cde754e5cb31a0d496dd8a6c280fe1394f5b1d14622b v1_sha256_e88f29f8e36d3f625899719a181350c9735b479b9eeb3b29fb69ce250faf11d5 v1_sha256_dec4444c09a6e648e055abbd792395ffcc02b7e2993f221748fbe8f820616bbf v1_sha256_90212f3a2db9b90dddf892044a2949809f8e43975f83fc51bcdf21a122be5757 v1_sha256_85f3c58d9aa3a4acaeec282d978ff919d2136bc6497362b42a186d698d8e7bca v1_sha256_243fe6ffa1fe9fd0fd5e97a0581d0a0c132e0458a005068744693bc97b452086 v1_sha256_8873d18aff0febad3ac97ae0121dc9b51579a2f8c5f1206f76e7dc785e721523 v1_sha256_7a7375655345a76157528f8d46d757858aae8f98aa47c86df82e1602c7c57726 v1_sha256_091db68698872b305269e71844eae258d1ee9d54b2e8dc79aa59bd1255a47716 v1_sha256_ba9cd09a6f7619f20008af4724f86467b3ecd2204c50eeba99a58c0af7096a18 v1_sha256_5a89a844a878d69806b9acacb559ebae3a409a61decd565df4d6aab1e9d05c00 v1_sha256_6a8d0af8ecffd0f1315d4c304155902847a0552a02bc570628d46ddaf363cfac v1_sha256_4d1708171b7eedc79ab63aebe7fe3c0f080a395928f437dfdef33c6f3a6cab09 v1_sha256_abdfcb713d625f078b18c3530d6293d326e1eef7c71563d2edf8f2e43c420883 v1_sha256_e1415753a9779876ea386bdb0c97837dbc14dcd329b9654e4cb6636879c4bbd6 v1_sha256_e0a6497c077728b3cd8092ffdbc5c072c7851bf9948b728e74b06aff69d9979d v1_sha256_4b2c195c00461f1289824a23b01b4082b5399a28cbf484eb8684cd4c5642dd63 v1_sha256_8dc93de2d5be827c403938de24e1209617655375a335e9712411a1a78c8e5d6b v1_sha256_03fb73fa03f0997bdd710602bf2ff34ee2ae817e992eb757023e6148b59ddef2 v1_sha256_3d3693535b1b8d39f1b22c93ce72b1778c081e329bd2746ad136f52459a72fab v1_sha256_e2fc6f4a096740a4b37c8c619fdcfd3a1a836524e820bdc6ce7243651b5cfcca v1_sha256_34c18562e795db0135e75e258d591a680d241a979676168253d9172fa8c57891 v1_sha256_4f1a07ee6130b972713252d03afa5425196e5b8838a8d9dace2aeb13f1af5bc1 v1_sha256_3ab81c6f76af477197298cb3b948f82ee667bc3459e5ef2e1d483645798c939c v1_sha256_051fa38db3ab34e2109468a91bc2c9b48644cc3341e09ec3a42783745246efed v1_sha256_0400692620c9f76024f6c620dc1f08e7ef096c3b32813e0bba634bc632acde6d v1_sha256_bb78dcfc33c508efb8a6c37e0851f13dbfa095ad924fb96974e08e4d5cbfafe2 v1_sha256_209f925f914bfa21e188f2c67449082e266f5411949579cc3d33be4de9dc9c88 v1_sha256_baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096 v1_sha256_2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae v1_sha256_cde09d9924582456f4022a27b6f2b43080ca722721e997510e62e5dab00a220b v1_sha256_6d2162d52ceb5a49b973342b2c5532b47fe26c7a39c3a77ca1a96542af4de4e7 v1_sha256_5dc9c149c4ee909628cea1a5fca55a70d04fc72692029ea992bee87ec7a22313 v1_sha256_0f54dcef969c12adc88d3987edb7f2310b9ccd95212465d1e07da98e99907990 v1_sha256_335caedddaa0272ca4b1409a338a2016c28d51d88de0514bf98876b56da57b0a v1_sha256_856993134e200ade9c9991b501cb7d103088118a171c326b7c568bf38da7cfc0 v1_sha256_faa0c53569ac72006a6697340cefecd1b4e107d53f6a218da29f6a54f1e866c6 v1_sha256_1e25ddc3f35544fad7bb1daf717671a1253041e002cfe324c6794bb8c7b18d7e v1_sha256_9291105935716f9b96060bbb20ae8bb2578caaf405e1529a34bfb639218f1b5a v1_sha256_9438ec66d37819e100c16293d5979256ccb0265be5b742958ddc486c9376a7f4 v1_sha256_ba4b5f286996a1c97acf010bd68cd0181fbc51e5f371800480c00c97bfcecff1 v1_sha256_83d28f03580f29c776a9cd7dd10e1a1f73148571c7fe03dae39818578b9bf8a8 v1_sha256_6729ef639d305b90f22a57d54ace399b8503c74a444acb83063b18a4a487cef4 v1_sha256_edf93172d42d267daecebd739cd52a3b50ddda4e20d5b75ec5912b6944ee5a58 v1_sha256_e5da659f8d12683b36a17ca32140f2c9249574265415264e8d696637c81c4c1d v1_sha256_2d6a681e2d3bc492d4bf5ed25f1375455ce760fc85fc58113a981d1aaa43e66a v1_sha256_146772e9dba5a5943b388b2aa623342e3f50f4263416dfb25002351864ae27e2 v1_sha256_86b093c2a6451c574101f851b768051c5c43c9e4253444b7a427f94700a9d95b v1_sha256_371ecc9d767911382f9ee7300f47625e77f7002425b2d8f39cea847475e69202 v1_sha256_6dea7742e96283114a4eb283ac243d35a0bd6385a355822b727e3ed44958ceb4 v1_sha256_25e9820f0f37bb50f3be0ce639f2ab0fa929b2037be9cc538e2b75bc3b1fe18b v1_sha256_286176b7076d9ae726621ec164f3519bd88878494b65594af69662f1ef0271be v1_sha256_c56f0c6e097ae6e06c209ff87c6fb4120360d420e88c7dd644eb0c4f4efeaf5a v1_sha256_c5980e92efed926d845ce25f2938e119f0c49cb0f36f838f88ad1dd9d463c7e8 v1_sha256_05cf61808c189c20669e597580449198f6c7521a82684267aede755068693423 v1_sha256_d555878089f04bf2e738c5d6fc1f046e1e17ac7ec6fb66d04a3a9f8118682ca2 plyara-2.2.8/tests/data/utils/rulehashes_legacy.txt0000664000175000017500000004514514751212104020574 0ustar rharhab5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b d625c96fb568e0c09a1dd30ffb7131fdc3283beb3765efabb1550b7749534f28 6e4235b85c28a5588e4aee487208fffe3396252ca3b8fbd8b380be2aa7ffaba8 48d5066ef3a8892e8b46d265c7965c7d7e32c94eda6fda9a22bb5ba8511c9ba5 fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa 35c7123f53f3ff008f2c2b4a597dfb21a201d42e231f5c4d079858f1c107097a 7e60301adaa02cff63834e821e2fba8e13aeb0cbc3d40f35911a71598e844657 337fedbc074ec447a95ba4b50e36f11dbbca513821e939a7f5779a0bdb980032 cafcf706500f101c7c2c3361c43e7ce85d3aa839e73443f0b6c943041c2c52a7 6e678a811f3695e52111271d3aa97189cfcf8d230ca67dc2d442ff851e417cb5 233ab605c528c99f351f0a70b7b5b685fe78fa3ee7629ea041cf45343173624c ab28b5e9c550a0c41f837544c56de18696c363a3b7d5f80f82a28783c573f167 31497df25b6b1557dccb9fe11b20ee00b867d30f5a4e9b5a83275a71371cdabc 21a4f89e07b0d233a4e536e600b22740da6e87ebac4efe9221561f763d20347d d5ed2224d36719855f93d535a6305dd80a2e585211b82504efcd5464b17d2e3d 24e897d1960c73fc2c0ccb8539189123381fa72e65981468c5cde4c6e7f44f79 23414016f63d10cf38e28a8a7853b0be821984f2eb3b3e801abbdcb7413e31e4 3f0459a62a3437d3da09fa05491214c33360e90da77416bb0eaaa4bd3ad73b4a 744c817b4cfb184693f02e5d320f5fea20c06027b9501c34c9ca2c138c8f1719 eec9e3a8fe93a554edad5559bf73faa2ea8448e416e50af063807cdbea1282d6 850f7b8ab28ae4fd099de78cb5f652d212bb34e20be0d4c4297202993cd90686 fa69df58aff909bcb74bf01b0211e775f4a18b33f010368f9dde8339a306d274 3390d9d8d6d030dab5d1f9910985d75e02bf168adfbe0e131c447603b21a8dba c7c6d5a6cd37fbc61bb0273aa3cae578855f1695fe8489e35572ecda1644e153 9ad757632d401c10e85117cda6a91e1243bf33fe65822db7788ed31ba17979d2 52f4dcdbbe287eada284a7ac870d7850455f9316692d2a783cd85dfac89e7f21 dd0aa31717bacb2f692a72a6f58088a7c97d35b4b0f35c1a5cfed41c7d4bc8e0 2caa7c62df6c10bc55dfcbca4df69bbb035101bd5e90f2db95a92b952ad72743 e50919daa2cbfd3410a2cf936804dbebf5ef8e26e06e036f93c6e781e410e5d4 978521088871b4476b78edc63ce97d323e53b7e63036c69ee915c9b627062106 fece85a1563e5d9c30ab923148f7332a779212184ac06b406d03bd9b31df3507 a46a61c2c9004858a99c68e1d8b417e582ed9eccd64d3b066ef6a182abdfd6ee b6a7c7c3b8a87d2238d1f678c2e77affd6ddd59aed0c343061da4c3d2e5a5c7b b6058483da2793a09cdaefa90ac4f678849d46f7ef8972a06f8f10000e6c7da8 f1e2aee6541de036c6e5de385798a2cf3ff1c74b6b89ae5b65d92c884cbd0b81 93ce1adb7bceb7a0852c4de97b293a9d8c770d676b2ecd7c38059d80a77fbb8a a2bd69c2b74a634b8e596a2ced695f4ecf84facef2201d5c48321f3a6b82db73 d0d7621722b19407c20ad8c8cfbc7ffc926e68ca6bc18b31da2b0a8119264665 14b3670ce59c25a7af65d7d043530028da62badaa5f44a448a9a19491972a720 b49fa4eadfba58752c83a7aed680d48498febcb945c1fd75c45d6addbfa328da 59233c200a123f105877fe3b4497b68ef744594e58295f43bf37bdea3bed7af0 f7fd2c110d24c4d4f5a78f03fde32a4833d3ffc7443171268d46da8fa910ac68 6d36773a867e51b78dc2c48f1332d9a4041fe3f2665ad7698bc63f6df6925d9d a6565988a60034e0e58a2515f27502573569ae0d6de0aaccd7ff61f9c6742999 77110ef9e36e7ecbcdc7c94c6a94740c48d5d1fdc9f50d372b4b4fea7b4db0bd 0937e7c5e5be2fcd0923aa9a964ddb4b197c24c3c3c212242bd2eae41e8c07dc 858bc0c9ab0b5eeae5bcd9f9e23c230bddcb6c57e639f8677c564b7cb82d1f37 34ed4ea989005f3a52429c683ff3885c62cd61377006cf3e516485cf71b3e435 454320053a83d8cf3fed72ccddf8a8c3ebcb4b9c88893c3d182281081fda5352 233918f5b8c0992b20ef5b6508cb4e0c9a777528a7c86150c75bcdbaa1200c0f f7d8471b7b6cebabf76bcace1a3d984b067444d9ee4cc01b9eebc6b94b61d62c 4eefab5519d908aa13b9f66ad161c3f555e395c17605262bb08c618017aa3ba8 7a344f3f20d58c33df3ab2803c57c13e4b03a52537819aee002a7798d2f3c051 8fc690c1356029c213d06ad0b12a161b019ba4fe49f559b0827251a91a8977eb 622b4b2cdab5a85f10562b12f93c19b6a7e8c9795aab0a02602c4f77e4f1f97a 5867a8cd0d5d2ff73ea4262eff75e032f0143ee96f23d1a8ac11f14afa50d228 33f391851dc7dbd13e244e65d7e4b03f37a0b953e28cb3ac2cd7e12605a18df2 1332a1c0f8bdb7856c1eaaac3e9232fcf85d3ebb0278207f68c0f2022e19c170 d29abf9dc66fc3e211078947f10bd8de5df1e841e3c5eacd1ddb23ae27cc9588 8344a0ee5758dcbb11f3324889b000fce105c3fd751d3b4630681db0e6c5c797 438b7e7ba068932af2347af0caf7642fc7614dee240323f81e0db8b71af5531e b4c70e39cbfae94108c3f6817301e46a8d5d5175988760739a38b2591ec4e73c 855f0f0b68cd069648114327dc7e46e4675b9bfaefa6fdae5577b521dbdb3a5d 59c0b5e5001a5b5970783081cf2fb82251e8020300e5571e970160eedce9d11a ea6ba6b705a6cddf11fb0af4f52cea3ec45c2aead28dbd16f20b0e2b54bff2fd 9c744cafd13de55ef58c031ccb1695425e33cc3f6eeee7618cefefc62fd565d7 94c5b8876151ce981171db9bd5b305fc69aac566403601714f12dcd4791b6e51 bc4aba77900a563f21975e3be936786a4bbe9eec2d61546ccba548ad973dcee5 80fd1b14e764bc6181b4920cd8e3f7c0d567bce37b818d067e66341026b0c3f2 4b65099b1bcbdd2aaecd3e9b917c6d76dbeb9a367ab6e15c73ad624f15efbb1b 1b5146472ca54388a973229defce53f0319b90d2ca73ff6c739c24ff2b2a5fe0 b61e5fcd412651889aafdc83a6a36195ef8806cee320dfef3a28d5b057707424 56f31be82f5fa1ed932b42574c91b5d57866eece83193afe01551033d340d5af 8a562ac9831e27743fa69622072726c91cf593ef8cd65380e2b6208045218e03 76c38b6b9c91c970140eb5aca4ce7aa1475f55d9e8b2f1fc04b51f5eb96bab63 cb941ffac208d3617d17f2ffe812808eb00f7533b1bd817388bbb64c5cef6fbf e5527ad7feda7128a8da438e1b44b4e56ff2db621f1b2d10c98e006614cdd3a9 823ce68a445823098e6b7acb18c70318c95f7853de2d8c5968a530b53c266464 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 28d94acbee4119701830f2a5bde19c0b4e62157dcf7b526a9a16d96bedc6e861 2cb2837744913f3504de915e72cd4905672c4f648f3a82954d6cb3213cd7d88b eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 448c13819aefbd6cb88a0baef7d195cdc299c766c98ef65fa2d26cf9103886a3 0ee55483f4da8116aa12790859016fc499ff139e5c3bbeaf58936780bb1d5194 42c5c50301f7a09feccbc6733f8258d377112c8a6194a0f1a152a0c02f025742 a3fa72ced02134135a4b597e3e1b4e8b1509c63d7cc7875b3815ec861401c587 b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 0f083d0561f6293c712b219dd7876101bc35622366715967277b3fea9e796677 d7d55842bb7847ea099d3ccb9ad7d4f9ea5005a6b54c1cfba08223af81bea44e 2d900fe9142d858d025e58bce6777839c3ac56b08f91db1df01ffb56c00ce78b 61c54d7eab99ca2b145279c4cdc79cb7a7362f09b998271cf8b4aa851cff7e25 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 3b7d00c8bc4d8d9927ab67cd945bf6a06ab2b35e53f7213612db44bcb2211c52 57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 b4b177dddaa32cefe81e39074ec09e9863613a48589d20289ffe1e48c6475f59 eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 1d9857c194ac02b204ab40f6e410597b88c8e9fea398977d243832d3f27af8cc 79535bcef9252a3f988515bd33a963ff5db119b3b42c6c6b263f60fa6bf0fff7 a8c95988f4ee0d3a8347bc71347d8d3e0144663034ffd4ce3a61c96c7378c247 61507c8074e56e0bb9dc6e17b87dfc0f27cddec39f8ee5fea66da282af5dbd7f ff0159a9e24f5bed4486fc206b87e6861ace224b69cbb2a15a3660d6c1c78ab2 7311ff08867cfc0c21f65fde14e2d74cb1ec4a530004e7e1134fa638a6885aad 534e2bbec22b7fdc5bcf64d1aa9c4ba18511b3bc64af76c333954c663c166f53 373e1cca8482e2f91fc8c7f24871de37a20cd0d0157957668a17f518adda954a 7055d18035295d7e79f5fb7d48cec88241006f26cdd3a4a56e38634f03caed4b b98960cdf0dc1e4341ed411bf35ca9d2a5d5930a0f6fe9339b1777a475e3b671 6f7e951df4257cd339ee5d0dc753bc4b5c84d4ef377a2abb429e90bd2bc249c0 4a5802a54145b5c528f28060ebba76f99b09e38d5c85453b41bd39333f548dc0 ea1fbccbc92d8a893a08b8725eda9708fff27201809c4bb54c584bd4590053f2 e9aecb612643021ba59416b8acba38b18311f33507e9de4290ec73b17ac79f6d f77b3e9a82e7d8fee7e90a441946cde5bb34e9ca140dfd9867ae1969df132e8f 4137b404f831ad09946d9670aab06eaa79efb5a1f8e5d9bec788244a8b91b416 0ee02a3dd03826273764d6c8d99e084f0e43d0a9a8c1f4fef9e7be6ebf076235 d2c6532a650dfdb5add9ac35ac2812abc79901bb53efceeda811f66f6f36e6bc bdd70e3a96a094b10114cf2de544513221bdf4817b372d3673d9812bc3014706 602acca95d424aa7b3bc303ea6f48468f150bf3446237afadb664f9e5d43ea96 0ee02a3dd03826273764d6c8d99e084f0e43d0a9a8c1f4fef9e7be6ebf076235 07b4f1381e939ff4863946df80415382b5571d61e5802b4fa22f94b262837698 a38b2b1f0d8743d7e7842d2e828ea00c491b4a2fec94a4964e3fc04455716e30 a3427bc7cad83a0310619dc84d6ab03170424a24c96c957950ba8a86044d1fa4 efd56f96a8a9280e841cfc54773b53e88a7654855f1d14224f1367114cda01f8 fdd9520246f00efb95b1dde38571e56f2fe636bface44d5e99da73317a536031 10733fffd6cb51f75729733b160fbb6eb95ef10b6ad63f0dc83717df0d4f3645 8b45632c84dffa3b00c1b5165c71f88d83535570e0b64bed51885b7db937e189 bcb52f695698f6d87c4028588b60e87d7fd3e4ec033a9752d1300e3aa170826a e2aec810a7e0fd6c7bf95777699258c1be5032ca037c1ecc9c21ff0e58f9815e 1f147d73dc5b2a432c26a4b2b8bc4f59000556e343313232d7dd8f41c0c7c9f1 a82c5f118c138990383dc8385dd645809b9bb9f4fa13e51196a0572b8103b582 1a12f768609126da3937b4aaf53ea0d2f1c7945cff1f8371ae9ac8128eba82ab 87df8988524de69102e410b5e33bd5a2333762994963fb6ee2b731c8aec9f6f5 e51821108d9312a616571a24bac528dd67f7ec625a2fd28509f4690fd7512baa f20a932dbf6502ccef7ec8494aa226ade59c1f9a988c01c7a8dcfb5aae3ea159 93d9037e7529cd7e50573d7d36222ae2db2baab6173f95774c4f1fcd2644d565 dba3ea7749ce8afa934d7d3dd55eff8afe095f09e33e69836118d494eac0df9b 373ef4ea5952433e6ec8a1ea7c5bda6e4edd69d62d6f2dbee68d2c8b9a760713 000fd941376b243d4ce19839aa6e0798c610da99c47bc0cbbea3a81b967a4ace 141b164cab2d321f3f07a1e2ea68bc04bf9073aa5c2ad701c212e415eb2dc65c 77ae236316021cf25914361fbb16ff4d3f24951edebd6cf6aa652c26654d1d69 e462fcbef2160111b45e2de3aaf99c4c1daa2aa71c5e64ff307702e453d1a5b5 5584764a0fbdddde431909b5e84568c0ec4308a27bf6b93230984caff3cc05c4 e5ac97648e6fcb58cae4be7fcf39229a5700cb95086098faa5eadc4a8ba0a238 777bf0f5bcfc3bd8233eb93dd6ec29a03c9fd780c706234a9cb856a2306d29c2 e0379d82f6be001031c63966c98cd9e6a40fe4e4573b80b79949b4245aac65dd 98c6a60db8d5638e36acb79d36a0ecd759e238b051eba2f5cb2968d593ea8b84 36ffd44421550f5d49155500ac4c3ea949eba8926aa6c28a12708b73df4d2dbe 2096e4cd078307e9f6dfd5896f7983afc84810332c43a0e13f9c75ab4a5e1fff 2a2ca2716342acde21cdb2139ba341d8cd5d1ad19bf86577fe2478ac4c9f75a4 75190af954fbb55013a946d5c89c362a7f8893f0277ce97187eb5c95995e6f2f 25fb21923178b636cf351ca8cf1db0bb402a44b539179a0a8bf82fd86bb61f7f 90255ab5a0bf48636e14bfbf5ef457fb7bdb7f3b852b97f6296c0d0aa5d597c6 65c86b2d669db528d4899bf28b842557403bee2b4caadaa97856fd9fad51cddf 82e13ec666ef22eafac1e0fc9819256c9b2e54c6144e1195c40e9f273116825d 08eefdf3eb717d2da91eea6578dd13aeee6b91f60160a1476db39e59a23c9b89 cb43bd091f9c115755381f011d55758b9cfbb168252a9cf214082113d352d34f 90731b250381c7f5fd78a1d0ab090f84c2771490209a6aa15379a2a2cbfdbfd0 9c4759f03806f3e1fb335a842f42aa5b95f2f99662bb1b05996f81644b7a002c e7c1f9c8fcb2d700334ad4233ddbe938a722a9db34817fa30f17d392d1feeac6 5bf4290172017a28c9bad170d64e194281e166afb24cd9940ebc3f5d248b002c 60218558c22a6ae24f7be02c25fd926f2d24727173e5359cf083c762e369bb72 c9e6e99561727771205c1b2c2f59d20eb9ad8f390f1556c7621720731ab945e4 fc023deca888bf41e3625e570a7d6ab4ded1fa7ec40c88a221931f77a2632820 1951b49eb53765753bdedfed9802cff70701134ddf661f5d45487fa1bf6ed97f d1b3bf70935250b7551d15ff12dc799166e6cc2a7cf26f0e6c90fd01225035e1 a063a5852652ce4303c6ac4e0db71aa1fc28e06276f2945edbe0eca482892301 2253d4c27f53a5376da2f59beb5268d8557b9ed80e258fde0406263af17f4b90 ea5534cbedbf39fb4b0d025a2cc1b2eac486d8139616eef224946a1265765a53 7daafcb0a11658fb619fc091fb7a9ddc8896e2275bf4c7389bfc1763ec2092a8 227f854c373fd03ad8c9ba9eb6492c012e7b73c81da20c1a32b3d958f765627b 7c16dc84fb0976c8a4a1f0d4d0709d778fc03e0d7bdb18086b2945e5aac9fdd5 393cae7c30d75c6b2636bf7ed4ecda0f56c23ba0a3b662b3419b1e69aeb3c3e8 cb0640a3c09db87889bdcb3fa96dbb2906e685bf04488501b0b90f977b63f72b 83acd4899678786dbbf0873134f7ddde8594ccdf2779727fe3786c041976ac58 a9528ef5de417405b51299592fa4d6ca5ce82ca4eb2ca6cee53dcce7f1924d45 b3f685e297ed0f1ca40c56af73a404390d0a696e2795b8ae9f689d4a64bb6a05 34a1a7ddb9305549582f643a8d52dcd8749ebb80e8aba7f65d725252dc197afb dfb967a004d4406ca866b81d4e20ea0e0d4052f4be24f530f28316c0c92e0890 c2017f3c2c3ee624c3cac69a0f7b4c395403a03ac3c31ef8e12ada0db3cbc07b 00a3df13f1738e43765dd7bf0b267938cd5f4ae7731c71cd1dce22449ea4aacc 3ba991991f4268c4879000d0c507861544d33c2456ae8d228db2134a08226802 678c8b3a894fa60fb9d068e32701457f5c4f9c5bc9e5a2b84bca66c13a796cfe 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 84f26476b729bca519d3377ddfcac1d39ef452dee83c812cd4a8b606fab28f03 abc532eb35417b44d41bbfc290ca648b5f4c9f0441cb5ab24e7fe13765c58fd3 8049e401e74c66f3ad7d694e1cbb7c304509560d63645cab433dcad8955c74a9 647fe40400cf861e6bff4c5d2456aca94c118d6aad82c2f3e1a35339b7807661 9bbe09f5cf618f97a90e8ab1c481e320e9467f7133fd7d9e488aac1988f664d9 7725771151d04ff2a1220b0b894da0a75b879a2a2b14610c1c386d82f3f06778 543228495ffa272921c5c0b0f3954a59aa172f3a1da885aa5923eb41b98ba21d 0a87829d3c185186b86409f94105c1e934c18d0ab3c7075e97e6c517591d264e 6c1b9842ffb35a63d75a69a04108bf981556f5aec700210d11b16ecbe17e67f7 ac9a31d1154848e295e2e84981aca08af87c7ba314bff33e637d634085edfd4d 15313b49f2ba25f02d15f0923cc3b87751956b9722b001d809413ca328cdc872 7e630d99a4428479116d0adc203ad2154c4c8f70f5cd5e09188caea9d3fa5c9c b469368104f3366ee79762a2727cd0be07cb54949ecbd9823e2bc4b0a138772e 9337c0094e1ffc38ac06dc78dada7b64454b70b2d9e762233a67402cc74cda99 9337c0094e1ffc38ac06dc78dada7b64454b70b2d9e762233a67402cc74cda99 cae0929cfe6df92972e3c5af3a9391e2b233fb67e66d7a0678b71c5a13a5aeaf 22ec92647bf126cb5cb56ba6bd0a26cd7e2dba2e10885c5196127b251d299304 b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 83495ded3182c806af4bcabbf0f88d3492512ec6644b6e61a66b55ec275c8356 6a1b453172b5c6ddf5bb7aac5b779d54984fff7aabc3ecc7bc77374bd5a05ed9 3f4b79f4acd358ee7ac0d65dc9aa72fc10121aa44eca1557ab68d29d93192098 e493d84edd738a7747563514ed9cf887f52ef650e3b0927b2065faeeb5ba3963 bf7f60162b81afeba583a859778e09bb5e25e663f2d6289185452d587065ad44 8fef447823662ec28d860ded8a176662333c7bce7b465badbad21ebc017e0ef2 d57401bd04eb69dac0788c8b97dfdb221769449d9ed9da80c6bf9faeecd21a2e f79a895121a165a0ba07aca991675fcd4ef63b554fef3e7976893dea15896cc9 08c7b537f3751506b66ba75f70abb52081d13baa047666c0c4e8faa1091496cf b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 195a92c503f1a8d4465d051363df5746cfa02a188ce2e8683b7502832706781b 6b0439d2fbe124cfdef39cbc2badfd9f4e6d87321dba765bce860fb50c509d40 9e22d038b1a14e227518a3efd692dc9c2c01bca8e7490828086c69b03ef781bc 9bd63d1d658e7ba00c2400bf927ab5e21e65d0c1d84b2d713ffe124766edda3b 07e920470ed49261940474cdf52868ab6785f1365db988ac8d01a121bf2c0951 9cf7def3df501e4531cb12a6168e1bb17ec5388cc146a7bdb5eb789de98f45de 74c03afc643e23d4c252132174feba2048f16a24c9dabf1c76ec189209fda5b7 048852e5cb9b794a0cb60478628b4fa496f384f4586321bf5e7e9426f608f9d7 650a28cca27d6d9f1369cde754e5cb31a0d496dd8a6c280fe1394f5b1d14622b e88f29f8e36d3f625899719a181350c9735b479b9eeb3b29fb69ce250faf11d5 dec4444c09a6e648e055abbd792395ffcc02b7e2993f221748fbe8f820616bbf 90212f3a2db9b90dddf892044a2949809f8e43975f83fc51bcdf21a122be5757 85f3c58d9aa3a4acaeec282d978ff919d2136bc6497362b42a186d698d8e7bca 243fe6ffa1fe9fd0fd5e97a0581d0a0c132e0458a005068744693bc97b452086 8873d18aff0febad3ac97ae0121dc9b51579a2f8c5f1206f76e7dc785e721523 7a7375655345a76157528f8d46d757858aae8f98aa47c86df82e1602c7c57726 091db68698872b305269e71844eae258d1ee9d54b2e8dc79aa59bd1255a47716 ba9cd09a6f7619f20008af4724f86467b3ecd2204c50eeba99a58c0af7096a18 5a89a844a878d69806b9acacb559ebae3a409a61decd565df4d6aab1e9d05c00 6a8d0af8ecffd0f1315d4c304155902847a0552a02bc570628d46ddaf363cfac 4d1708171b7eedc79ab63aebe7fe3c0f080a395928f437dfdef33c6f3a6cab09 abdfcb713d625f078b18c3530d6293d326e1eef7c71563d2edf8f2e43c420883 e1415753a9779876ea386bdb0c97837dbc14dcd329b9654e4cb6636879c4bbd6 e0a6497c077728b3cd8092ffdbc5c072c7851bf9948b728e74b06aff69d9979d 4b2c195c00461f1289824a23b01b4082b5399a28cbf484eb8684cd4c5642dd63 8dc93de2d5be827c403938de24e1209617655375a335e9712411a1a78c8e5d6b 03fb73fa03f0997bdd710602bf2ff34ee2ae817e992eb757023e6148b59ddef2 3d3693535b1b8d39f1b22c93ce72b1778c081e329bd2746ad136f52459a72fab e2fc6f4a096740a4b37c8c619fdcfd3a1a836524e820bdc6ce7243651b5cfcca 34c18562e795db0135e75e258d591a680d241a979676168253d9172fa8c57891 4f1a07ee6130b972713252d03afa5425196e5b8838a8d9dace2aeb13f1af5bc1 3ab81c6f76af477197298cb3b948f82ee667bc3459e5ef2e1d483645798c939c 051fa38db3ab34e2109468a91bc2c9b48644cc3341e09ec3a42783745246efed 0400692620c9f76024f6c620dc1f08e7ef096c3b32813e0bba634bc632acde6d bb78dcfc33c508efb8a6c37e0851f13dbfa095ad924fb96974e08e4d5cbfafe2 209f925f914bfa21e188f2c67449082e266f5411949579cc3d33be4de9dc9c88 baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae cde09d9924582456f4022a27b6f2b43080ca722721e997510e62e5dab00a220b 6d2162d52ceb5a49b973342b2c5532b47fe26c7a39c3a77ca1a96542af4de4e7 5dc9c149c4ee909628cea1a5fca55a70d04fc72692029ea992bee87ec7a22313 0f54dcef969c12adc88d3987edb7f2310b9ccd95212465d1e07da98e99907990 335caedddaa0272ca4b1409a338a2016c28d51d88de0514bf98876b56da57b0a 856993134e200ade9c9991b501cb7d103088118a171c326b7c568bf38da7cfc0 faa0c53569ac72006a6697340cefecd1b4e107d53f6a218da29f6a54f1e866c6 1e25ddc3f35544fad7bb1daf717671a1253041e002cfe324c6794bb8c7b18d7e 9291105935716f9b96060bbb20ae8bb2578caaf405e1529a34bfb639218f1b5a 9438ec66d37819e100c16293d5979256ccb0265be5b742958ddc486c9376a7f4 ba4b5f286996a1c97acf010bd68cd0181fbc51e5f371800480c00c97bfcecff1 83d28f03580f29c776a9cd7dd10e1a1f73148571c7fe03dae39818578b9bf8a8 6729ef639d305b90f22a57d54ace399b8503c74a444acb83063b18a4a487cef4 edf93172d42d267daecebd739cd52a3b50ddda4e20d5b75ec5912b6944ee5a58 e5da659f8d12683b36a17ca32140f2c9249574265415264e8d696637c81c4c1d 2d6a681e2d3bc492d4bf5ed25f1375455ce760fc85fc58113a981d1aaa43e66a 146772e9dba5a5943b388b2aa623342e3f50f4263416dfb25002351864ae27e2 86b093c2a6451c574101f851b768051c5c43c9e4253444b7a427f94700a9d95b 371ecc9d767911382f9ee7300f47625e77f7002425b2d8f39cea847475e69202 6dea7742e96283114a4eb283ac243d35a0bd6385a355822b727e3ed44958ceb4 25e9820f0f37bb50f3be0ce639f2ab0fa929b2037be9cc538e2b75bc3b1fe18b 286176b7076d9ae726621ec164f3519bd88878494b65594af69662f1ef0271be c56f0c6e097ae6e06c209ff87c6fb4120360d420e88c7dd644eb0c4f4efeaf5a c5980e92efed926d845ce25f2938e119f0c49cb0f36f838f88ad1dd9d463c7e8 05cf61808c189c20669e597580449198f6c7521a82684267aede755068693423 d555878089f04bf2e738c5d6fc1f046e1e17ac7ec6fb66d04a3a9f8118682ca2 plyara-2.2.8/tests/data/imports/0000775000175000017500000000000014751212104014670 5ustar rharhaplyara-2.2.8/tests/data/imports/import_ruleset_pe.yar0000664000175000017500000000044414751212104021150 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "pe" rule pe_001 { condition: pe.number_of_sections == 1 } rule pe_002 { condition: pe.exports("CPlApplet") } rule pe_003 { condition: pe.characteristics & pe.DLL } plyara-2.2.8/tests/data/imports/import_ruleset_hash.yar0000664000175000017500000000044714751212104021472 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "hash" rule hash_001 { condition: hash.md5("dummy") == "275876e34cf609db118f3d84b799a790" } rule hash_002 { condition: hash.md5(0, filesize) == "feba6c919e3797e7778e8f2e85fa033d" } plyara-2.2.8/tests/data/imports/import_ruleset_androguard.yar0000664000175000017500000000050414751212104022667 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "androguard" rule androguard_001 { condition: androguard.package_name(/videogame/) } rule androguard_002 { condition: androguard.activity(/\.sms\./) or androguard.activity("com.package.name.sendSMS") } plyara-2.2.8/tests/data/imports/import_ruleset_magic.yar0000664000175000017500000000037114751212104021623 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "magic" rule magic_001 { condition: magic.type() contains "PDF" } rule magic_002 { condition: magic.mime_type() == "application/pdf" } plyara-2.2.8/tests/data/imports/import_ruleset_elf.yar0000664000175000017500000000035114751212104021307 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "elf" rule elf_001 { condition: elf.number_of_sections == 1 } rule elf_002 { condition: elf.machine == elf.EM_X86_64 } plyara-2.2.8/tests/data/imports/import_ruleset_dotnet.yar0000664000175000017500000000046714751212104022046 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "dotnet" rule dotnet_001 { condition: dotnet.number_of_streams != 5 } rule dotnet_002 { condition: for any i in (0..dotnet.number_of_streams - 1): (dotnet.streams[i].name == "#Blop") } plyara-2.2.8/tests/data/imports/import_ruleset_math.yar0000664000175000017500000000030114751212104021465 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "math" rule math_001 { condition: uint16(0) == 0x5A4D and math.entropy(0, filesize) > 7.0 } plyara-2.2.8/tests/data/imports/__init__.py0000775000175000017500000000000014751212104016772 0ustar rharhaplyara-2.2.8/tests/data/imports/import_ruleset_cuckoo.yar0000664000175000017500000000031514751212104022024 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates import "cuckoo" rule cuckoo_001 { condition: cuckoo.network.http_request(/http:\/\/someone\.doingevil\.com/) } plyara-2.2.8/tests/data/common/0000775000175000017500000000000014751212104014463 5ustar rharhaplyara-2.2.8/tests/data/common/__init__.py0000775000175000017500000000000014751212104016565 0ustar rharhaplyara-2.2.8/tests/data/common/test_rules_from_yara_project.yar0000664000175000017500000005416414751212104023170 0ustar rharha/* Copyright (c) 2016. The YARA Authors. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // This file of YARA rules is derived from // https://raw.githubusercontent.com/VirusTotal/yara/master/tests/test-rules.c // License left intact // Rules that test for errors in rule syntax are removed // Boolean operators rule test { condition: true } rule test { condition: true or false } rule test { condition: true and true } rule test { condition: 0x1 and 0x2} rule test { condition: false } rule test { condition: true and false } rule test { condition: false or false } // Comparison operators rule test { condition: 2 > 1 } rule test { condition: 1 < 2 } rule test { condition: 2 >= 1 } rule test { condition: 1 <= 1 } rule test { condition: 1 == 1 } rule test { condition: 1.5 == 1.5} rule test { condition: 1.0 == 1} rule test { condition: 1.5 >= 1.0} rule test { condition: 1.0 != 1.000000000000001 } rule test { condition: 1.0 < 1.000000000000001 } rule test { condition: 1.0 >= 1.000000000000001 } rule test { condition: 1.000000000000001 > 1 } rule test { condition: 1.000000000000001 <= 1 } rule test { condition: 1.0 == 1.0000000000000001 } rule test { condition: 1.0 >= 1.0000000000000001 } rule test { condition: 1.5 >= 1} rule test { condition: 1.0 >= 1} rule test { condition: 0.5 < 1} rule test { condition: 0.5 <= 1} rule test { condition: 1.0 <= 1} rule test { condition: "abc" == "abc"} rule test { condition: "abc" <= "abc"} rule test { condition: "abc" >= "abc"} rule test { condition: "ab" < "abc"} rule test { condition: "abc" > "ab"} rule test { condition: "abc" < "abd"} rule test { condition: "abd" > "abc"} rule test { condition: 1 != 1} rule test { condition: 1 != 1.0} rule test { condition: 2 > 3} rule test { condition: 2.1 < 2} rule test { condition: "abc" != "abc"} rule test { condition: "abc" > "abc"} rule test { condition: "abc" < "abc"} // Arithmetic operators rule test { condition: (1 + 1) * 2 == (9 - 1) \\ 2 } rule test { condition: 5 % 2 == 1 } rule test { condition: 1.5 + 1.5 == 3} rule test { condition: 3 \\ 2 == 1} rule test { condition: 3.0 \\ 2 == 1.5} rule test { condition: 1 + -1 == 0} rule test { condition: -1 + -1 == -2} rule test { condition: 4 --2 * 2 == 8} rule test { condition: -1.0 * 1 == -1.0} rule test { condition: 1-1 == 0} rule test { condition: -2.0-3.0 == -5} rule test { condition: --1 == 1} rule test { condition: 1--1 == 2} rule test { condition: 2 * -2 == -4} rule test { condition: -4 * 2 == -8} rule test { condition: -4 * -4 == 16} rule test { condition: -0x01 == -1} rule test { condition: 0o10 == 8 } rule test { condition: 0o100 == 64 } rule test { condition: 0o755 == 493 } // Bitwise operators rule test { condition: 0x55 | 0xAA == 0xFF } rule test { condition: ~0xAA ^ 0x5A & 0xFF == (~0xAA) ^ (0x5A & 0xFF) } rule test { condition: ~0x55 & 0xFF == 0xAA } rule test { condition: 8 >> 2 == 2 } rule test { condition: 1 << 3 == 8 } rule test { condition: 1 << 64 == 0 } rule test { condition: 1 >> 64 == 0 } rule test { condition: 1 | 3 ^ 3 == 1 | (3 ^ 3) } rule test { condition: ~0xAA ^ 0x5A & 0xFF == 0x0F } rule test { condition: 1 | 3 ^ 3 == (1 | 3) ^ 3} // Anonymous strings rule test { strings: $ = "a" $ = "b" condition: all of them } // Strings rule test { strings: $a = "a" condition: $a } rule test { strings: $a = "ab" condition: $a } rule test { strings: $a = "abc" condition: $a } rule test { strings: $a = "xyz" condition: $a } rule test { strings: $a = "abc" nocase fullword condition: $a } rule test { strings: $a = "aBc" nocase condition: $a } rule test { strings: $a = "abc" fullword condition: $a } rule test { strings: $a = "a" fullword condition: $a } rule test { strings: $a = "ab" fullword condition: $a } rule test { strings: $a = "abc" wide fullword condition: $a } rule test { strings: $a = "a" wide condition: $a } rule test { strings: $a = "a" wide ascii condition: $a } rule test { strings: $a = "ab" wide condition: $a } rule test { strings: $a = "ab" wide ascii condition: $a } rule test { strings: $a = "abc" wide condition: $a } rule test { strings: $a = "abc" wide nocase fullword condition: $a } rule test { strings: $a = "aBc" wide nocase condition: $a } rule test { strings: $a = "aBc" wide ascii nocase condition: $a } rule test { strings: $a = "---xyz" wide nocase condition: $a } rule test { strings: $a = "abc" fullword condition: $a } rule test { strings: $a = "abc" fullword condition: $a } rule test { strings: $a = "abc" fullword condition: $a } rule test { strings: $a = "abc" fullword condition: $a } rule test { strings: $a = "abc" wide condition: $a } rule test { strings: $a = "abcdef" wide condition: $a } rule test { strings: $a = "abc" ascii wide fullword condition: $a } rule test { strings: $a = "abc" ascii wide fullword condition: $a } rule test { strings: $a = "abc" wide fullword condition: $a } rule test { strings: $a = "abc" wide fullword condition: $a } rule test { strings: $a = "ab" wide fullword condition: $a } rule test { strings: $a = "abc" wide fullword condition: $a } rule test { strings: $a = "abc" wide fullword condition: $a } rule test { strings: $a = "abcdef" $b = "cdef" $c = "ef" condition: all of them } rule test { strings: $a = "This program cannot" xor condition: #a == 255 } rule test { strings: $a = "This program cannot" xor ascii condition: #a == 256 } rule test { strings: $a = "This program cannot" xor wide condition: #a == 256 } rule test { strings: $a = "ab" xor fullword condition: #a == 1084 } // Wildcard strings rule test { strings: $s1 = "abc" $s2 = "xyz" condition: for all of ($*) : ($) } // Hex strings rule test { strings: $a = { 64 01 00 00 60 01 } condition: $a } rule test { strings: $a = { 64 0? 00 00 ?0 01 } condition: $a } rule test { strings: $a = { 6? 01 00 00 60 0? } condition: $a } rule test { strings: $a = { 64 01 [1-3] 60 01 } condition: $a } rule test { strings: $a = { 64 01 [1-3] (60|61) 01 } condition: $a } rule test { strings: $a = { 4D 5A [-] 6A 2A [-] 58 C3} condition: $a } rule test { strings: $a = { 4D 5A [300-] 6A 2A [-] 58 C3} condition: $a } rule test { strings: $a = { 2e 7? (65 | ?? ) 78 } condition: $a } rule test { strings: $a = { 4D 5A [0-300] 6A 2A } condition: $a } rule test { strings: $a = { 4D 5A [0-128] 45 [0-128] 01 [0-128] C3 } condition: $a } rule test { strings: $a = { 31 32 [-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [-] // Inline comment 38 39 } condition: $a } rule test { strings: $a = { 31 32 /* Inline comment */ [-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 /* Inline multi-line comment */ [-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [-] 33 34 [-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [1] 34 35 [2] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [1-] 34 35 [1-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [0-3] 34 35 [1-] 38 39 } condition: $a } rule test { strings: $a = { 31 32 [0-2] 35 [1-] 37 38 39 } condition: $a } rule test { strings: $a = { 31 32 [0-1] 33 } condition: !a == 3} rule test { strings: $a = { 31 32 [0-1] 34 } condition: !a == 4} rule test { strings: $a = { 31 32 [0-2] 34 } condition: !a == 4 } rule test { strings: $a = { 31 32 [-] 38 39 } condition: all of them } rule test { strings: $a = { 31 32 [-] 32 33 } condition: $a } rule test { strings: $a = { 35 36 [-] 31 32 } condition: $a } rule test { strings: $a = { 31 32 [2-] 34 35 } condition: $a } rule test { strings: $a = { 31 32 [0-1] 33 34 [0-2] 36 37 } condition: $a } rule test { strings: $a = { 31 32 [0-1] 34 35 [0-2] 36 37 } condition: $a } rule test { strings: $a = { 31 32 [0-3] 37 38 } condition: $a } rule test { strings: $a = { 31 32 [1] 33 34 } condition: $a } rule test { strings: $a = {31 32 [3-6] 32} condition: !a == 6 } rule test { strings: $a = {31 [0-3] (32|33)} condition: !a == 2 } // Test count rule test { strings: $a = "ssi" condition: #a == 2 } // Test at rule test { strings: $a = "ssi" condition: $a at 2 and $a at 5 } rule test { strings: $a = "mis" condition: $a at ~0xFF & 0xFF } rule test { strings: $a = { 00 00 00 00 ?? 74 65 78 74 } condition: $a at 308} // Test in rule test { strings: $a = { 6a 2a 58 c3 } condition: $a in (entrypoint .. entrypoint + 1) } // Test offset rule test { strings: $a = "ssi" condition: @a == 2 } rule test { strings: $a = "ssi" condition: @a == @a[1] } rule test { strings: $a = "ssi" condition: @a[2] == 5 } // Test length rule test { strings: $a = /m.*?ssi/ condition: !a == 5 } rule test { strings: $a = /m.*?ssi/ condition: !a[1] == 5 } rule test { strings: $a = /m.*ssi/ condition: !a == 8 } rule test { strings: $a = /m.*ssi/ condition: !a[1] == 8 } rule test { strings: $a = /ssi.*ppi/ condition: !a[1] == 9 } rule test { strings: $a = /ssi.*ppi/ condition: !a[2] == 6 } rule test { strings: $a = { 6D [1-3] 73 73 69 } condition: !a == 5} rule test { strings: $a = { 6D [-] 73 73 69 } condition: !a == 5} rule test { strings: $a = { 6D [-] 70 70 69 } condition: !a == 11} rule test { strings: $a = { 6D 69 73 73 [-] 70 69 } condition: !a == 11} // Test of rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: any of them } rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: 1 of them } rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: 2 of them } rule test { strings: $a1 = "dummy1" $b1 = "dummy1" $b2 = "ssi" condition: any of ($a*, $b*) } rule test { strings: $ = /abc/ $ = /def/ $ = /ghi/ condition: for any of ($*) : ( for any i in (1..#): (uint8(@[i] - 1) == 0x00) ) } rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: all of them } // Test for rule test { strings: $a = "ssi" condition: for all i in (1..#a) : (@a[i] >= 2 and @a[i] <= 5) } rule test { strings: $a = "ssi" $b = "mi" condition: for all i in (1..#a) : ( for all j in (1..#b) : (@a[i] >= @b[j])) } rule test { strings: $a = "ssi" condition: for all i in (1..#a) : (@a[i] == 5) } // Test re rule test { strings: $a = /ssi/ condition: $a } rule test { strings: $a = /ssi(s|p)/ condition: $a } rule test { strings: $a = /ssim*/ condition: $a } rule test { strings: $a = /ssa?/ condition: $a } rule test { strings: $a = /Miss/ nocase condition: $a } rule test { strings: $a = /(M|N)iss/ nocase condition: $a } rule test { strings: $a = /[M-N]iss/ nocase condition: $a } rule test { strings: $a = /(Mi|ssi)ssippi/ nocase condition: $a } rule test { strings: $a = /ppi\\tmi/ condition: $a } rule test { strings: $a = /ppi\\.mi/ condition: $a } rule test { strings: $a = /^mississippi/ fullword condition: $a } rule test { strings: $a = /mississippi.*mississippi$/s condition: $a } rule test { strings: $a = /^ssi/ condition: $a } rule test { strings: $a = /ssi$/ condition: $a } rule test { strings: $a = /ssissi/ fullword condition: $a } rule test { strings: $a = /^[isp]+/ condition: $a } rule test { strings: $a = /a.{1,2}b/ wide condition: !a == 6 } rule test { strings: $a = /a.{1,2}b/ wide condition: !a == 8 } rule test { strings: $a = /\\babc/ wide condition: $a } rule test { strings: $a = /\\babc/ wide condition: $a } rule test { strings: $a = /\\babc/ wide condition: $a } rule test { strings: $a = /\\babc/ wide condition: $a } rule test { strings: $a = /\\babc/ wide condition: $a } rule test { strings: $a = /abc\\b/ wide condition: $a } rule test { strings: $a = /abc\\b/ wide condition: $a } rule test { strings: $a = /abc\\b/ wide condition: $a } rule test { strings: $a = /abc\\b/ wide condition: $a } rule test { strings: $a = /abc\\b/ wide condition: $a } rule test { strings: $a = /\\b/ wide condition: $a } rule test { strings: $a = /MZ.{300,}t/ condition: !a == 317 } rule test { strings: $a = /MZ.{300,}?t/ condition: !a == 314 } rule test { strings: $a = /abc[^d]/ nocase condition: $a } rule test { strings: $a = /abc[^d]/ condition: $a } rule test { strings: $a = /abc[^D]/ nocase condition: $a } rule test { strings: $a = /abc[^D]/ condition: $a } rule test { strings: $a = /abc[^f]/ nocase condition: $a } rule test { strings: $a = /abc[^f]/ condition: $a } rule test { strings: $a = /abc[^F]/ nocase condition: $a } rule test { strings: $a = /abc[^F]/ condition: $a } rule test { strings: $a = " cmd.exe " nocase wide condition: $a } // Test entry point rule test { strings: $a = { 6a 2a 58 c3 } condition: $a at entrypoint } rule test { strings: $a = { b8 01 00 00 00 bb 2a } condition: $a at entrypoint } rule test { strings: $a = { b8 01 00 00 00 bb 2a } condition: $a at entrypoint } rule test { condition: entrypoint >= 0 } // Test file size rule test { condition: filesize == %zd } // Test comments rule test { condition: // this is a comment /*** this is a comment ***/ /* /* /* this is a comment */ true } // Test matches operator rule test { condition: "foo" matches /foo/ } rule test { condition: "foo" matches /bar/ } rule test { condition: "FoO" matches /fOo/i } rule test { condition: "xxFoOxx" matches /fOo/i } rule test { condition: "xxFoOxx" matches /^fOo/i } rule test { condition: "xxFoOxx" matches /fOo$/i } rule test { condition: "foo" matches /^foo$/i } rule test { condition: "foo\\nbar" matches /foo.*bar/s } rule test { condition: "foo\\nbar" matches /foo.*bar/ } // Test global rules global private rule global_rule { condition: true } rule test { condition: true } global private rule global_rule { condition: false } rule test { condition: true } // Test modules import "tests" rule test { condition: tests.constants.one + 1 == tests.constants.two } import "tests" rule test { condition: tests.constants.foo == "foo" } import "tests" rule test { condition: tests.constants.empty == "" } import "tests" rule test { condition: tests.empty() == "" } import "tests" rule test { condition: tests.struct_array[1].i == 1 } import "tests" rule test { condition: tests.struct_array[0].i == 1 or true } import "tests" rule test { condition: tests.integer_array[0] == 0 } import "tests" rule test { condition: tests.integer_array[1] == 1 } import "tests" rule test { condition: tests.integer_array[256] == 256 } import "tests" rule test { condition: tests.string_array[0] == "foo" } import "tests" rule test { condition: tests.string_array[2] == "baz" } import "tests" rule test { condition: tests.string_dict["foo"] == "foo" } import "tests" rule test { condition: tests.string_dict["bar"] == "bar" } import "tests" rule test { condition: tests.isum(1,2) == 3 } import "tests" rule test { condition: tests.isum(1,2,3) == 6 } import "tests" rule test { condition: tests.fsum(1.0,2.0) == 3.0 } import "tests" rule test { condition: tests.fsum(1.0,2.0,3.0) == 6.0 } import "tests" rule test { condition: tests.foobar(1) == tests.foobar(1) } import "tests" rule test { condition: tests.foobar(1) != tests.foobar(2) } import "tests" rule test { condition: tests.length("dummy") == 5 } import "tests" rule test { condition: tests.struct_array[0].i == 1 } import "tests" rule test { condition: tests.isum(1,1) == 3 } import "tests" rule test { condition: tests.fsum(1.0,1.0) == 3.0 } import "tests" rule test { condition: tests.match(/foo/,"foo") == 3 } import "tests" rule test { condition: tests.match(/foo/,"bar") == -1 } import "tests" rule test { condition: tests.match(/foo.bar/i,"FOO\\nBAR") == -1 } import "tests" rule test { condition: tests.match(/foo.bar/is,"FOO\\nBAR") == 7 } // Test time module import "time" rule test { condition: time.now() > 0 } // Test hash module import "hash" rule test { condition: hash.md5(0, filesize) == "ab56b4d92b40713acc5af89985d4b786" and hash.md5(1, filesize) == "e02cfbe5502b64aa5ae9f2d0d69eaa8d" and hash.sha1(0, filesize) == "03de6c570bfe24bfc328ccd7ca46b76eadaf4334" and hash.sha1(1, filesize) == "a302d65ae4d9e768a1538d53605f203fd8e2d6e2" and hash.sha256(0, filesize) == "36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c" and hash.sha256(1, filesize) == "aaaaf2863e043b9df604158ad5c16ff1adaf3fd7e9fcea5dcb322b6762b3b59a" } import "hash" rule test { condition: hash.md5(0, filesize) == "ab56b4d92b40713acc5af89985d4b786" and hash.md5(1, filesize) == "e02cfbe5502b64aa5ae9f2d0d69eaa8d" and hash.md5(0, filesize) == "ab56b4d92b40713acc5af89985d4b786" and hash.md5(1, filesize) == "e02cfbe5502b64aa5ae9f2d0d69eaa8d" } // Test integer functions rule test { condition: uint8(0) == 0xAA} rule test { condition: uint16(0) == 0xBBAA} rule test { condition: uint32(0) == 0xDDCCBBAA} rule test { condition: uint8be(0) == 0xAA} rule test { condition: uint16be(0) == 0xAABB} rule test { condition: uint32be(0) == 0xAABBCCDD} // Test include files include "tests/data/baz.yar" rule t { condition: baz } include "tests/data/foo.yar" rule t { condition: foo } // Test process scan rule test { strings: $a = { 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 } condition: all of them } // Test performance warnings rule test { strings: $a = { 01 } condition: $a } rule test { strings: $a = { 01 ?? } condition: $a } rule test { strings: $a = { 01 ?? ?? } condition: $a } rule test { strings: $a = { 01 ?? ?? 02 } condition: $a } rule test { strings: $a = { 01 ?? ?2 03 } condition: $a } rule test { strings: $a = { 01 ?? 02 1? } condition: $a } rule test { strings: $a = { 1? 2? 3? } condition: $a } rule test { strings: $a = { 1? 2? 3? 04 } condition: $a } rule test { strings: $a = { 1? ?? 03 } condition: $a } rule test { strings: $a = { 00 01 } condition: $a } rule test { strings: $a = { 01 00 } condition: $a } rule test { strings: $a = { 00 00 } condition: $a } rule test { strings: $a = { 00 00 00 } condition: $a } rule test { strings: $a = { 00 00 01 } condition: $a } rule test { strings: $a = { 00 00 00 00 } condition: $a } rule test { strings: $a = { 00 00 00 01 } condition: $a } rule test { strings: $a = { FF FF FF FF } condition: $a } rule test { strings: $a = { 00 00 01 02 } condition: $a } rule test { strings: $a = { 00 01 02 03 } condition: $a } rule test { strings: $a = { 01 02 03 04 } condition: $a } rule test { strings: $a = { 01 02 03 } condition: $a } rule test { strings: $a = { 20 01 02 } condition: $a } rule test { strings: $a = { 01 02 } condition: $a } rule test { strings: $a = "foo" wide condition: $a } rule test { strings: $a = "MZ" condition: $a } plyara-2.2.8/tests/data/core/0000775000175000017500000000000014751212104014123 5ustar rharhaplyara-2.2.8/tests/data/core/comments_ruleset_crlf.yar0000664000175000017500000000351114751212104021236 0ustar rharhaglobal rule comments1 : asdf // nameline { // openbrace meta: // metasection author = "Malware Utkonos" // metakv date = "2025-01-05" description = "TESTRULE" strings: // stringssection $op = { ABABABABABAB // bytestringinternal1 CDCDCDCDCDCD // bytestringinternal2 ~EF } // bytestring condition: // conditionsection uint16(0) == 0x5a4d and // conditioninternal1 pe.is_pe and // conditioninternal2 uint32(uint32(0x3c)) == 0x00004550 and // conditioninternal3 $op // condition } // closebrace global rule comments2 /* nameline secondline */ { /* openbrace secondline */ meta: /* metasection secondline */ author = "Malware Utkonos" /* metakv secondline */ date = "2025-01-05" description = "TESTRULE" strings: /* stringssection secondline */ $op = { ABABABABABAB /* bytestringinternal1 secondline */ CDCDCDCDCDCD ~EF } /* bytestring secondline */ condition: /* conditionsection secondline */ uint16(0) == 0x5a4d and /* conditioninternal1 secondline */ pe.is_pe and uint32(uint32(0x3c)) == 0x00004550 and $op /* condition secondline */ } /* closebrace secondline */ plyara-2.2.8/tests/data/core/windows_newline_ruleset_with_error.yar0000664000175000017500000000010314751212104024054 0ustar rharharule sample { strings: $ = { 00 00 } conditio: all of them } plyara-2.2.8/tests/data/core/condition_ruleset.yar0000664000175000017500000000061014751212104020366 0ustar rharharule image_filetype { condition: ( uint32be(0x00) == 0x89504E47 or // PNG uint16be(0x00) == 0xFFD8 or // JPEG uint32be(0x00) == 0x47494638 // GIF ) and ( $eval or 1 of ($key*) ) and ( @a[1] or !a[1] ) and not filename matches /[0-9a-zA-Z]{30,}/ } plyara-2.2.8/tests/data/core/raw_sections_condition.txt0000664000175000017500000000057114751212104021435 0ustar rharha636f6e646974696f6e3a0a202020202020202075696e743136283029203d3d2030783561346420616e642020202020202020202020202020202020202020202f2f20466f6f0a202020202020202075696e7433322875696e74333228307833632929203d3d203078303030303435353020616e642020202020202f2f204261720a2020202020202020246f7020202020202020202020202020202020202020202020202020202020202020202020202020202020202f2f2042617a0a plyara-2.2.8/tests/data/core/xor_modifier_ruleset.yar0000664000175000017500000000145614751212104021077 0ustar rharharule xor_unmodified { strings: $a = "one" xor wide condition: all of them } // The following work with YARA >= 3.11.0 rule xor_mod_num_single { strings: $a = "one" xor(16) condition: all of them } rule xor_mod_num_range { strings: $a = "one" xor( 16 - 128 ) condition: all of them } rule xor_mod_hexnum_single { strings: $a = "one" xor(0x10) condition: all of them } rule xor_mod_hexnum_range { strings: $a = "one" xor( 0x10 - 0x80 ) condition: all of them } rule xor_mod_mixed_range1 { strings: $a = "one" xor( 16 - 0x80 ) condition: all of them } rule xor_mod_mixed_range2 { strings: $a = "one" xor( 0x10 - 64 ) condition: all of them } plyara-2.2.8/tests/data/core/metadata_ruleset.yar0000664000175000017500000000126114751212104020163 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates rule StringTypeMetadata { meta: string_value = "String Metadata" string_value2 = "String Metadata with \"ending quotes\"" condition: false } rule IntegerTypeMetadata { meta: integer_value = 100 condition: false } rule BooleanTypeMetadata { meta: boolean_value = true condition: false } rule AllTypesMetadata { meta: string_value = "Different String Metadata" integer_value = 33 boolean_value = false condition: false } rule NegativeNumberMetadata { meta: negative = -5 condition: false } plyara-2.2.8/tests/data/core/include_ruleset.yar0000664000175000017500000000016714751212104020032 0ustar rharhainclude "string_ruleset.yar" rule includerule { strings: $a = "1aosidjoasidjaosidj" condition: all of them } plyara-2.2.8/tests/data/core/test_ruleset_1_rule.yar0000664000175000017500000000024614751212104020633 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates rule rule_one { strings: $a = "one" condition: all of them } plyara-2.2.8/tests/data/core/raw_sections.yar0000664000175000017500000000064614751212104017346 0ustar rharharule name { meta: author = "Malware Utkonos" // Name date = "2025-01-05" description = "TESTRULE" strings: $op = { ABABABABABAB // Line 1 CDCDCDCDCDCD } // Line 2 condition: uint16(0) == 0x5a4d and // Foo uint32(uint32(0x3c)) == 0x00004550 and // Bar $op // Baz } plyara-2.2.8/tests/data/core/metakv_test.yar0000664000175000017500000000102314751212104017162 0ustar rharhaimport "pe" rule meta_test { meta: author = "Malware Utkonos" date = "2020-01-04" tlp = "Green" strings: $op = { 55 8B EC 81 [2] 00 00 00 89 [5] 89 } condition: pe.exports("initTest") and all of them } rule meta_test2 { meta: author = "Malware Utkonos" date = "2020-01-04" tlp = "Green" author = "Someone else" strings: $op = { 55 8B EC 81 [2] 00 00 00 89 [5] 89 } condition: pe.exports("initTest") and all of them } plyara-2.2.8/tests/data/core/only_condition.yar0000664000175000017500000000004014751212104017661 0ustar rharharule testName {condition: true} plyara-2.2.8/tests/data/core/string_ruleset.yar0000664000175000017500000000434214751212104017714 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates rule Text { strings: $text_string = "foobar" condition: $text_string } rule FullwordText { strings: $text_string = "foobar" fullword condition: $text_string } rule CaseInsensitiveText { strings: $text_string = "foobar" nocase condition: $text_string } rule WideCharText { strings: $wide_string = "Borland" wide condition: $wide_string } rule WideCharAsciiText { strings: $wide_and_ascii_string = "Borland" wide ascii condition: $wide_and_ascii_string } rule HexWildcard { strings: $hex_string = { E2 34 ?? C8 A? FB } condition: $hex_string } rule HexJump { strings: $hex_string = { F4 23 [4-6] 62 B4 } condition: $hex_string } rule HexAlternatives { strings: $hex_string = { F4 23 ( 62 B4 | 56 ) 45 } condition: $hex_string } rule HexMultipleAlternatives { strings: $hex_string = { F4 23 ( 62 B4 | 56 | 45 ?? 67 ) 45 } condition: $hex_string } rule RegExp { strings: $re1 = /md5: [0-9a-fA-F]{32}/nocase // no case for hash $re2 = /state: (on|off)/i//no case for state $re3 = /\x00https?:\/\/[^\x00]{4,500}\x00\x00\x00/ condition: $re1 and $re2 and $re3 } rule Xor { strings: $xor_string = "This program cannot" xor condition: $xor_string } rule WideXorAscii { strings: $xor_string = "This program cannot" xor wide ascii condition: $xor_string } rule WideXor { strings: $xor_string = "This program cannot" xor wide condition: $xor_string } rule DoubleBackslash { strings: $bs = "\"\\\\\\\"" condition: $bs } rule DoubleQuote { strings: $text_string = "foobar\"" condition: $text_string } rule HorizontalTab { strings: $text_string = "foo\tbar" condition: $text_string } rule Newline { strings: $text_string = "foo\nbar" condition: $text_string } rule HexEscape { strings: $text_string = "foo\x00bar" condition: $text_string } plyara-2.2.8/tests/data/core/xor_modifier_whitespace.yar0000664000175000017500000000203614751212104021543 0ustar rharharule XorExample1 { strings: $xor_string = "This program cannot" xor(0x01-0xff) condition: $xor_string } rule XorExample2 { strings: $xor_string = "This program cannot" xor (0x01-0xff) condition: $xor_string } rule XorExample3 { strings: $xor_string = "This program cannot" xor( 0x01-0xff) condition: $xor_string } rule XorExample4 { strings: $xor_string = "This program cannot" xor(0x01 -0xff) condition: $xor_string } rule XorExample5 { strings: $xor_string = "This program cannot" xor(0x01- 0xff) condition: $xor_string } rule XorExample6 { strings: $xor_string = "This program cannot" xor(0x01-0xff ) condition: $xor_string } rule XorExample7 { strings: $xor_string = "This program cannot" xor ( 0x01 - 0xff ) condition: $xor_string } rule XorExample8 { strings: $xor_string = "This program cannot" xor ( 0x01 - 0xff ) condition: $xor_string } plyara-2.2.8/tests/data/core/windows_newline_ruleset.yar0000664000175000017500000000010414751212104021611 0ustar rharharule sample { strings: $ = { 00 00 } condition: all of them } plyara-2.2.8/tests/data/core/bad_regex_string_char.yar0000664000175000017500000000014414751212104021142 0ustar rharharule badrexchar { strings: $a = /foobar🔥bazfoo/ condition: all of them } plyara-2.2.8/tests/data/core/bad_condition_string.yar0000664000175000017500000000010314751212104021014 0ustar rharharule sample { strings: $ = { 00 00 } conditio: all of them } plyara-2.2.8/tests/data/core/scope_ruleset.yar0000664000175000017500000000036714751212104017522 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates global rule GlobalScope { condition: false } private rule PrivateScope { condition: false } global private rule PrivateGlobalScope { condition: false } plyara-2.2.8/tests/data/core/comments_ruleset.yar0000664000175000017500000000343514751212104020235 0ustar rharhaglobal rule comments1 : asdf // nameline { // openbrace meta: // metasection author = "Malware Utkonos" // metakv date = "2025-01-05" description = "TESTRULE" strings: // stringssection $op = { ABABABABABAB // bytestringinternal1 CDCDCDCDCDCD // bytestringinternal2 ~EF } // bytestring condition: // conditionsection uint16(0) == 0x5a4d and // conditioninternal1 pe.is_pe and // conditioninternal2 uint32(uint32(0x3c)) == 0x00004550 and // conditioninternal3 $op // condition } // closebrace global rule comments2 /* nameline secondline */ { /* openbrace secondline */ meta: /* metasection secondline */ author = "Malware Utkonos" /* metakv secondline */ date = "2025-01-05" description = "TESTRULE" strings: /* stringssection secondline */ $op = { ABABABABABAB /* bytestringinternal1 secondline */ CDCDCDCDCDCD ~EF } /* bytestring secondline */ condition: /* conditionsection secondline */ uint16(0) == 0x5a4d and /* conditioninternal1 secondline */ pe.is_pe and uint32(uint32(0x3c)) == 0x00004550 and $op /* condition secondline */ } /* closebrace secondline */ plyara-2.2.8/tests/data/core/raw_sections_meta.txt0000664000175000017500000000034714751212104020376 0ustar rharha6d6574613a0a2020202020202020617574686f72203d20224d616c776172652055746b6f6e6f732220202f2f204e616d650a202020202020202064617465203d2022323032352d30312d3035220a20202020202020206465736372697074696f6e203d20225445535452554c45220a20202020 plyara-2.2.8/tests/data/core/__init__.py0000775000175000017500000000000014751212104016225 0ustar rharhaplyara-2.2.8/tests/data/core/base64_modifier_ruleset.yar0000664000175000017500000000114014751212104021341 0ustar rharha// The following work with YARA >= 3.12.0 rule base64_unmodified { strings: $a = "one" base64 condition: all of them } rule base64wide_unmodified { strings: $a = "one" base64wide condition: all of them } rule base64_mod_custom { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") condition: all of them } rule base64wide_mod_custom { strings: $a = "one" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") condition: all of them } plyara-2.2.8/tests/data/core/raw_sections_strings.txt0000664000175000017500000000031314751212104021132 0ustar rharha737472696e67733a0a2020202020202020246f70203d207b204142414241424142414241422020202020202f2f204c696e6520310a20202020202020202020202020202020434443444344434443444344207d202020202f2f204c696e6520320a20202020 plyara-2.2.8/tests/data/core/illegal_char.yar0000664000175000017500000000030514751212104017244 0ustar rharharule rule1 { strings: $op = { ABABABABABABABABABABAB } condition: $op } 💩 rule rule2 { strings: $op = { ABABABABABABABABABABAB } condition: $op } plyara-2.2.8/tests/data/core/test_ruleset_2_rules.yar0000664000175000017500000000040014751212104021007 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates rule rule_two { strings: $a = "two" condition: all of them } rule rule_three { strings: $a = "three" condition: all of them } plyara-2.2.8/tests/data/core/tag_ruleset.yar0000664000175000017500000000035114751212104017155 0ustar rharha// This ruleset is used for unit tests - Modification will require test updates rule OneTag: tag1 { condition: false } rule TwoTags : tag1 tag2 { condition: false } rule ThreeTags : tag1 tag2 tag3 { condition: false } plyara-2.2.8/tests/data/core/windows_newline_ruleset_comment.yar0000664000175000017500000000013514751212104023337 0ustar rharha/* Multiline comment */ rule sample { strings: $ = { 00 00 } condition: all of them } plyara-2.2.8/tests/data/__init__.py0000775000175000017500000000000014751212104015275 0ustar rharhaplyara-2.2.8/tests/__init__.py0000775000175000017500000000000014751212104014364 0ustar rharhaplyara-2.2.8/tests/test_core.py0000775000175000017500000016100514751212104014631 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit test plyara core module.""" import binascii import concurrent.futures import contextlib import importlib.resources import unittest import plyara.core from plyara.core import Plyara from plyara.exceptions import ParseTypeError, ParseValueError class TestImports(unittest.TestCase): """Check parsing of import statements.""" def setUp(self): self.parser = plyara.core.Plyara() self.data = importlib.resources.files('tests.data.imports') def test_import_pe(self): """Check parsing of pe module import.""" input_string = self.data.joinpath('import_ruleset_pe.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('pe', rule['imports']) def test_import_elf(self): """Check parsing of elf module import.""" input_string = self.data.joinpath('import_ruleset_elf.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('elf', rule['imports']) def test_import_cuckoo(self): """Check parsing of cuckoo module import.""" input_string = self.data.joinpath('import_ruleset_cuckoo.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('cuckoo', rule['imports']) def test_import_magic(self): """Check parsing of magic module import.""" input_string = self.data.joinpath('import_ruleset_magic.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('magic', rule['imports']) def test_import_hash(self): """Check parsing of hash module import.""" input_string = self.data.joinpath('import_ruleset_hash.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('hash', rule['imports']) def test_import_math(self): """Check parsing of math module import.""" input_string = self.data.joinpath('import_ruleset_math.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('math', rule['imports']) def test_import_dotnet(self): """Check parsing of dotnet module import.""" input_string = self.data.joinpath('import_ruleset_dotnet.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('dotnet', rule['imports']) def test_import_androguard(self): """Check parsing of androguard module import.""" input_string = self.data.joinpath('import_ruleset_androguard.yar').read_text() result = self.parser.parse_string(input_string) for rule in result: self.assertIn('androguard', rule['imports']) class TestStoreRawSections(unittest.TestCase): """Test various raw sections are stored correctly.""" def setUp(self): self.data = importlib.resources.files('tests.data.core') def test_raw_meta(self): """Test that the raw meta result is as expected.""" expected = self.data.joinpath('raw_sections_meta.txt').read_text().strip() test_rule = self.data.joinpath('raw_sections.yar').read_text() parser = plyara.core.Plyara() result = parser.parse_string(test_rule)[0] raw = result.get('raw_meta') section = binascii.hexlify(raw.encode()).decode() self.assertEqual(section, expected) def test_raw_strings(self): """Test that the raw strings result is as expected.""" expected = self.data.joinpath('raw_sections_strings.txt').read_text().strip() test_rule = self.data.joinpath('raw_sections.yar').read_text() parser = plyara.core.Plyara() result = parser.parse_string(test_rule)[0] raw = result.get('raw_strings') section = binascii.hexlify(raw.encode()).decode() self.assertEqual(section, expected) def test_raw_condition(self): """Test that the raw condition result is as expected.""" expected = self.data.joinpath('raw_sections_condition.txt').read_text().strip() test_rule = self.data.joinpath('raw_sections.yar').read_text() parser = plyara.core.Plyara() result = parser.parse_string(test_rule)[0] raw = result.get('raw_condition') section = binascii.hexlify(raw.encode()).decode() self.assertEqual(section, expected) def test_no_raw_sections(self): """Test that raw sections are not saved when init parameter is False.""" test_rule = self.data.joinpath('raw_sections.yar').read_text() parser = plyara.core.Plyara(store_raw_sections=False) result = parser.parse_string(test_rule)[0] self.assertIsNone(result.get('raw_meta')) self.assertIsNone(result.get('raw_strings')) self.assertIsNone(result.get('raw_condition')) class TestRuleParser(unittest.TestCase): """Check yara rule parsing.""" def setUp(self): self.parser = plyara.core.Plyara() self.data = importlib.resources.files('tests.data.core') self.common = importlib.resources.files('tests.data.common') self.unhandled_rule = 'Unhandled Test Rule: {}' def test_comments(self): """Check that all comment locations in a rule are parsed correctly.""" input_string = self.data.joinpath('comments_ruleset.yar').read_text() lcomments = [ "// nameline", "// openbrace", "// metasection", "// metakv", "// stringssection", "// bytestring", "// conditionsection", "// conditioninternal1", "// conditioninternal2", "// conditioninternal3", "// condition", "// closebrace", "// bytestringinternal2", "// bytestringinternal1" ] mcomments = [ "/* nameline\n secondline */", "/* openbrace\n secondline */", "/* metasection\n secondline */", "/* metakv\n secondline */", "/* stringssection\n secondline */", "/* bytestring\n secondline */", "/* conditionsection\n secondline */", "/* conditioninternal1\n secondline */", "/* condition\n secondline */", "/* closebrace\n secondline */", "/* bytestringinternal1\n secondline */" ] expected = [lcomments, mcomments] result = self.parser.parse_string(input_string) for i, entry in enumerate(result): with self.subTest(i=i): comments = entry.get('comments') self.assertListEqual(comments, expected[i]) def test_comments_crlf(self): """Check that all comment locations in a rule are parsed correctly in CRLF file.""" input_string = self.data.joinpath('comments_ruleset_crlf.yar').read_bytes().decode() lcomments = [ "// nameline", "// openbrace", "// metasection", "// metakv", "// stringssection", "// bytestring", "// conditionsection", "// conditioninternal1", "// conditioninternal2", "// conditioninternal3", "// condition", "// closebrace", "// bytestringinternal2", "// bytestringinternal1" ] mcomments = [ "/* nameline\r\n secondline */", "/* openbrace\r\n secondline */", "/* metasection\r\n secondline */", "/* metakv\r\n secondline */", "/* stringssection\r\n secondline */", "/* bytestring\r\n secondline */", "/* conditionsection\r\n secondline */", "/* conditioninternal1\r\n secondline */", "/* condition\r\n secondline */", "/* closebrace\r\n secondline */", "/* bytestringinternal1\r\n secondline */" ] expected = [lcomments, mcomments] result = self.parser.parse_string(input_string) for i, entry in enumerate(result): with self.subTest(i=i): comments = entry.get('comments') self.assertListEqual(comments, expected[i]) def test_scopes(self): input_string = self.data.joinpath('scope_ruleset.yar').read_text() result = self.parser.parse_string(input_string) for entry in result: rulename = entry['rule_name'] if rulename == 'GlobalScope': self.assertIn('global', entry['scopes']) elif rulename == 'PrivateScope': self.assertIn('private', entry['scopes']) elif rulename == 'PrivateGlobalScope': self.assertIn('global', entry['scopes']) self.assertIn('private', entry['scopes']) else: raise AssertionError(self.unhandled_rule.format(rulename)) def test_tags(self): input_string = self.data.joinpath('tag_ruleset.yar').read_text() result = self.parser.parse_string(input_string) for entry in result: rulename = entry['rule_name'] if rulename == 'OneTag': self.assertEqual(len(entry['tags']), 1) self.assertIn('tag1', entry['tags']) elif rulename == 'TwoTags': self.assertEqual(len(entry['tags']), 2) self.assertIn('tag1', entry['tags']) self.assertIn('tag2', entry['tags']) elif rulename == 'ThreeTags': self.assertTrue(len(entry['tags']), 3) self.assertIn('tag1', entry['tags']) self.assertIn('tag2', entry['tags']) self.assertIn('tag3', entry['tags']) else: raise AssertionError(self.unhandled_rule.format(rulename)) def test_metadata(self): input_string = self.data.joinpath('metadata_ruleset.yar').read_text() result = self.parser.parse_string(input_string) for entry in result: rulename = entry['rule_name'] kv = entry['metadata'] kv_list = [(k,) + (v, ) for dic in kv for k, v in dic.items()] if rulename == 'StringTypeMetadata': self.assertEqual(len(kv), 2) self.assertEqual(kv_list[0][0], 'string_value') self.assertEqual(kv_list[0][1], 'String Metadata') self.assertEqual(kv_list[1][0], 'string_value2') self.assertEqual(kv_list[1][1], 'String Metadata with \\"ending quotes\\"') elif rulename == 'IntegerTypeMetadata': self.assertEqual(len(kv), 1) self.assertEqual(kv_list[0][0], 'integer_value') self.assertIs(kv_list[0][1], 100) elif rulename == 'BooleanTypeMetadata': self.assertEqual(len(kv), 1) self.assertEqual(kv_list[0][0], 'boolean_value') self.assertIs(kv_list[0][1], True) elif rulename == 'AllTypesMetadata': self.assertEqual(len(kv), 3) self.assertEqual(kv_list[0][0], 'string_value') self.assertEqual(kv_list[1][0], 'integer_value') self.assertEqual(kv_list[2][0], 'boolean_value') self.assertEqual(kv_list[0][1], 'Different String Metadata') self.assertIs(kv_list[1][1], 33) self.assertIs(kv_list[2][1], False) elif rulename == 'NegativeNumberMetadata': self.assertEqual(len(kv), 1) self.assertEqual(kv_list[0][0], 'negative') self.assertIs(kv_list[0][1], -5) else: raise AssertionError(self.unhandled_rule.format(rulename)) def test_strings(self): input_string = self.data.joinpath('string_ruleset.yar').read_text() result = self.parser.parse_string(input_string) for entry in result: rulename = entry['rule_name'] kv = entry['strings'] if rulename == 'Text': self.assertEqual(kv, [{'name': '$text_string', 'value': 'foobar', 'type': 'text'}]) elif rulename == 'FullwordText': self.assertEqual(kv, [{ 'name': '$text_string', 'value': 'foobar', 'type': 'text', 'modifiers': ['fullword']}]) elif rulename == 'CaseInsensitiveText': self.assertEqual(kv, [{'name': '$text_string', 'value': 'foobar', 'type': 'text', 'modifiers': ['nocase']}]) elif rulename == 'WideCharText': self.assertEqual(kv, [{'name': '$wide_string', 'value': 'Borland', 'type': 'text', 'modifiers': ['wide']}]) elif rulename == 'WideCharAsciiText': self.assertEqual(kv, [{'name': '$wide_and_ascii_string', 'value': 'Borland', 'type': 'text', 'modifiers': ['wide', 'ascii']}]) elif rulename == 'HexWildcard': self.assertEqual(kv, [{'name': '$hex_string', 'value': '{ E2 34 ?? C8 A? FB }', 'type': 'byte'}]) elif rulename == 'HexJump': self.assertEqual(kv, [{'name': '$hex_string', 'value': '{ F4 23 [4-6] 62 B4 }', 'type': 'byte'}]) elif rulename == 'HexAlternatives': self.assertEqual(kv, [{'name': '$hex_string', 'value': '{ F4 23 ( 62 B4 | 56 ) 45 }', 'type': 'byte'}]) elif rulename == 'HexMultipleAlternatives': self.assertEqual(kv, [{'name': '$hex_string', 'value': '{ F4 23 ( 62 B4 | 56 | 45 ?? 67 ) 45 }', 'type': 'byte'}]) elif rulename == 'RegExp': self.assertEqual(kv, [ { 'name': '$re1', 'value': '/md5: [0-9a-fA-F]{32}/', 'type': 'regex', 'modifiers': ['nocase'], }, { 'name': '$re2', 'value': '/state: (on|off)/i', 'type': 'regex', }, { 'name': '$re3', 'value': r'/\x00https?:\/\/[^\x00]{4,500}\x00\x00\x00/', 'type': 'regex', }]) elif rulename == 'Xor': self.assertEqual(kv, [{'name': '$xor_string', 'value': 'This program cannot', 'type': 'text', 'modifiers': ['xor']}]) elif rulename == 'WideXorAscii': self.assertEqual(kv, [{'name': '$xor_string', 'value': 'This program cannot', 'type': 'text', 'modifiers': ['xor', 'wide', 'ascii']}]) elif rulename == 'WideXor': self.assertEqual(kv, [{'name': '$xor_string', 'value': 'This program cannot', 'type': 'text', 'modifiers': ['xor', 'wide']}]) elif rulename == 'DoubleBackslash': self.assertEqual(kv, [{'name': '$bs', 'value': r'\"\\\\\\\"', 'type': 'text'}]) elif rulename == 'DoubleQuote': self.assertEqual(kv, [{'name': '$text_string', 'value': r'foobar\"', 'type': 'text'}]) elif rulename == 'HorizontalTab': self.assertEqual(kv, [{'name': '$text_string', 'value': r'foo\tbar', 'type': 'text'}]) elif rulename == 'Newline': self.assertEqual(kv, [{'name': '$text_string', 'value': r'foo\nbar', 'type': 'text'}]) elif rulename == 'HexEscape': self.assertEqual(kv, [{'name': '$text_string', 'value': r'foo\x00bar', 'type': 'text'}]) else: raise AssertionError(self.unhandled_rule.format(rulename)) def test_condition_only(self): """Test that a rule with only conditions is parsed correctly.""" input_string = self.data.joinpath('only_condition.yar').read_text() result = self.parser.parse_string(input_string) for entry in result: condition_terms = entry.get('condition_terms') self.assertListEqual(condition_terms, ['true']) raw_condition = entry.get('raw_condition') self.assertEqual(raw_condition, 'condition: true') def test_private_bytestring_modifier(self): """Check if the private bytestring modifier is parsed correctly.""" input_rule = r''' rule sample { strings: $a = { ABABABAB } private condition: $a } ''' result = self.parser.parse_string(input_rule) modifier = result[0]['strings'][0]['modifiers'][0] self.assertEqual(modifier, 'private') def test_illegal_rule_name(self): """Check if illegal rule name with '.' character.""" inputRules = r''' rule sam.ple { condition: true } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(inputRules) def test_illegal_rule_tag(self): """Check if illegal rule tag with '.' character.""" inputRules = r''' rule badtag : inva.lid { condition: true } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(inputRules) def test_string_illegal_character(self): """Check if illegal character is in a string.""" inputRules = r''' rule sample { strings: $ = "foo bar" condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(inputRules) def test_string_bad_escaped_hex(self): inputRules = r''' rule sample { strings: $ = "foo\xZZbar" condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(inputRules) def test_string_invalid_escape(self): inputRules = r''' rule sample { strings: $ = "foo\gbar" condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(inputRules) def test_illegal_stringname_prefix(self): input_rule = r''' rule sample { strings: $-a = "test" condition: any of them } ''' with self.assertRaises(ParseTypeError): self.parser.parse_string(input_rule) def test_illegal_stringname_infix(self): input_rule = r''' rule sample { strings: $a-b = "test" condition: any of them } ''' with self.assertRaises(ParseTypeError): self.parser.parse_string(input_rule) def test_illegal_stringname_suffix(self): input_rule = r''' rule sample { strings: $a- = "test" condition: any of them } ''' with self.assertRaises(ParseTypeError): self.parser.parse_string(input_rule) def test_duplicate_string_name(self): input_rule = r''' rule sample { strings: $a = "test" $a = "testtest" condition: $a } ''' with self.assertRaises(ParseTypeError): self.parser.parse_string(input_rule) def test_conditions(self): input_string = self.data.joinpath('condition_ruleset.yar').read_text() # Just checking for parsing errors self.parser.parse_string(input_string) def test_include(self): input_string = self.data.joinpath('include_ruleset.yar').read_text() result = self.parser.parse_string(input_string) self.assertEqual(result[0]['includes'], ['string_ruleset.yar']) def test_include_statements(self): self.parser.parse_string('include "file1.yara"\ninclude "file2.yara"\ninclude "file3.yara"') self.assertEqual(len(self.parser.includes), 3) def test_rules_from_yara_project(self): input_rules = self.common.joinpath('test_rules_from_yara_project.yar').read_text() plyara = Plyara() output = plyara.parse_string(input_rules) self.assertEqual(len(output), 293) def test_multiple_threads(self): input_rules = self.common.joinpath('test_rules_from_yara_project.yar').read_text() def parse_rules(rules): plyara = Plyara() return plyara.parse_string(input_rules) with concurrent.futures.ThreadPoolExecutor(max_workers=4) as e: futs = [e.submit(parse_rules, input_rules) for _ in range(4)] for fut in concurrent.futures.as_completed(futs): self.assertEqual(len(fut.result()), 293) def test_clear(self): # instantiate parser parser = Plyara() # open a ruleset with one or more rules input_rules = self.data.joinpath('test_ruleset_2_rules.yar').read_text() # parse the rules parser.parse_string(input_rules) # clear the parser's state parser.clear() # has lineno been reset self.assertEqual(parser.lexer.lineno, 1) # open a ruleset with one rule input_rules = self.data.joinpath('test_ruleset_1_rule.yar').read_text() # parse the rules result = parser.parse_string(input_rules) # does the result contain just the rule from the second parse self.assertEqual(len(result), 1) self.assertEqual(result[0]['rule_name'], 'rule_one') def test_find_imports_in_condition(self): """Check if the finder function works across all testing rules.""" input_rulesets = self.data.glob('import_ruleset_*.yar') for ruleset in input_rulesets: import_type = ruleset.name[15:-4] with self.subTest(import_type=import_type): parser = Plyara() input_string = ruleset.read_text() parser._raw_input = input_string parser.parser.parse(input_string, lexer=parser.lexer) imports = parser._find_imports_in_condition(parser.imports, parser.rules[0]['condition_terms']) self.assertEqual(imports, [import_type]) class TestRuleParserKVMeta(unittest.TestCase): """Check metadata key value pairs.""" def setUp(self): self.parser = plyara.core.Plyara(meta_as_kv=True) self.data = importlib.resources.files('tests.data.core') def test_meta_kv(self): input_string = self.data.joinpath('metakv_test.yar').read_text() reference1 = {'author': 'Malware Utkonos', 'date': '2020-01-04', 'tlp': 'Green'} reference2 = {'author': 'Someone else', 'date': '2020-01-04', 'tlp': 'Green'} result = self.parser.parse_string(input_string) self.assertEqual(result[0]['metadata_kv'], reference1) self.assertEqual(result[1]['metadata_kv'], reference2) class TestYaraRules(unittest.TestCase): """Check as wide a variety of yara rules as possible.""" def setUp(self): self.parser = plyara.core.Plyara() self.data = importlib.resources.files('tests.data.core') def test_multiple_rules(self): input_string = ''' rule FirstRule { meta: author = "Andrés Iniesta" date = "2015-01-01" strings: $a = "hark, a \\"string\\" here" fullword ascii $b = { 00 22 44 66 88 aa cc ee } condition: all of them } import "bingo" import "bango" rule SecondRule : aTag { meta: author = "Ivan Rakitić" date = "2015-02-01" strings: $x = "hi" $y = /state: (on|off)/ wide $z = "bye" condition: for all of them : ( # > 2 ) } rule ThirdRule {condition: uint32(0) == 0xE011CFD0} ''' plyara = Plyara() result = plyara.parse_string(input_string) self.assertEqual(len(result), 3) kv_list = [(k,) + (v, ) for dic in result[0]['metadata'] for k, v in dic.items()] self.assertEqual(kv_list[0][0], 'author') self.assertEqual(kv_list[0][1], 'Andrés Iniesta') self.assertEqual(kv_list[1][0], 'date') self.assertEqual(kv_list[1][1], '2015-01-01') self.assertEqual([x['name'] for x in result[0]['strings']], ['$a', '$b']) def disable_test_rule_name_imports_and_scopes(self): input_string_nis = r''' rule four {meta: i = "j" strings: $a = "b" condition: true } global rule five {meta: i = "j" strings: $a = "b" condition: false } private rule six {meta: i = "j" strings: $a = "b" condition: true } global private rule seven {meta: i = "j" strings: $a = "b" condition: true } import "lib1" rule eight {meta: i = "j" strings: $a = "b" condition: true } import "lib1" import "lib2" rule nine {meta: i = "j" strings: $a = "b" condition: true } import "lib2" private global rule ten {meta: i = "j" strings: $a = "b" condition: true } ''' plyara = Plyara() result = plyara.parse_string(input_string_nis) self.assertEqual(len(result), 7) for rule in result: rule_name = rule['rule_name'] if rule_name == 'four': self.assertNotIn('scopes', rule) self.assertIn('imports', rule) if rule_name == 'five': self.assertIn('imports', rule) self.assertIn('global', rule['scopes']) if rule_name == 'six': self.assertIn('imports', rule) self.assertIn('private', rule['scopes']) if rule_name == 'seven': self.assertIn('imports', rule) self.assertTrue('private' in rule['scopes'] and 'global' in rule['scopes']) if rule_name == 'eight': self.assertIn('lib1', rule['imports']) self.assertNotIn('scopes', rule) if rule_name == 'nine': self.assertTrue('lib1' in rule['imports'] and 'lib2' in rule['imports']) self.assertNotIn('scopes', rule) if rule_name == 'ten': self.assertTrue('lib1' in rule['imports'] and 'lib2' in rule['imports']) self.assertTrue('global' in rule['scopes'] and 'private' in rule['scopes']) def test_rule_name_imports_by_instance(self): input1 = r''' rule one {meta: i = "j" strings: $a = "b" condition: true } ''' input2 = r''' import "lib1" rule two {meta: i = "j" strings: $a = "b" condition: true } import "lib2" private global rule three {meta: i = "j" strings: $a = "b" condition: true } ''' plyara1 = Plyara(import_effects=True) result1 = plyara1.parse_string(input1) plyara2 = Plyara(import_effects=True) result2 = plyara2.parse_string(input2) self.assertEqual(len(result1), 1) self.assertEqual(len(result2), 2) for rule in result1: rule_name = rule['rule_name'] if rule_name == 'one': self.assertNotIn('scopes', rule) self.assertNotIn('imports', rule) for rule in result2: rule_name = rule['rule_name'] if rule_name == 'two': self.assertTrue('lib1' in rule['imports'] and 'lib2' in rule['imports']) self.assertNotIn('scopes', rule) if rule_name == 'three': self.assertTrue('lib1' in rule['imports'] and 'lib2' in rule['imports']) self.assertTrue('global' in rule['scopes'] and 'private' in rule['scopes']) def test_rule_name(self): input_rule = r''' rule testName { meta: my_identifier_1 = "" my_identifier_2 = 24 my_identifier_3 = true strings: $my_text_string = "text here" $my_hex_string = { E2 34 A1 C8 23 FB } condition: $my_text_string or $my_hex_string } ''' plyara = Plyara() result = plyara.parse_string(input_rule) self.assertEqual(len(result), 1) self.assertEqual(result[0]['rule_name'], 'testName') def test_store_raw(self): input_rule = r''' rule testName { meta: my_identifier_1 = "" my_identifier_2 = 24 my_identifier_3 = true strings: $my_text_string = "text here" $my_hex_string = { E2 34 A1 C8 23 FB } condition: $my_text_string or $my_hex_string } rule testName2 { strings: $test1 = "some string" condition: $test1 or true } rule testName3 { condition: true } rule testName4 : tag1 tag2 {meta: i = "j" strings: $a = "b" condition: true } ''' plyara = Plyara(store_raw_sections=True) result = plyara.parse_string(input_rule) self.assertEqual(len(result), 4) self.assertTrue(result[0].get('raw_meta', False)) self.assertTrue(result[0].get('raw_strings', False)) self.assertTrue(result[0].get('raw_condition', False)) self.assertFalse(result[1].get('raw_meta', False)) self.assertTrue(result[1].get('raw_strings', False)) self.assertTrue(result[1].get('raw_condition', False)) self.assertFalse(result[2].get('raw_meta', False)) self.assertFalse(result[2].get('raw_strings', False)) self.assertTrue(result[2].get('raw_condition', False)) self.assertTrue(result[3].get('raw_meta', False)) self.assertTrue(result[3].get('raw_strings', False)) self.assertTrue(result[3].get('raw_condition', False)) def test_tags(self): input_tags = r''' rule eleven: tag1 {meta: i = "j" strings: $a = "b" condition: true } rule twelve : tag1 tag2 {meta: i = "j" strings: $a = "b" condition: true } ''' plyara = Plyara() result = plyara.parse_string(input_tags) for rule in result: rule_name = rule['rule_name'] if rule_name == 'eleven': self.assertEqual(len(rule['tags']), 1) self.assertIn('tag1', rule['tags']) if rule_name == 'twelve': self.assertEqual(len(rule['tags']), 2) self.assertIn('tag1', rule['tags']) self.assertIn('tag2', rule['tags']) def test_empty_string(self): input_rules = r''' rule thirteen { meta: my_identifier_1 = "" my_identifier_2 = 24 my_identifier_3 = true strings: $my_text_string = "text here" $my_hex_string = { E2 34 A1 C8 23 FB } condition: $my_text_string or $my_hex_string } ''' plyara = Plyara() result = plyara.parse_string(input_rules) for rule in result: rule_name = rule['rule_name'] if rule_name == 'thirteen': self.assertEqual(len(rule['metadata']), 3) def test_bytestring(self): input_rules = r''' rule testName { strings: $a1 = { E2 34 A1 C8 23 FB } $a2 = { E2 34 A1 C8 2? FB } $a3 = { E2 34 A1 C8 ?? FB } $a4 = { E2 34 A1 [6] FB } $a5 = { E2 34 A1 [4-6] FB } $a6 = { E2 34 A1 [4 - 6] FB } $a7 = { E2 34 A1 [-] FB } $a8 = { E2 34 A1 [10-] FB } $a9 = { E2 23 ( 62 B4 | 56 ) 45 FB } $a10 = { E2 23 62 B4 56 // comment 45 FB } $a11 = { E2 23 62 B4 56 /* comment */ 45 FB } $a12 = { E2 23 62 B4 56 45 FB // comment } $a13 = { E2AAAAAA ~00 BBBBBBFB } $a14 = { E2AAAAAA~00 BBBBBBFB } $a15 = { E2AAAAAA~00BBBBBBFB } condition: any of them } ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(len(result), 1) for rule in result: rule_name = rule['rule_name'] if rule_name == 'testName': self.assertEqual(len(rule['strings']), 15) for hex_string in rule['strings']: # Basic sanity check. self.assertIn('E2', hex_string['value']) self.assertIn('FB', hex_string['value']) self.assertEqual(rule['strings'][0]['value'], '{ E2 34 A1 C8 23 FB }') self.assertEqual(rule['strings'][4]['value'], '{ E2 34 A1 [4-6] FB }') self.assertEqual(rule['strings'][8]['value'], '{ E2 23 ( 62 B4 | 56 ) 45 FB }') long_string = '{ E2 23 62 B4 56 // comment\n 45 FB }' self.assertEqual(rule['strings'][9]['value'], long_string) self.assertEqual(rule['strings'][10]['value'], '{ E2 23 62 B4 56 /* comment */ 45 FB }') long_string = '{\n E2 23 62 B4 56 45 FB // comment\n }' self.assertEqual(rule['strings'][11]['value'], long_string) self.assertEqual(rule['strings'][12]['value'], '{ E2AAAAAA ~00 BBBBBBFB }') self.assertEqual(rule['strings'][13]['value'], '{ E2AAAAAA~00 BBBBBBFB }') self.assertEqual(rule['strings'][14]['value'], '{ E2AAAAAA~00BBBBBBFB }') @staticmethod def test_nested_bytestring(): input_rules = r''' rule sample { strings: $ = { 4D 5A ( 90 ( 00 | 01 ) | 89 ) } condition: all of them } ''' plyara = Plyara() plyara.parse_string(input_rules) def test_bytestring_bad_jump(self): input_rules = r''' rule testName { strings: $a6 = { E2 34 A1 [6 - 4] FB } condition: any of them } ''' plyara = Plyara() with self.assertRaises(ParseValueError): plyara.parse_string(input_rules) def test_bytestring_bad_group(self): input_rules = r''' rule sample { strings: $ = { 4D 5A ( 90 ( 00 | 01 ) | 89 ) ) } condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseValueError): plyara.parse_string(input_rules) def test_bytestring_bad_hexchar(self): input_rules = r''' rule sample { strings: $ = { 4D 5X } condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(input_rules) def test_rexstring(self): input_rules = r''' rule testName { strings: $a1 = /abc123 \d/i $a2 = /abc123 \d+/i // comment $a3 = /abc123 \d\/ afterspace/is // comment $a4 = /abc123 \d\/ afterspace/is nocase // comment $a5 = /abc123 \d\/ afterspace/nocase // comment $a6 = /abc123 \d\/ afterspace/nocase// comment /* It should only consume the regex pattern and not text modifiers or comment, as those will be parsed separately. */ condition: any of them } ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(len(result), 1) for rule in result: rule_name = rule['rule_name'] if rule_name == 'testName': self.assertEqual(len(rule['strings']), 6) for rex_string in rule['strings']: if rex_string['name'] == '$a1': self.assertEqual(rex_string['value'], '/abc123 \\d/i') elif rex_string['name'] == '$a2': self.assertEqual(rex_string['value'], '/abc123 \\d+/i') elif rex_string['name'] == '$a3': self.assertEqual(rex_string['value'], '/abc123 \\d\\/ afterspace/is') elif rex_string['name'] == '$a4': self.assertEqual(rex_string['value'], '/abc123 \\d\\/ afterspace/is') self.assertEqual(rex_string['modifiers'], ['nocase']) elif rex_string['name'] in ['$a5', '$a6']: self.assertEqual(rex_string['value'], '/abc123 \\d\\/ afterspace/') self.assertEqual(rex_string['modifiers'], ['nocase']) else: self.assertFalse('Unknown string name...') def test_bad_regex_string_char(self): """Test if an illegal character in a regex string raises an exception.""" input_string = self.data.joinpath('bad_regex_string_char.yar').read_text() with self.assertRaises(ParseTypeError): _ = self.parser.parse_string(input_string) def test_string(self): input_rules = r''' rule testName { strings: $a1 = "test string" $a2 = "test string" // comment $a3 = "test string" /* comment */ $a4 = "teststring" //comment $a5 = "test // string" // comm ent $a6 = "test /* string */ string" $a7 = "teststring" //comment $a8 = "'test" $a9 = "'test' string" $a10 = "\"test string\"" $a11 = "test \"string\"" $a12 = "test \"string\" test \\" $a13 = "test string" // "comment" $a14 = "test string" nocase wide // comment condition: any of them } ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(len(result), 1) for rule in result: self.assertEqual(len(rule['strings']), 14) self.assertEqual(rule['strings'][0]['value'], 'test string') self.assertEqual(rule['strings'][1]['value'], 'test string') self.assertEqual(rule['strings'][2]['value'], 'test string') self.assertEqual(rule['strings'][3]['value'], 'teststring') self.assertEqual(rule['strings'][4]['value'], 'test // string') self.assertEqual(rule['strings'][5]['value'], 'test /* string */ string') self.assertEqual(rule['strings'][6]['value'], 'teststring') self.assertEqual(rule['strings'][7]['value'], "'test") self.assertEqual(rule['strings'][8]['value'], "'test' string") self.assertEqual(rule['strings'][9]['value'], '\\"test string\\"') self.assertEqual(rule['strings'][10]['value'], 'test \\"string\\"') self.assertEqual(rule['strings'][11]['value'], 'test \\"string\\" test \\\\') self.assertEqual(rule['strings'][12]['value'], 'test string') self.assertEqual(rule['strings'][13]['value'], 'test string') def test_raw_condition_contains_all_condition_text(self): input_rules = r''' rule testName {condition: any of them} ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(result[0]['raw_condition'], 'condition: any of them') def test_raw_strings_contains_all_string_text(self): input_rules = r''' rule testName {strings: $a = "1" condition: true} ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(result[0]['raw_strings'], 'strings: $a = "1" ') def test_raw_meta_contains_all_meta_text(self): input_rules = r''' rule testName {meta: author = "Test" condition: true} ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(result[0]['raw_meta'], 'meta: author = "Test" ') # strings after meta input_rules = r''' rule testName {meta: author = "Test" strings: $a = "1"} ''' plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(result[0]['raw_meta'], 'meta: author = "Test" ') def test_illegal_char(self): """Test if an illegal character anywhere raises an exception.""" input_string = self.data.joinpath('illegal_char.yar').read_text() with self.assertRaises(ParseTypeError): _ = self.parser.parse_string(input_string) def test_parse_file_without_rules_returns_empty_list(self): input_rules = str() plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(result, list()) def test_bad_condition_string(self): input_string = self.data.joinpath('bad_condition_string.yar').read_text() plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(input_string) def test_lineno_incremented_by_newlines_in_bytestring(self): input_rules = r''' rule sample { strings: $ = { 00 00 00 00 00 00 00 00 00 00 00 00 } //line 6 conditio: //fault all of them } ''' plyara = Plyara() with contextlib.suppress(ParseTypeError): try: plyara.parse_string(input_rules) except ParseTypeError as e: self.assertEqual(7, e.lineno) raise e def test_lineno_incremented_by_windows_newlines_in_bytestring(self): input_rules = self.data.joinpath('windows_newline_ruleset_with_error.yar').read_text() plyara = Plyara() with self.assertRaises(ParseTypeError): try: plyara.parse_string(input_rules) except ParseTypeError as e: self.assertEqual(6, e.lineno) raise e def test_lineno_incremented_by_windows_newlines_in_comment(self): input_rules = self.data.joinpath('windows_newline_ruleset_comment.yar').read_text() plyara = Plyara() plyara.parse_string(input_rules) self.assertEqual(plyara.lexer.lineno, 13) def test_windows_CRNL(self): input_rules = self.data.joinpath('windows_newline_ruleset.yar').read_text() reference = [{'condition_terms': ['all', 'of', 'them'], 'raw_condition': "condition:\nall of them\n", 'raw_strings': "strings:\n$ = { 00\n 00 }\n", 'rule_name': 'sample', 'start_line': 1, 'stop_line': 8, 'strings': [{'name': '$', 'type': 'byte', 'value': '{ 00\n 00 }'}]}] plyara = Plyara() result = plyara.parse_string(input_rules) self.assertEqual(result, reference) def test_anonymous_array_condition(self): inputRules = r''' rule sample { strings: $ = { 01 02 03 04 } condition: for all of ($) : ( @ < 0xFF ) } ''' plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(result[0].get('condition_terms')[8], '@') def test_xor_modified_condition(self): """Check that various xor modifiers are parsed correctly.""" input_rules = self.data.joinpath('xor_modifier_ruleset.yar').read_text() expected = [ ['xor', 'wide'], ['xor(16)'], ['xor(16-128)'], ['xor(0x10)'], ['xor(0x10-0x80)'], ['xor(16-0x80)'], ['xor(0x10-64)'] ] parser = plyara.core.Plyara() results = parser.parse_string(input_rules) for i, result in enumerate(results): with self.subTest(i=result.get('rule_name')): mods = result.get('strings')[0].get('modifiers') self.assertListEqual(mods, expected[i]) def test_xor_modifier_whitespace(self): """Check that all whitespace variations are equivalently parsed.""" input_rules = self.data.joinpath('xor_modifier_whitespace.yar').read_text() expected = 'xor(0x01-0xff)' parser = plyara.core.Plyara() results = parser.parse_string(input_rules) for result in results: with self.subTest(i=result.get('rule_name')): mod = result.get('strings')[0].get('modifiers')[0] self.assertEqual(mod, expected) def test_base64_modified_condition(self): """Check that various ba64 modifiers are parsed correctly.""" input_rules = self.data.joinpath('base64_modifier_ruleset.yar').read_text() expected = [ ['base64'], ['base64wide'], [r'base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu")'], [r'base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu")'] ] parser = plyara.core.Plyara() results = parser.parse_string(input_rules) for i, result in enumerate(results): with self.subTest(i=result.get('rule_name')): mods = result.get('strings')[0].get('modifiers') self.assertListEqual(mods, expected[i]) def test_duplicated_simple_modifier_xor(self): """Test that a duplicated string modifier raises an exception.""" error_msg = r'Duplicate simple text string modifier xor on line \d' input_rule = r''' rule duplicate_modifier { strings: $a = "one" xor xor condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_duplicated_complex_simple_modifier_xor(self): """Test that a duplicated string modifiers of two types raises an exception.""" error_msg = r'Duplicate complex xor text string modifier xor on line \d' input_rule = r''' rule duplicate_modifier { strings: $a = "one" xor xor(0x10) condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_duplicated_complex_simple_modifier_base64(self): """Test that a duplicated string modifiers of two types raises an exception.""" error_msg = r'Duplicate complex base64 text string modifier base64 on line \d' input_rule = r''' rule duplicate_modifier { strings: $a = "one" base64 base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_duplicated_complex_simple_modifier_base64wide(self): """Test that a duplicated string modifiers of two types raises an exception.""" error_msg = r'Duplicate complex base64wide text string modifier base64wide on line \d' input_rule = r''' rule duplicate_modifier { strings: $a = "one" base64wide base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_unexpected_xor(self): """Test that an xor modifier on the wrong string type raises an exception.""" error_msg = r'Unknown text xor for token of type XOR_MOD on line \d' input_rule = r''' rule invalid_xor_modifier { strings: $a = /AA/ xor(500) condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_unexpected_base64(self): """Test that an base64 modifier on the wrong string type raises an exception.""" error_msg = r'Unknown text base64 for token of type BASE64 on line \d' input_rule = r''' rule invalid_base64_modifier { strings: $a = /AA/ base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_unexpected_base64wide(self): """Test that an base64wide modifier on the wrong string type raises an exception.""" error_msg = r'Unknown text base64wide for token of type BASE64WIDE on line \d' input_rule = r''' rule invalid_base64wide_modifier { strings: $a = /AA/ base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_invalid_modifier_combination_base64_nocase(self): """Test that incompatible modifiers raise an exception.""" error_msg = r'Incompatible modifiers \[nocase, base64\] on line \d' input_rule = r''' rule base64_error_nocase { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") nocase condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_invalid_modifier_combination_base64_xor(self): """Test that incompatible modifiers raise an exception.""" error_msg = r'Incompatible modifiers \[xor, base64\] on line \d' input_rule = r''' rule base64_error_xor { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") xor condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_invalid_modifier_combination_base64wide_nocase(self): """Test that incompatible modifiers raise an exception.""" error_msg = r'Incompatible modifiers \[nocase, base64wide\] on line \d' input_rule = r''' rule base64wide_error_nocase { strings: $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") nocase condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_invalid_modifier_combination_base64wide_xor(self): """Test that incompatible modifiers raise an exception.""" error_msg = r'Incompatible modifiers \[xor, base64wide\] on line \d' input_rule = r''' rule base64wide_error_xor { strings: $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") xor condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_base64_alphabet_length_too_long(self): """Test that a base64 alphabet is not more than 64 characters.""" error_msg = r'Modifier base64 predicate too long: \d+ on line \d' input_rule = r''' rule base64_error_xor { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstuxyz") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_base64_alphabet_length_too_short(self): """Test that a base64 alphabet is not less than 64 characters.""" error_msg = r'Modifier base64 predicate too short: \d+ on line \d' input_rule = r''' rule base64_error_xor { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZ") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_base64wide_alphabet_length_too_long(self): """Test that a base64wide alphabet is not more than 64 characters.""" error_msg = r'Modifier base64wide predicate too long: \d+ on line \d' input_rule = r''' rule base64_error_xor { strings: $a = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstuxyz") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) def test_base64wide_alphabet_length_too_short(self): """Test that a base64wide alphabet is not less than 64 characters.""" error_msg = r'Modifier base64wide predicate too short: \d+ on line \d' input_rule = r''' rule base64_error_xor { strings: $a = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZ") condition: all of them } ''' parser = plyara.core.Plyara() with self.assertRaisesRegex(ParseTypeError, error_msg): parser.parse_string(input_rule) if __name__ == '__main__': unittest.main() plyara-2.2.8/tests/test_issues.py0000775000175000017500000002521614751212104015217 0ustar rharha# Copyright 2014 Christian Buia # Copyright 2025 plyara Maintainers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for plyara Github issue fixes.""" import importlib.resources import json import unittest import plyara.core from plyara.exceptions import ParseTypeError from plyara.utils import rebuild_yara_rule class TestGithubIssues(unittest.TestCase): """Check that any fixes for reported issues remain fixed.""" def setUp(self): self.data = importlib.resources.files('tests.data.issues') self.parser = plyara.core.Plyara() # self.maxDiff = None # Reference: https://github.com/plyara/plyara/issues/63 def test_issue_63(self): input_string = self.data.joinpath('comment_only.yar').read_text() result = self.parser.parse_string(input_string) self.assertEqual(result, list()) # Reference: https://github.com/plyara/plyara/issues/99 def test_issue_99(self): input_string1 = self.data.joinpath('issue99_1.yar').read_text() input_string2 = self.data.joinpath('issue99_2.yar').read_text() rules = list() for input_string in [input_string1, input_string2]: yararules = self.parser.parse_string(input_string) self.assertEqual(len(yararules), 1) rules += yararules self.parser.clear() self.assertEqual(len(rules), 2) # Reference: https://github.com/plyara/plyara/issues/107 def test_issue_107(self): input_string = self.data.joinpath('issue107.yar').read_text() result = self.parser.parse_string(input_string) expected = ['(', '#TEST1', '>', '5', ')', 'and', '(', '#test2', '>', '5', ')'] self.assertEqual(result[0]['condition_terms'], expected) # Reference: https://github.com/plyara/plyara/issues/109 def test_issue_109(self): input_string = self.data.joinpath('issue109.yar').read_text() test_result = self.data.joinpath('issue109_good_enough.yar').read_text() results = self.parser.parse_string(input_string) rebuilt_rules = rebuild_yara_rule(results[0]) self.assertEqual(test_result, rebuilt_rules) # Reference: https://github.com/plyara/plyara/issues/112 def test_issue_112(self): input_string = self.data.joinpath('issue112.yar').read_text() correct = { 'minus_bad': ['$str_bef', 'in', '(', '@str_after', '-', '512', '..', '@str_after', ')'], 'minus_good': ['$str_bef', 'in', '(', '@str_after', '-', '512', '..', '@str_after', ')'], 'minus_very_bad': ['$str_bef', 'in', '(', '@str_after', '-', '-', '512', '..', '@str_after', ')'], 'minus_very_very_bad': ['$str_bef', 'in', '(', '@str_after', '-', '-', '512', '..', '@str_after', ')'], 'minus_bad_hexnum': ['$str_bef', 'in', '(', '@str_after', '-', '0x200', '..', '@str_after', ')'], 'minus_good_hexnum': ['$str_bef', 'in', '(', '@str_after', '-', '0x200', '..', '@str_after', ')'], 'minus_very_bad_hexnum': ['$str_bef', 'in', '(', '@str_after', '-', '-', '0x200', '..', '@str_after', ')'], 'minus_very_very_bad_hexnum': [ '$str_bef', 'in', '(', '@str_after', '-', '-', '0x200', '..', '@str_after', ')' ] } result = self.parser.parse_string(input_string) for rule in result: rule_name = rule['rule_name'] with self.subTest(rulename=rule_name): self.assertListEqual(rule['condition_terms'], correct[rule_name]) # Reference: https://github.com/plyara/plyara/issues/115 def test_issue_115(self): input_string = self.data.joinpath('issue115.yar').read_text() correct = { 'bad_parsed_subtraction': ['@a', '-', '@b', '<', '128'], 'good_parsed_addition': ['@a', '+', '@b', '<', '128'], 'rule_extra_empty_line': ['@b', '-', '@a', '<', '128'] } result = self.parser.parse_string(input_string) for rule in result: rule_name = rule['rule_name'] with self.subTest(rulename=rule_name): self.assertListEqual(rule['condition_terms'], correct[rule_name]) # Reference: https://github.com/plyara/plyara/issues/118 def test_issue_118(self): """Check that clearing the parser works after an exception has been raised.""" error_message = 'Unknown text strings: for token of type SECTIONSTRINGS on line 4' input_string = self.data.joinpath('issue118.yar').read_text() for i in range(4): with self.subTest(iteration=i): try: _ = self.parser.parse_string(input_string) except ParseTypeError as e: self.assertEqual(str(e), error_message) self.parser.clear() # Reference: https://github.com/plyara/plyara/issues/141 def test_issue_141_store_raw_sections_true(self): """Check when store_raw_sections at the default.""" input_string = self.data.joinpath('issue141.yar').read_text() parsed_rules = self.parser.parse_string(input_string) for i, rule in enumerate(parsed_rules): with self.subTest(rulenum=i): if i == 0: self.assertIsNone(rule.get('imports')) elif i == 1: self.assertEqual(rule.get('imports'), ['pe']) # Reference: https://github.com/plyara/plyara/issues/141 def test_issue_141_store_raw_sections_false(self): """Check when store_raw_sections set to False.""" input_string = self.data.joinpath('issue141.yar').read_text() parser = plyara.core.Plyara(store_raw_sections=False) parsed_rules = parser.parse_string(input_string) for i, rule in enumerate(parsed_rules): with self.subTest(rulenum=i): if i == 0: self.assertIsNone(rule.get('imports')) elif i == 1: self.assertEqual(rule.get('imports'), ['pe']) # Reference: https://github.com/plyara/plyara/issues/143 def test_issue_143(self): """Check whether xor modifier with hexnum range is parsed correctly.""" input_string = self.data.joinpath('issue143.yar').read_text() parsed_rules = self.parser.parse_string(input_string) strings = parsed_rules[0].get('strings') self.assertIsInstance(strings, list) modifier = strings[0].get('modifiers', list())[0] self.assertEqual(modifier, 'xor(0x01-0xff)') # Reference: https://github.com/plyara/plyara/issues/144 # Reference: https://github.com/CybercentreCanada/assemblyline/issues/231 def test_issue_144(self): """Check whether negative numbers are parsed correctly in the meta section.""" input_string = self.data.joinpath('issue144.yar').read_text() parsed_rules = self.parser.parse_string(input_string) metadata = parsed_rules[0].get('metadata') self.assertIsInstance(metadata, list) quality = [entry['quality'] for entry in metadata if 'quality' in entry] self.assertListEqual(quality, [-5]) # Reference: https://github.com/plyara/plyara/issues/145 def test_issue_145(self): """Check correct parsing for PR#130 changes.""" input_string = self.data.joinpath('issue145.yar').read_text() parsed_rules = self.parser.parse_string(input_string) for rule in parsed_rules: rulename = rule.get('rule_name') with self.subTest(rulenum=rulename): if rulename == 'test1': bv = rule['strings'][0]['value'] self.assertEqual(bv, '{ AA AA ~AA }') elif rulename == 'test2': bv = rule['strings'][0]['value'] self.assertEqual(bv, '{ AA AA~AA }') elif rulename == 'test3': md = rule['metadata'][0]['one'] self.assertEqual(md, 0) elif rulename == 'test4': ct = rule['condition_terms'] self.assertListEqual(ct, ['-', '0.5']) elif rulename == 'test5': ct = rule['condition_terms'] self.assertListEqual(ct, ['-', '1.5']) # Reference: https://github.com/plyara/plyara/issues/150 def test_issue_150(self): """Check that comments between rules are discarded and not attached to a rule.""" input_string = self.data.joinpath('issue150.yar').read_text() parsed_rules = self.parser.parse_string(input_string) for rule in parsed_rules: rulename = rule.get('rule_name') with self.subTest(rulenum=rulename): self.assertIsNone(rule.get('comments')) # Reference: https://github.com/plyara/plyara/issues/153 def test_issue_153(self): """Check that bytestring comments have the correct line number.""" input_string = self.data.joinpath('issue153.yar').read_text() expected = [5, 4] parser = plyara.core.Plyara(testmode=True) _ = parser.parse_string(input_string) for i, record in enumerate(parser._comment_record): with self.subTest(i=i): self.assertEqual(record.lineno, expected[i]) # Reference: https://github.com/plyara/plyara/issues/156 def test_issue_156(self): """Check that bytestring comments have the correct line number and correct end line number.""" input_string = self.data.joinpath('issue156.yar').read_text() expected = json.loads(self.data.joinpath('issue156.json').read_text()) parser = plyara.core.Plyara(testmode=True) result = parser.parse_string(input_string).pop() comments = list() for r in parser._comment_record: record = r.__dict__ record.pop('lexer') comments.append(record) comments = sorted(comments, key=lambda x: x['lineno']) self.assertListEqual(comments, expected) # Check if the rule has the correct stop line self.assertIs(result['stop_line'], 17) if __name__ == '__main__': unittest.main()