plyara-2.1.1/0000755000175000017500000000000013711334624012355 5ustar rhaistrhaistplyara-2.1.1/tests/0000755000175000017500000000000013711334624013517 5ustar rhaistrhaistplyara-2.1.1/tests/__init__.py0000644000175000017500000000000013711334624015616 0ustar rhaistrhaistplyara-2.1.1/tests/unit_tests.py0000644000175000017500000013327113711334624016301 0ustar rhaistrhaist#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2014 Christian Buia # Copyright 2020 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 unit tests. This module contains various unit tests for plyara. """ import concurrent.futures import contextlib import hashlib import io import pathlib import sys import unittest from plyara import Plyara from plyara.exceptions import ParseTypeError, ParseValueError from plyara.utils import generate_hash, generate_logic_hash from plyara.utils import rebuild_yara_rule from plyara.utils import detect_imports, detect_dependencies from plyara.utils import is_valid_rule_name, is_valid_rule_tag from plyara.command_line import main UNHANDLED_RULE_MSG = 'Unhandled Test Rule: {}' data_dir = pathlib.Path('tests').joinpath('data') @contextlib.contextmanager def captured_output(): """Capture stdout and stderr from execution.""" new_out, new_err = io.StringIO(), io.StringIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: sys.stdout, sys.stderr = old_out, old_err class TestUtilities(unittest.TestCase): def test_generate_hash(self): with data_dir.joinpath('logic_collision_ruleset.yar').open('r') as fh: inputString = fh.read() result = Plyara().parse_string(inputString) 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(self): with data_dir.joinpath('rulehashes.txt').open('r') as fh: rule_hashes = fh.read().splitlines() with data_dir.joinpath('test_rules_from_yara_project.yar').open('r') as fh: # Rules containing "(1..#)" or similar iterators cause Unhandled String Count Condition errors inputString = fh.read() results = Plyara().parse_string(inputString) for index, result in enumerate(results): rulehash = generate_hash(result) self.assertEqual(rulehash, rule_hashes[index]) def test_generate_logic_hash(self): with data_dir.joinpath('logic_collision_ruleset_v2.0.0.yar').open('r') as fh: inputString = fh.read() result = Plyara().parse_string(inputString) rule_mapping = {} for entry in result: rulename = entry['rule_name'] setname, _ = rulename.split('_') rulehash = generate_logic_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_logic_hash_output(self): with data_dir.joinpath('rulehashes_v2.0.0.txt').open('r') as fh: rule_hashes = fh.read().splitlines() with data_dir.joinpath('test_rules_from_yara_project.yar').open('r') as fh: # Rules containing "(1..#)" or similar iterators cause Unhandled String Count Condition errors inputString = fh.read() results = Plyara().parse_string(inputString) for index, result in enumerate(results): rulehash = generate_logic_hash(result) self.assertEqual(rulehash, rule_hashes[index]) def test_is_valid_rule_name(self): self.assertTrue(is_valid_rule_name('test')) self.assertTrue(is_valid_rule_name('test123')) self.assertTrue(is_valid_rule_name('test_test')) self.assertTrue(is_valid_rule_name('_test_')) self.assertTrue(is_valid_rule_name('include_test')) self.assertFalse(is_valid_rule_name('123test')) self.assertFalse(is_valid_rule_name('123 test')) self.assertFalse(is_valid_rule_name('test 123')) self.assertFalse(is_valid_rule_name('test test')) self.assertFalse(is_valid_rule_name('test-test')) self.assertFalse(is_valid_rule_name('include')) self.assertFalse(is_valid_rule_name('test!*@&*!&')) self.assertFalse(is_valid_rule_name('')) self.assertTrue(is_valid_rule_name('x' * 128)) self.assertFalse(is_valid_rule_name('x' * 129)) def test_is_valid_rule_tag(self): self.assertTrue(is_valid_rule_tag('test')) self.assertTrue(is_valid_rule_tag('test123')) self.assertTrue(is_valid_rule_tag('test_test')) self.assertTrue(is_valid_rule_tag('_test_')) self.assertTrue(is_valid_rule_tag('include_test')) self.assertFalse(is_valid_rule_tag('123test')) self.assertFalse(is_valid_rule_tag('123 test')) self.assertFalse(is_valid_rule_tag('test 123')) self.assertFalse(is_valid_rule_tag('test test')) self.assertFalse(is_valid_rule_tag('test-test')) self.assertFalse(is_valid_rule_tag('include')) self.assertFalse(is_valid_rule_tag('test!*@&*!&')) self.assertFalse(is_valid_rule_tag('')) self.assertTrue(is_valid_rule_tag('x' * 128)) self.assertFalse(is_valid_rule_tag('x' * 129)) def test_rebuild_yara_rule(self): with data_dir.joinpath('rebuild_ruleset.yar').open('r', encoding='utf-8') as fh: inputString = fh.read() result = Plyara().parse_string(inputString) rebuilt_rules = str() for rule in result: rebuilt_rules += rebuild_yara_rule(rule) self.assertEqual(inputString, 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().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) def test_detect_dependencies(self): with data_dir.joinpath('detect_dependencies_ruleset.yar').open('r') as fh: inputString = fh.read() result = Plyara().parse_string(inputString) self.assertEqual(detect_dependencies(result[0]), list()) self.assertEqual(detect_dependencies(result[1]), list()) self.assertEqual(detect_dependencies(result[2]), list()) self.assertEqual(detect_dependencies(result[3]), ['is__osx', 'priv01', 'priv02', 'priv03', 'priv04']) self.assertEqual(detect_dependencies(result[4]), ['is__elf', 'priv01', 'priv02', 'priv03', 'priv04']) self.assertEqual(detect_dependencies(result[5]), ['is__elf', 'is__osx', 'priv01', 'priv02']) self.assertEqual(detect_dependencies(result[6]), ['is__elf', 'is__osx', 'priv01']) self.assertEqual(detect_dependencies(result[7]), ['is__elf']) self.assertEqual(detect_dependencies(result[8]), ['is__osx', 'is__elf']) self.assertEqual(detect_dependencies(result[9]), ['is__osx']) self.assertEqual(detect_dependencies(result[10]), ['is__elf', 'is__osx']) self.assertEqual(detect_dependencies(result[11]), ['is__osx']) self.assertEqual(detect_dependencies(result[12]), list()) self.assertEqual(detect_dependencies(result[13]), list()) self.assertEqual(detect_dependencies(result[14]), ['is__osx']) self.assertEqual(detect_dependencies(result[15]), ['is__osx']) def test_detect_imports(self): for imp in ('androguard', 'cuckoo', 'dotnet', 'elf', 'hash', 'magic', 'math', 'pe'): with data_dir.joinpath('import_ruleset_{}.yar'.format(imp)).open('r') as fh: inputString = fh.read() results = Plyara().parse_string(inputString) for rule in results: self.assertEqual(detect_imports(rule), [imp]) class TestRuleParser(unittest.TestCase): def setUp(self): self.parser = Plyara() def test_import_pe(self): with data_dir.joinpath('import_ruleset_pe.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('pe', rule['imports']) def test_import_elf(self): with data_dir.joinpath('import_ruleset_elf.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('elf', rule['imports']) def test_import_cuckoo(self): with data_dir.joinpath('import_ruleset_cuckoo.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('cuckoo', rule['imports']) def test_import_magic(self): with data_dir.joinpath('import_ruleset_magic.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('magic', rule['imports']) def test_import_hash(self): with data_dir.joinpath('import_ruleset_hash.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('hash', rule['imports']) def test_import_math(self): with data_dir.joinpath('import_ruleset_math.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('math', rule['imports']) def test_import_dotnet(self): with data_dir.joinpath('import_ruleset_dotnet.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('dotnet', rule['imports']) def test_import_androguard(self): with data_dir.joinpath('import_ruleset_androguard.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) for rule in result: self.assertIn('androguard', rule['imports']) def test_scopes(self): with data_dir.joinpath('scope_ruleset.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) 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(UNHANDLED_RULE_MSG.format(rulename)) def test_tags(self): with data_dir.joinpath('tag_ruleset.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) 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(UNHANDLED_RULE_MSG.format(rulename)) def test_metadata(self): with data_dir.joinpath('metadata_ruleset.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) 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), 1) self.assertEqual(kv_list[0][0], 'string_value') self.assertEqual(kv_list[0][1], 'String Metadata') 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) else: raise AssertionError(UNHANDLED_RULE_MSG.format(rulename)) def test_strings(self): with data_dir.joinpath('string_ruleset.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) 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(UNHANDLED_RULE_MSG.format(rulename)) 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_conditions(self): with data_dir.joinpath('condition_ruleset.yar').open('r') as fh: inputString = fh.read() # Just checking for parsing errors self.parser.parse_string(inputString) def test_include(self): with data_dir.joinpath('include_ruleset.yar').open('r') as fh: inputString = fh.read() result = self.parser.parse_string(inputString) 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): with data_dir.joinpath('test_rules_from_yara_project.yar').open('r') as fh: inputRules = fh.read() plyara = Plyara() output = plyara.parse_string(inputRules) self.assertEqual(len(output), 293) def test_multiple_threads(self): with data_dir.joinpath('test_rules_from_yara_project.yar').open('r') as fh: inputRules = fh.read() def parse_rules(rules): plyara = Plyara() return plyara.parse_string(inputRules) with concurrent.futures.ThreadPoolExecutor(max_workers=4) as e: futs = [e.submit(parse_rules, inputRules) 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 with data_dir.joinpath('test_ruleset_2_rules.yar').open('r') as fh: inputRules = fh.read() # parse the rules parser.parse_string(inputRules) # clear the parser's state parser.clear() # has lineno been reset self.assertEqual(parser.lexer.lineno, 1) # open a ruleset with one rule with data_dir.joinpath('test_ruleset_1_rule.yar').open('r') as fh: inputRules = fh.read() # parse the rules result = parser.parse_string(inputRules) # does the result contain just the rule from the second parse self.assertEqual(len(result), 1) self.assertEqual(result[0]['rule_name'], 'rule_one') class TestRuleParserKVMeta(unittest.TestCase): def setUp(self): self.parser = Plyara(meta_as_kv=True) def test_meta_kv(self): with data_dir.joinpath('metakv_test.yar').open('r') as fh: inputString = fh.read() 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(inputString) self.assertEqual(result[0]['metadata_kv'], reference1) self.assertEqual(result[1]['metadata_kv'], reference2) class TestYaraRules(unittest.TestCase): def test_multiple_rules(self): inputString = ''' 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(inputString) 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): inputStringNIS = 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(inputStringNIS) 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() result1 = plyara1.parse_string(input1) plyara2 = Plyara() 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): inputRule = 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(inputRule) self.assertEqual(len(result), 1) self.assertEqual(result[0]['rule_name'], 'testName') def test_store_raw(self): inputRule = 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(inputRule) 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): inputTags = 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(inputTags) 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): inputRules = 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(inputRules) for rule in result: rule_name = rule['rule_name'] if rule_name == 'thirteen': self.assertEqual(len(rule['metadata']), 3) def test_bytestring(self): inputRules = 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 } condition: any of them } ''' plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(len(result), 1) for rule in result: rule_name = rule['rule_name'] if rule_name == 'testName': self.assertEqual(len(rule['strings']), 12) 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) @staticmethod def test_nested_bytestring(): inputRules = r''' rule sample { strings: $ = { 4D 5A ( 90 ( 00 | 01 ) | 89 ) } condition: all of them } ''' plyara = Plyara() plyara.parse_string(inputRules) def test_bytestring_bad_jump(self): inputRules = r''' rule testName { strings: $a6 = { E2 34 A1 [6 - 4] FB } condition: any of them } ''' plyara = Plyara() with self.assertRaises(ParseValueError): plyara.parse_string(inputRules) def test_bytestring_bad_group(self): inputRules = r''' rule sample { strings: $ = { 4D 5A ( 90 ( 00 | 01 ) | 89 ) ) } condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseValueError): plyara.parse_string(inputRules) def test_bytestring_bad_hexchar(self): inputRules = r''' rule sample { strings: $ = { 4D 5X } condition: all of them } ''' plyara = Plyara() with self.assertRaises(ParseTypeError): plyara.parse_string(inputRules) def test_rexstring(self): inputRules = 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(inputRules) 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_string(self): inputRules = 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(inputRules) 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_plyara_script(self): test_file_path = data_dir.joinpath('test_file.txt') # Without logging with captured_output() as (out, err): main([str(test_file_path)]) output = out.getvalue() error = err.getvalue() output_hash = hashlib.sha256(output.encode()).hexdigest() self.assertTrue(output_hash in ['9d1991858f1b48b2485a9cb45692bc33c5228fb5acfa877a0d097b1db60052e3', '18569226a33c2f8f0c43dd0e034a6c05ea38f569adc3ca37d3c975be0d654f06']) self.assertEqual(error, str()) # With logging with captured_output() as (out, err): main(['--log', str(test_file_path)]) output = out.getvalue() error = err.getvalue() output_hash = hashlib.sha256(output.encode()).hexdigest() error_hash = hashlib.sha256(error.encode()).hexdigest() self.assertTrue(output_hash in ['9d1991858f1b48b2485a9cb45692bc33c5228fb5acfa877a0d097b1db60052e3', '18569226a33c2f8f0c43dd0e034a6c05ea38f569adc3ca37d3c975be0d654f06']) self.assertTrue(error_hash in ['4c303175e30f2257cc11ede86e08329815d2c06ada198e32055f0c88b73dda5a']) def test_raw_condition_contains_all_condition_text(self): inputRules = r''' rule testName {condition: any of them} ''' plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(result[0]['raw_condition'], 'condition: any of them') def test_raw_strings_contains_all_string_text(self): inputRules = r''' rule testName {strings: $a = "1" condition: true} ''' plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(result[0]['raw_strings'], 'strings: $a = "1" ') def test_raw_meta_contains_all_meta_text(self): inputRules = r''' rule testName {meta: author = "Test" condition: true} ''' plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(result[0]['raw_meta'], 'meta: author = "Test" ') # strings after meta inputRules = r''' rule testName {meta: author = "Test" strings: $a = "1"} ''' plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(result[0]['raw_meta'], 'meta: author = "Test" ') def test_parse_file_without_rules_returns_empty_list(self): inputRules = str() plyara = Plyara() result = plyara.parse_string(inputRules) self.assertEqual(result, list()) def test_lineno_incremented_by_newlines_in_bytestring(self): inputRules = 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 self.assertRaises(ParseTypeError): try: plyara.parse_string(inputRules) except ParseTypeError as e: self.assertEqual(7, e.lineno) raise e def test_lineno_incremented_by_windows_newlines_in_bytestring(self): with data_dir.joinpath('windows_newline_ruleset_with_error.yar').open('r') as fh: inputRules = fh.read() plyara = Plyara() with self.assertRaises(ParseTypeError): try: plyara.parse_string(inputRules) except ParseTypeError as e: self.assertEqual(6, e.lineno) raise e def test_lineno_incremented_by_windows_newlines_in_comment(self): with data_dir.joinpath('windows_newline_ruleset_comment.yar').open('r') as fh: inputRules = fh.read() plyara = Plyara() plyara.parse_string(inputRules) self.assertEqual(plyara.lexer.lineno, 13) def test_windows_CRNL(self): with open('tests/data/windows_newline_ruleset.yar', 'r') as fh: inputRules = fh.read() 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(inputRules) 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): with data_dir.joinpath('xor_modifier_ruleset.yar').open('r') as fh: inputRules = fh.read() plyara = Plyara() results = plyara.parse_string(inputRules) for res in results: yr_mods = res.get('strings')[0]['modifiers'] xor_string_mod = [x for x in yr_mods if isinstance(x, str) and 'xor' in x].pop() self.assertIn('xor', xor_string_mod) if '(' in xor_string_mod: self.assertIn('(0x10', xor_string_mod) def test_base64_modified_condition(self): with data_dir.joinpath('base64_modifier_ruleset.yar').open('r') as fh: inputRules = fh.read() plyara = Plyara() results = plyara.parse_string(inputRules) for res in results: yr_mods = res.get('strings')[0]['modifiers'] yr_base64_mods = [x.get('base64_mod', None) for x in yr_mods if isinstance(x, dict)] yr_base64_mods.extend([x.get('base64wide_mod', None) for x in yr_mods if isinstance(x, dict)]) yr_string_mod0 = [x for x in yr_mods if isinstance(x, str) and x.startswith('base64')][0] self.assertEqual('base64', yr_string_mod0[:6]) for yr_base64_mod in yr_base64_mods: if not yr_base64_mod: continue self.assertEqual(yr_base64_mod, r"!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") class TestGithubIssues(unittest.TestCase): # Reference: https://github.com/plyara/plyara/issues/63 def issue_63(self): with data_dir.joinpath('comment_only.yar').open('r') as fh: inputString = fh.read() plyara = Plyara() result = plyara.parse_string(inputString) self.assertEqual(result, list()) # Reference: https://github.com/plyara/plyara/issues/99 def issue_99(self): rules = list() plyara = Plyara() for file in data_dir.glob('issue99*.yar'): with open(file, 'r') as fh: yararules = plyara.parse_string(fh.read()) self.assertEqual(len(yararules), 1) rules += yararules plyara.clear() self.assertEqual(len(rules), 2) # Reference: https://github.com/plyara/plyara/issues/107 def issue_107(self): with data_dir.joinpath('issue107.yar').open('r') as fh: inputString = fh.read() plyara = Plyara() result = plyara.parse_string(inputString) expected = ['(', '#TEST1', '>', '5', ')', 'and', '(', '#test2', '>', '5', ')'] self.assertEqual(result.rules[0]['condition_terms'], expected) if __name__ == '__main__': unittest.main() plyara-2.1.1/tests/data/0000755000175000017500000000000013711334624014430 5ustar rhaistrhaistplyara-2.1.1/tests/data/import_ruleset_elf.yar0000644000175000017500000000035113711334624021047 0ustar rhaistrhaist// 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.1.1/tests/data/base64_modifier_ruleset.yar0000644000175000017500000000114013711334624021646 0ustar rhaistrhaist// 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.1.1/tests/data/test_ruleset_2_rules.yar0000644000175000017500000000040013711334624021314 0ustar rhaistrhaist// 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.1.1/tests/data/detect_dependencies_ruleset.yar0000644000175000017500000000526013711334624022671 0ustar rhaistrhaist/* 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]+/ } plyara-2.1.1/tests/data/import_ruleset_magic.yar0000644000175000017500000000037113711334624021363 0ustar rhaistrhaist// 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.1.1/tests/data/rulehashes_v2.0.0.txt0000644000175000017500000004514513711334624020250 0ustar rhaistrhaistb5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 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 c5b0a508548f28a6bdb4ae81b113e824af800a1c9e40f7b3cebe2033974f80c0 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 321ec9ac18d58d3d2b25fcf306a08bae799e59d35f31773dd96323656e80d7d9 79535bcef9252a3f988515bd33a963ff5db119b3b42c6c6b263f60fa6bf0fff7 a8c95988f4ee0d3a8347bc71347d8d3e0144663034ffd4ce3a61c96c7378c247 61507c8074e56e0bb9dc6e17b87dfc0f27cddec39f8ee5fea66da282af5dbd7f ff0159a9e24f5bed4486fc206b87e6861ace224b69cbb2a15a3660d6c1c78ab2 7311ff08867cfc0c21f65fde14e2d74cb1ec4a530004e7e1134fa638a6885aad 3914181944d4eb8e612fdabf962234680acfffb9779a00aadf0fbf3cd14b6dbd 97563d55ed98cb23bda5abb17d9c2c3093f422d400255e7c99f2376713d66a9d 87624c65c6f419ab8e73a86e0a922de1767b1aa05e113fb38acd4583c3b55161 2ec61650d214cef487df3d11d8a959e339947fe7047fcfadebca1af9348a2d1d 7068ddfa0218c6ecdb0bc16d49c98094bbaae0dcb59e52c2527059cdff3916de 79088de2a8f5f9d8dc7e9abb7da7ea07594918f69becf82de09fd7ef8f91f178 a2aabe8079f2639ea09c9640e4dc94e1039042a4573c4070ff68e72d90f1630c 67e68d2f1674a2bd02cef3f83f535712832176da4839d9d9e56debf5d6e7737b d73242285645e991e55039628c1d3876b3b1abca808a0e5704bae11e42997069 6384018af7c8859a954eb3d3cb8ac10e4c6c419a2aff5c9394a5742f0feb42c4 437e4c099c0e3d2debdeee79eb82d82e78e956545d13e8857ae5ea261e14e490 eac5441aabadcd3899fe0ddb640f88de58bc49b2c8b319e2808716a256192bf8 88e84856df54e85c6287784865809ac1ad635e1b710997ab19333cd44baf3ffe 63386b4232419c5d7db9f02b7f81bb59924bf55743aa3838d63e3e7c767b9346 b1c3fd978bf1790db220df34e55bcb5a390f0232acda571a33cbe4fcb4306801 718c48f1ade4c467cdd16e63f3cc81e484842ec45b6aa77edf9c1355136ef4bd 36e8425d7c36b8dda4e6ac4b03050f060a1854bc888d4c17a7bdafea1c5c8dc5 6377bd86000b35ecb0d939fb5711c257b871dbd55d5472918404dd01575306d2 2f5cad1857f3320f193137427276c07e99fb7459bb3957782bf4b7a82bd65102 16f732af664af14ac8ab4dd398aab44943a59d91e6ab2ae320fa6e5a73dd13b6 01f4621f2c5b5269f3644c150eb89191759f201a47fc443e9acbe7a805e5f7db 7897117d4ebaf57077b25f9107b3d244f48c22df40940a99d39ed53d601d81c2 7897117d4ebaf57077b25f9107b3d244f48c22df40940a99d39ed53d601d81c2 6978ec7145806c870fc17164fe163af9682a36d22f6684f115781819aac8229c 2c6cbf5096a2ace88525d926776fe6c294bc5a8d47d9c64faf31a73109db4722 effdfc0d750450666e8fc5200b807219a7086d068b1f85ddf2953cf2ab539f79 0985616fbf23e37981bdca3eab65f29fcd75d5b4a6b9592a86a6e4e98b3174b9 a1f0e1d30fb43a5a3721ecb4470ea3400337002b7961c68e920972407f84cdfb 73d29ae7cde57a1b04cf639b144477c11e46bbef9998c9f1932dea9a1a80af4d 173212a17efbd33ead807fc464742f674692b6d297c1851d4f3439ce944b1564 b599dcdf28e0d614aec914d8a8c0ccfb559045f0d029ee1cd72886a159497901 866f50777c5967e2055445dd5ea3d3fb5707419f5d46bb7c2af9a8cb95f4b9fe 00567f292137497bc2b9c234b8e80d53aa9ae6e549c03ed3b6dfd71d684747b3 000fd941376b243d4ce19839aa6e0798c610da99c47bc0cbbea3a81b967a4ace 141b164cab2d321f3f07a1e2ea68bc04bf9073aa5c2ad701c212e415eb2dc65c 77ae236316021cf25914361fbb16ff4d3f24951edebd6cf6aa652c26654d1d69 85a90b1be58158aef163390b5b1abfcc11bda1c64ce379d0ca8759a9cd776787 83c516b4501d8f2f53bdaf71e3df3b7a2818574b526530ee569bb0d27087d391 3b24d472de4c339a5b330c3cb271d186702f22379c5cfefb9c6bffb8b5b03785 2f658c197bce380fc4b9d3c1657e5175c0daef2f678f30ce3696b6cc84d339f2 3861bbee0a10b02efa3dd1f4916c428fd23aa5a913fc15d1e8a3017d31d11e28 9ef48bf75666e9dfdf4904921c8a357fafc391faf69de1a598caeee2c8b16825 0e461d65857aea54fadf627c5cff83533785064c82eeefda8a1289fea68a406d ba66129d335b5ee04e50fe4a6cadab907ee876f537f16dfe364d06e8b597844a 3c38daa7b6b52345994a3c57e8596eabea74ef7709b988c1d3c20a261887e163 10badf3477c0b3061f46e90f667816039163be301d4ca93756a0988e5c310efb 684d1218ff3d6a57317277a3d4281a10432fb1cab074395c469901275735ed71 9ef48bf75666e9dfdf4904921c8a357fafc391faf69de1a598caeee2c8b16825 9ef48bf75666e9dfdf4904921c8a357fafc391faf69de1a598caeee2c8b16825 d4550a3c660a96f9a788b9fa9c8df2403b044741f5be3da4ac2409f69e67cf4a d4550a3c660a96f9a788b9fa9c8df2403b044741f5be3da4ac2409f69e67cf4a 268374581666c0a7b3a9482892f15cb0500a5e48592b2f116cc03655ced6a782 a4fd473a0216048ffc7c21d71c0d6e72305c03f90682b303f4b1ac232b2215c0 cf035fdf5d441e3d5d4dc56557c96dd7cd1c75e01c98bf91736c7cbc4569aab5 e7c1f9c8fcb2d700334ad4233ddbe938a722a9db34817fa30f17d392d1feeac6 5bf4290172017a28c9bad170d64e194281e166afb24cd9940ebc3f5d248b002c b1ee98c789583fbf8b5a0fe969b490425446a491e3dcf4b1df7af455dbe5261c 80647734fab6b68255f13bb4ff2547138dbe3337d28dafa0303ec1f07049781f e31ed1540da07094fad4e4ccf51be251e0fe2d8c95995131b0de6c4f09f950f5 230a9f6947d31c7477a7eb441ed9d551709b2afee0c6b0f46be04147e248d5e3 d1b3bf70935250b7551d15ff12dc799166e6cc2a7cf26f0e6c90fd01225035e1 a063a5852652ce4303c6ac4e0db71aa1fc28e06276f2945edbe0eca482892301 2253d4c27f53a5376da2f59beb5268d8557b9ed80e258fde0406263af17f4b90 ea5534cbedbf39fb4b0d025a2cc1b2eac486d8139616eef224946a1265765a53 7daafcb0a11658fb619fc091fb7a9ddc8896e2275bf4c7389bfc1763ec2092a8 227f854c373fd03ad8c9ba9eb6492c012e7b73c81da20c1a32b3d958f765627b 7c16dc84fb0976c8a4a1f0d4d0709d778fc03e0d7bdb18086b2945e5aac9fdd5 393cae7c30d75c6b2636bf7ed4ecda0f56c23ba0a3b662b3419b1e69aeb3c3e8 cb0640a3c09db87889bdcb3fa96dbb2906e685bf04488501b0b90f977b63f72b 83acd4899678786dbbf0873134f7ddde8594ccdf2779727fe3786c041976ac58 a9528ef5de417405b51299592fa4d6ca5ce82ca4eb2ca6cee53dcce7f1924d45 b3f685e297ed0f1ca40c56af73a404390d0a696e2795b8ae9f689d4a64bb6a05 34a1a7ddb9305549582f643a8d52dcd8749ebb80e8aba7f65d725252dc197afb dfb967a004d4406ca866b81d4e20ea0e0d4052f4be24f530f28316c0c92e0890 c2017f3c2c3ee624c3cac69a0f7b4c395403a03ac3c31ef8e12ada0db3cbc07b 00a3df13f1738e43765dd7bf0b267938cd5f4ae7731c71cd1dce22449ea4aacc 866f50777c5967e2055445dd5ea3d3fb5707419f5d46bb7c2af9a8cb95f4b9fe ba66129d335b5ee04e50fe4a6cadab907ee876f537f16dfe364d06e8b597844a 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 84f26476b729bca519d3377ddfcac1d39ef452dee83c812cd4a8b606fab28f03 5b5218dc5e3a8deb146183952fd468495f7d805e7b2e80db046f59221da79209 32a5cdd4b1f10f63257863591c07fe4f1b3e2dee4eada109bb7a6882ae03d8c3 647fe40400cf861e6bff4c5d2456aca94c118d6aad82c2f3e1a35339b7807661 9bbe09f5cf618f97a90e8ab1c481e320e9467f7133fd7d9e488aac1988f664d9 7725771151d04ff2a1220b0b894da0a75b879a2a2b14610c1c386d82f3f06778 543228495ffa272921c5c0b0f3954a59aa172f3a1da885aa5923eb41b98ba21d 0a87829d3c185186b86409f94105c1e934c18d0ab3c7075e97e6c517591d264e 6c1b9842ffb35a63d75a69a04108bf981556f5aec700210d11b16ecbe17e67f7 ac9a31d1154848e295e2e84981aca08af87c7ba314bff33e637d634085edfd4d 15313b49f2ba25f02d15f0923cc3b87751956b9722b001d809413ca328cdc872 7e630d99a4428479116d0adc203ad2154c4c8f70f5cd5e09188caea9d3fa5c9c 801e9e61c91b95c3295b83e2bf553b929d1710116608f40dbfddc317135c4b13 f306468e7781bbbfdf50f3ac1906118fe4a21aa9c0049b94845c497640da5de9 f306468e7781bbbfdf50f3ac1906118fe4a21aa9c0049b94845c497640da5de9 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 9ac915fbb2a19b0a642525f09b9949aef69831d3780460c6b4bdcd2744f9a048 347d415e2689fe86aecd739e8f800fdb6f2aa8c8d63399d2707ebe7ca4202b36 1799fdbf703258f99a9d1b368b6d6b0d787e1cb78bf2967a20cb0adf145ed25d 2308c0bfdfa71a71591108bb40932fb2870f8c8404ca2f0ccb1f96e86952558e 5898ab0ddaeedef07afb632368a41f8cbd4b8de37e6b68e4310cbcaa53441fc1 bdf5744eb35cecada1527ad94901190cc2c9c46afe08f3e55a9af322e15ff08f b7f14ae60f237c23cfd75e973bf6b61a779df6f4c5c48ee398e8f0054415819e dad7f8157bc5a03cbc05b18b4acd36565b67d8af5c43a0c423f9ed0f9c985bcd 861882ea647abc9efda7a14e4308761dbc5586363b5328ebacbaf1ba930f3a43 e68fd71fc673469f511da3c27aa1c08b174547ae90042dcaf59d1d710802f476 5d1f712d0942750a6d6cdb802ee681c85ff9ee81eb1fa2d24ac0317975ce027c add3fe697c5bdff3ef8123476ba1dd4146961798295ed91f53234e3f78f546dc 19dc5f6aaf1b563bf634320ee18fbed35bf791f452d4038a677fa3c877148cf2 9e6f6f329f7028755324da253dd8e1b786c20f0b4c22339c3cb02fc10d733373 6646b8166a297404b5bc8deb098c01bc11fbf7230983fff0da4d2c43650e6528 d45e660f108ca2f9cd24f2f569e6ff5e264cf603a74bcfb9ddc9073a3f790748 6f11883a2871690c650d8a4b23c261390389b58382996880f734407edda671ed 937e0d2c2d0e762a5806f96c75766cc66fff8b85bc1ddf2d4d3ad075ee12346c d4efc2020d74816eef5e59faedee11bf9e7c712fc6f0f148f9ffdffe2022dba6 5c71bdf46c8ed0de55aba4ba871df28d969a3cdd082134ccc069e264b675c183 c23bd22368d03adc58de77e5e2e24151a02c72749f1bd26e15fe55028c03a908 5cf2a1e47bf3d8234d3530ba77b0ccf4e40d1fb6264bcc7f81f04701ded530cc b56d737653140daffcd11f0d97397816f2e389e1a5c92ea36f8da5f0defafef8 caabb09e779f9c79723fc3439f70f3c69f5372cdc648c6c5f1dca48023692d30 05cf61808c189c20669e597580449198f6c7521a82684267aede755068693423 d555878089f04bf2e738c5d6fc1f046e1e17ac7ec6fb66d04a3a9f8118682ca2 plyara-2.1.1/tests/data/test_ruleset_1_rule.yar0000644000175000017500000000024613711334624021140 0ustar rhaistrhaist// This ruleset is used for unit tests - Modification will require test updates rule rule_one { strings: $a = "one" condition: all of them } plyara-2.1.1/tests/data/windows_newline_ruleset_with_error.yar0000644000175000017500000000011313711334624024362 0ustar rhaistrhaistrule sample { strings: $ = { 00 00 } conditio: all of them } plyara-2.1.1/tests/data/condition_ruleset.yar0000644000175000017500000000061013711334624020673 0ustar rhaistrhaistrule 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.1.1/tests/data/import_ruleset_pe.yar0000644000175000017500000000044413711334624020710 0ustar rhaistrhaist// 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.1.1/tests/data/string_ruleset.yar0000644000175000017500000000434213711334624020221 0ustar rhaistrhaist// 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.1.1/tests/data/include_ruleset.yar0000644000175000017500000000016713711334624020337 0ustar rhaistrhaistinclude "string_ruleset.yar" rule includerule { strings: $a = "1aosidjoasidjaosidj" condition: all of them } plyara-2.1.1/tests/data/bad_condition_string.yar0000644000175000017500000000010313711334624021321 0ustar rhaistrhaistrule sample { strings: $ = { 00 00 } conditio: all of them } plyara-2.1.1/tests/data/metakv_test.yar0000644000175000017500000000102313711334624017467 0ustar rhaistrhaistimport "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.1.1/tests/data/import_ruleset_cuckoo.yar0000644000175000017500000000031513711334624021564 0ustar rhaistrhaist// 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.1.1/tests/data/xor_modifier_ruleset.yar0000644000175000017500000000127213711334624021400 0ustar rhaistrhaistrule 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_range { strings: $a = "one" xor( 16 - 0x80 ) condition: all of them } plyara-2.1.1/tests/data/comment_only.yar0000644000175000017500000000004313711334624017645 0ustar rhaistrhaist// This rule file has been deleted plyara-2.1.1/tests/data/issue99_2.yar0000644000175000017500000000005413711334624016677 0ustar rhaistrhaistrule test2 { condition: false } plyara-2.1.1/tests/data/metadata_ruleset.yar0000644000175000017500000000102213711334624020463 0ustar rhaistrhaist// This ruleset is used for unit tests - Modification will require test updates rule StringTypeMetadata { meta: string_value = "String Metadata" 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 } plyara-2.1.1/tests/data/import_ruleset_hash.yar0000644000175000017500000000044713711334624021232 0ustar rhaistrhaist// 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.1.1/tests/data/tag_ruleset.yar0000644000175000017500000000035113711334624017462 0ustar rhaistrhaist// 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.1.1/tests/data/windows_newline_ruleset_comment.yar0000644000175000017500000000015113711334624023642 0ustar rhaistrhaist/* Multiline comment */ rule sample { strings: $ = { 00 00 } condition: all of them } plyara-2.1.1/tests/data/import_ruleset_math.yar0000644000175000017500000000030113711334624021225 0ustar rhaistrhaist// 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.1.1/tests/data/test_rules_from_yara_project.yar0000644000175000017500000005416413711334624023135 0ustar rhaistrhaist/* 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.1.1/tests/data/rulehashes.txt0000644000175000017500000004514513711334624017345 0ustar rhaistrhaistb5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 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 8d8937a492ee8bf6b7752acf833f040f5d0df54f25416c6e34f55ac034243c50 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 ff34ea0308125bd1d9691f054c32c6d7b686ed63538d0b9b10e3859a45e2ea83 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.1.1/tests/data/rebuild_ruleset.yar0000644000175000017500000000050713711334624020340 0ustar rhaistrhaistrule 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)^unit8(1)==0x12 } plyara-2.1.1/tests/data/scope_ruleset.yar0000644000175000017500000000036713711334624020027 0ustar rhaistrhaist// 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.1.1/tests/data/logic_collision_ruleset_v2.0.0.yar0000644000175000017500000000154613711334624022771 0ustar rhaistrhaist// This ruleset is used for unit tests - Modification will require test updates rule Set001_Rule001 { strings: $a = "foobar" condition: $a } rule Set001_Rule002 { strings: $b = "foobar" condition: $b } rule Set001_Rule003 { strings: $aaa = "foobar" condition: $* } rule Set001_Rule004 { strings: $ = "foobar" condition: $* } 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.1.1/tests/data/logic_collision_ruleset.yar0000644000175000017500000000234513711334624022064 0ustar rhaistrhaist// 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.1.1/tests/data/issue107.yar0000644000175000017500000000020513711334624016522 0ustar rhaistrhaistrule test{ strings: $TEST1 = "testy" $test2 = "tasty" condition: ( #TEST1 > 5 ) and ( #test2 > 5 ) } plyara-2.1.1/tests/data/import_ruleset_androguard.yar0000644000175000017500000000050413711334624022427 0ustar rhaistrhaist// 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.1.1/tests/data/windows_newline_ruleset.yar0000644000175000017500000000011413711334624022117 0ustar rhaistrhaistrule sample { strings: $ = { 00 00 } condition: all of them } plyara-2.1.1/tests/data/import_ruleset_dotnet.yar0000644000175000017500000000046713711334624021606 0ustar rhaistrhaist// 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.1.1/tests/data/test_file.txt0000644000175000017500000000115613711334624017152 0ustar rhaistrhaistrule 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.1.1/tests/data/issue99_1.yar0000644000175000017500000000005313711334624016675 0ustar rhaistrhaistrule test1 { condition: true } plyara-2.1.1/tests/data/string_exclusive_modifiers.yar0000644000175000017500000000243213711334624022604 0ustar rhaistrhaistrule duplicate_modifier { strings: $a = "one" xor xor condition: all of them } rule invalid_xor_modifier { strings: $a = /AA/ xor(500) condition: all of them } rule base64_error_nocase { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") nocase $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") nocase condition: all of them } rule base64_error_xor { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") xor $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") xor condition: all of them } rule base64_error_xor { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstuxyz") xor $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstuxyz") xor condition: all of them } rule base64_error_xor { strings: $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZ") xor $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZ") xor condition: all of them } plyara-2.1.1/tests/data/mixed_ruleset.yar0000644000175000017500000000047413711334624020023 0ustar rhaistrhaist// This ruleset is used for unit tests - Modification will require test updates 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 } plyara-2.1.1/.gitignore0000644000175000017500000000612413711334624014350 0ustar rhaistrhaist## 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/ *.egg-info/ .installed.cfg *.egg # 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/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ ## Sublime Text Section # Cache files for Sublime Text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # Workspace files are user-specific *.sublime-workspace # Project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using Sublime Text *.sublime-project # sftp configuration file sftp-config.json # Package control specific files Package Control.last-run Package Control.ca-list Package Control.ca-bundle Package Control.system-ca-bundle Package Control.cache/ Package Control.ca-certs/ Package Control.merged-ca-bundle Package Control.user-ca-bundle oscrypto-ca-bundle.crt bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github GitHub.sublime-settings ## Archive Section # It's better to unpack these files and commit the raw source because # git has its own built in compression methods. *.7z *.jar *.rar *.zip *.gz *.tgz *.bzip *.bz2 *.xz *.lzma *.cab # Packing-only formats *.iso *.tar # Package management formats *.dmg *.xpi *.gem *.egg *.deb *.rpm *.msi *.msm *.msp ## Vagrant Section .vagrant/ ## Vi Section # Swap [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-v][a-z] [._]sw[a-p] # Session Session.vim # Temporary .netrwhist *~ # Auto-generated tag files tags ## Virtualenv Section # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ .Python #[Bb]in [Ii]nclude [Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json ## 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 ## Local plyara Section parsetab.py test.yar test.py plyara-2.1.1/setup.cfg0000644000175000017500000000004213711334624014172 0ustar rhaistrhaist[pycodestyle] max-line-length=120 plyara-2.1.1/setup.py0000755000175000017500000000414713711334624014100 0ustar rhaistrhaist#!/usr/bin/env python3 # Copyright 2014 Christian Buia # Copyright 2020 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. """A setuptools based setup module. See: https://packaging.python.org/guides/distributing-packages-using-setuptools/ https://github.com/pypa/sampleproject """ import pathlib from setuptools import find_packages, setup here = pathlib.Path().cwd() # Get the long description from the README file with here.joinpath('README.rst').open(encoding='utf-8') as fh: long_description = fh.read() setup( name='plyara', version='2.1.1', description='Parse YARA rules.', long_description=long_description, url='https://github.com/plyara/plyara', author='plyara Maintainers', license='Apache License 2.0', test_suite='tests.unit_tests', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Software Development :: Build Tools', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', ], keywords='malware analysis yara', packages=find_packages(exclude=['docs', 'examples', 'tests']), install_requires=[ 'ply>=3.11' ], entry_points={ 'console_scripts': [ 'plyara=plyara.command_line:main', ], }, project_urls={ 'Bug Reports': 'https://github.com/plyara/plyara/issues', 'Source': 'https://github.com/plyara/plyara', }, ) plyara-2.1.1/.readthedocs.yml0000644000175000017500000000011013711334624015433 0ustar rhaistrhaistbuild: image: latest python: version: 3.6 setup_py_install: true plyara-2.1.1/README.rst0000644000175000017500000001542113711334624014047 0ustar rhaistrhaistplyara ====== .. image:: https://travis-ci.com/plyara/plyara.svg?branch=master :target: https://travis-ci.com/plyara/plyara :alt: Build Status .. image:: https://readthedocs.org/projects/plyara/badge/?version=latest :target: http://plyara.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://api.codacy.com/project/badge/Grade/7bd0be1749804f0a8dd3d57f69888f68 :target: https://www.codacy.com/app/plyara/plyara :alt: Code Health .. image:: https://api.codacy.com/project/badge/Coverage/1c234b3d1ff349fa9dea7b4048dbc115 :target: https://app.codacy.com/app/plyara/plyara :alt: Test Coverage .. image:: http://img.shields.io/pypi/v/plyara.svg :target: https://pypi.python.org/pypi/plyara :alt: PyPi Version Parse 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_ for lexing YARA rules. This is a community-maintained fork of the `original plyara`_ by 8u1a_. The "plyara" trademark is used with permission. Installation ------------ Plyara requires Python 3.6+. Install with pip:: pip3 install plyara Usage ----- Use the plyara Python library in your own applications: .. code-block:: 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:: $ 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:: $ cat example.yar rule silent_banker : banker { meta: description = "This is just an example" thread_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 } $ plyara example.yar [ { "condition_terms": [ "$a", "or", "$b", "or", "$c" ], "metadata": [ { "description": "This is just an example" }, { "thread_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 thread_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" ] } ] 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. .. code-block:: 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() Migration --------- If you used an older version of plyara, and want to migrate to this version, there will be some changes required. Most importantly, the parser object instantiation has changed. It was: .. code-block:: python # Old style - don't do this! import plyara.interp as interp rules_list = interp.parseString(open('myfile.yar').read()) But is now: .. code-block:: python # New style - do this instead! import plyara parser = plyara.Plyara() rules_list = parser.parse_string(open('myfile.yar').read()) The existing parsed keys have stayed the same, and new ones have been added. When reusing a ``parser`` for multiple rules and/or files, 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. Contributing ------------ * If you find a bug, or would like to see a new feature, Pull Requests and Issues_ are always welcome. * By submitting changes, you agree to release those changes under the terms of the 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, and pyflakes. See the .travis.yml file for exact use. For more information on these linters, please refer to the Python Code Quality Authority: http://meta.pycqa.org/en/latest/ Discussion ------------ * You may join our IRC channel on irc.freenode.net #plyara .. _PLY: http://www.dabeaz.com/ply/ .. _YARA: http://plusvic.github.io/yara/ .. _plyara.readthedocs.io: https://plyara.readthedocs.io/en/latest/ .. _original plyara: https://github.com/8u1a/plyara .. _8u1a: https://github.com/8u1a .. _Issues: https://github.com/plyara/plyara/issues .. _LICENSE: https://github.com/plyara/plyara/blob/master/LICENSE plyara-2.1.1/requirements-dev.txt0000644000175000017500000000005613711334624016416 0ustar rhaistrhaistnose coverage pycodestyle pydocstyle pyflakes plyara-2.1.1/.travis.yml0000644000175000017500000000214413711334624014467 0ustar rhaistrhaistlanguage: python python: - "3.6.11" - "3.7.8" - "3.8.3" sudo: required dist: focal install: - "pip install nose" - "pip install pycodestyle pydocstyle pyflakes" - "pip install coverage" - "pip install codacy-coverage" - "pip install collective.checkdocs Pygments" - "python setup.py install" script: - nosetests --with-coverage --cover-package=plyara --cover-xml - pycodestyle plyara/*.py - pycodestyle setup.py - pycodestyle tests/unit_tests.py - pydocstyle --ignore=D104 plyara/__init__.py - pydocstyle --ignore=D203,D205,D208,D209,D213,D300,D400,D401,D403,D406,D407,D413,D415 plyara/core.py - pydocstyle --ignore=D203,D213 plyara/exceptions.py - pydocstyle --ignore=D203,D213,D406,D407,D413 plyara/utils.py - pydocstyle --ignore=D101,D102,D203,D213 tests/unit_tests.py - pydocstyle plyara/command_line.py - pydocstyle setup.py - pyflakes plyara/core.py - pyflakes plyara/exceptions.py - pyflakes plyara/command_line.py - pyflakes plyara/utils.py - pyflakes setup.py - pyflakes tests/unit_tests.py - python setup.py checkdocs after_success: - python-codacy-coverage plyara-2.1.1/plyara/0000755000175000017500000000000013711334624013645 5ustar rhaistrhaistplyara-2.1.1/plyara/utils.py0000755000175000017500000004172213711334624015370 0ustar rhaistrhaist#!/usr/bin/env python3 # Copyright 2014 Christian Buia # Copyright 2020 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 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 """ # 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 """ # 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. """ 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. """ 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', ) or next_term in ('and', 'or', ): dependencies.append(term) return dependencies def generate_logic_hash(rule): """Calculate hash value of rule strings and condition. Args: rule: Dict output from a parsed rule. Returns: str: Hexdigest SHA-256. """ import warnings warnings.warn( 'Utility generate_logic_hash() may be deprecated, see generate_hash()', PendingDeprecationWarning ) 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()) # Handle string modifiers if modifiers: value = '{}{}'.format(entry['value'], ' & '.join(sorted(modifiers))) else: value = entry['value'] 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 sorted_string_values = sorted(string_values) for condition in conditions: # All string references (sort for consistency) if condition == 'them' or condition == '$*': condition_mapping.append('{}'.format(' | '.join(sorted_string_values))) elif condition.startswith('$') and condition != '$': # Exact Match if condition in string_mapping['named']: condition_mapping.append('{}'.format(string_mapping['named'][condition])) # Wildcard Match elif '*' in condition: wildcard_strings = list() condition = condition.replace('$', r'\$').replace('*', '.*') pattern = re.compile(condition) 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: logger.error('[!] Unhandled String Condition {}'.format(condition)) # Count Match elif condition.startswith('#') and condition != '#': condition = condition.replace('#', '$') if condition in string_mapping['named']: condition_mapping.append('{}'.format(string_mapping['named'][condition])) else: logger.error('[!] Unhandled String Count Condition {}'.format(condition)) else: condition_mapping.append(condition) logic_hash = hashlib.sha256(''.join(condition_mapping).encode()).hexdigest() return logic_hash def generate_hash(rule, secure_hash=None): """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. secure_hash: Alternate hash function, defaults to SHA-256 Returns: str: hexdigest """ condition_string_prefaces = ('$', '!', '#', '@') if secure_hash is None: hf = hashlib.sha256() else: hf = secure_hash() 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 condition in conditions: # All string references (sort for consistency) if condition == 'them' or condition == '$*': all_values = '{}'.format(' | '.join(string_values)) if condition == 'them': condition_mapping.extend(['(', all_values, ')']) else: condition_mapping.append(all_values) elif condition.startswith('$') and condition != '$': # Exact Match if condition in string_mapping['named']: condition_mapping.append('{}'.format(string_mapping['named'][condition])) # Wildcard Match elif '*' in condition: wildcard_strings = list() condition = condition.replace('$', r'\$').replace('*', '.*') pattern = re.compile(condition) 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: logger.error('[!] Unhandled String Condition "{}" in "{}"'.format(condition, ' '.join(conditions))) # Count Match elif condition[:1] in condition_string_prefaces and condition not in ('#', '!='): symbol = condition[:1] condition = '${}'.format(condition[1:]) if symbol == '#': symbol_type = 'COUNTOFSTRING' elif symbol == '@': symbol_type = 'POSITIONOFSTRING' elif symbol == '!': symbol_type = 'LENGTHOFSTRING' elif symbol == condition == '$': symbol_type = 'ANONYMOUSSTRING' else: symbol_type = 'UNKNOWN' if condition in string_mapping['named']: condition_mapping.append('<{}>{}'.format(symbol_type, string_mapping['named'][condition])) else: condition_mapping.append('<{}>{}'.format(symbol_type, condition)) logger.error('[!] Unhandled {} Condition "{}" in "{}"'.format( symbol_type, symbol, ' '.join(conditions)) ) else: condition_mapping.append(condition) hf.update(''.join(condition_mapping).encode()) hexdigest = 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) 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.1.1/plyara/__init__.py0000755000175000017500000000125413711334624015763 0ustar rhaistrhaist#!/usr/bin/env python3 # Copyright 2014 Christian Buia # Copyright 2020 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. __all__ = ['Plyara'] from plyara.core import * plyara-2.1.1/plyara/core.py0000755000175000017500000011411413711334624015154 0ustar rhaistrhaist#!/usr/bin/env python # Copyright 2014 Christian Buia # Copyright 2020 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 string import tempfile import re 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.""" EXCLUSIVE_TEXT_MODIFIERS = {'nocase', 'xor', 'base64'} COMPARISON_OPERATORS = {'==', '!=', '>', '<', '>=', '<='} IMPORT_OPTIONS = {'pe', 'elf', 'cuckoo', 'magic', 'hash', 'math', 'dotnet', 'androguard'} KEYWORDS = {'all', 'and', 'any', 'ascii', 'at', 'condition', 'contains', 'entrypoint', 'false', 'filesize', 'fullword', 'for', 'global', 'in', 'import', 'include', 'int8', 'int16', 'int32', 'int8be', 'int16be', 'int32be', 'matches', 'meta', 'nocase', 'not', 'or', 'of', 'private', 'rule', 'strings', 'them', 'true', 'uint8', 'uint16', 'uint32', 'uint8be', 'uint16be', 'uint32be', 'wide', 'xor', 'base64', 'base64wide'} FUNCTION_KEYWORDS = {'uint8', 'uint16', 'uint32', 'uint8be', 'uint16be', 'uint32be'} def __init__(self, console_logging=False, store_raw_sections=True, meta_as_kv=False): """Initialize the parser object. Args: console_logging: Enable a stream handler if no handlers exist. (default False) 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) """ self.rules = list() self.current_rule = dict() self.string_modifiers = list() self.imports = set() self.includes = list() self.terms = list() self.scopes = list() self.tags = list() self.comments = list() if console_logging: self._set_logging() # 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 self.lexer = lex.lex(module=self, debug=False) self.parser = yacc.yacc(module=self, debug=False, outputdir=tempfile.gettempdir()) 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() if self.lexer.lineno > 1: # Per https://ply.readthedocs.io/en/latest/ply.html#panic-mode-recovery # This discards the entire parsing stack and resets the parser to its # initial state. self.parser.restart() # Per https://ply.readthedocs.io/en/latest/ply.html#eof-handling # Be aware that setting more input with the self.lexer.input() method # does NOT reset the lexer state or the lineno attribute used for # position tracking. self.lexer.lineno = 1 @staticmethod def _set_logging(): """Set the console logger only if handler(s) aren't already set.""" if not len(logger.handlers): logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) logger.addHandler(ch) 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'] = self.string_modifiers self.string_modifiers = list() 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: self.string_modifiers.append(element_value) 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) elif element_type == ElementTypes.MCOMMENT: 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 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): rule['imports'] = list(self.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 = {'"', '\\', 't', 'n', 'x'} tokens = [ 'BYTESTRING', 'STRING', 'REXSTRING', 'EQUALS', 'STRINGNAME', 'STRINGNAME_ARRAY', 'STRINGNAME_COUNT', 'STRINGNAME_LENGTH', 'LPAREN', 'RPAREN', 'LBRACK', 'RBRACK', 'LBRACE', 'RBRACE', 'ID', 'BACKSLASH', 'FORWARDSLASH', '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', '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', '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' } 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_FORWARDSLASH = 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'}' t.value = t.value self._condition_end = t.lexpos return t @staticmethod def t_NEWLINE(t): r'(\n|\r\n)+' t.lexer.lineno += len(t.value) t.value = t.value @staticmethod def t_COMMENT(t): r'(//[^\n]*)' return t @staticmethod def t_MCOMMENT(t): r'/\*(.|\n|\r\n)*?\*/' 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]+' t.value = t.value return t def t_SECTIONMETA(self, t): r'meta\s*:' t.value = t.value self._meta_start = t.lexpos t.lexer.section = 'meta' return t def t_SECTIONSTRINGS(self, t): r'strings\s*:' t.value = t.value self._strings_start = t.lexpos if self._meta_end is None: self._meta_end = t.lexpos t.lexer.section = 'strings' return t def t_SECTIONCONDITION(self, t): r'condition\s*:' t.value = t.value self._condition_start = t.lexpos if self._meta_end is None: self._meta_end = t.lexpos if self._strings_end is None: self._strings_end = t.lexpos t.lexer.section = 'condition' return t # Text string handling @staticmethod def t_begin_STRING(t): r'"' 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'.' 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 """ raise ParseTypeError('Illegal string character: {!r}, at line: {}'.format(t.value[0], t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) # Byte string handling @staticmethod def t_begin_BYTESTRING(t): r'\{' 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_pair(t): r'\s*[a-fA-F0-9?]{2}\s*' @staticmethod def t_BYTESTRING_comment(t): r'\/\/[^\r\n]*' @staticmethod def t_BYTESTRING_mcomment(t): r'/\*(.|\n|\r\n)*?\*/' @staticmethod def t_BYTESTRING_jump(t): r'\[\s*(\d*)\s*-?\s*(\d*)\s*\]' 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('Illegal bytestring jump bounds: {}, at line: {}'.format(t.value, t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) @staticmethod def t_BYTESTRING_group_start(t): r'\(' t.lexer.bytestring_group += 1 @staticmethod def t_BYTESTRING_group_end(t): r'\)' t.lexer.bytestring_group -= 1 @staticmethod def t_BYTESTRING_group_logical_or(t): r'\|' @staticmethod def t_BYTESTRING_end(t): r'\}' t.type = 'BYTESTRING' t.value = t.lexer.lexdata[t.lexer.bytestring_start:t.lexer.lexpos] if t.lexer.bytestring_group != 0: raise ParseValueError('Unbalanced group in bytestring: {}, at line: {}'.format(t.value, t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) t.lexer.begin('INITIAL') # Account for newlines in bytestring. if '\r\n' in t.value: t.lexer.lineno += t.value.count('\r\n') else: t.lexer.lineno += t.value.count('\n') return t t_BYTESTRING_ignore = ' \r\n\t' @staticmethod def t_BYTESTRING_error(t): """Raise parsing error for illegal bytestring character. Args: t: Token input from lexer. Raises: ParseTypeError """ raise ParseTypeError('Illegal bytestring character : {}, at line: {}'.format(t.value[0], t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) # Rexstring Handling @staticmethod def t_begin_REXSTRING(t): r'/' if hasattr(t.lexer, 'section') and t.lexer.section in ('strings', 'condition'): t.lexer.rexstring_start = t.lexer.lexpos - 1 t.lexer.begin('REXSTRING') t.lexer.escape = 0 t.lexer.hex_escape = 0 else: t.type = 'FORWARDSLASH' return t @staticmethod def t_REXSTRING_end(t): r'/(?:i?s?)' 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'.' 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 """ raise ParseTypeError('Illegal rexstring character : {!r}, at line: {}'.format(t.value[0], t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) @staticmethod def t_STRINGNAME(t): r'\$[0-9a-zA-Z\-_]*[*]?' t.value = t.value return t @staticmethod def t_STRINGNAME_ARRAY(t): r'@[0-9a-zA-Z\-_]*[*]?' t.value = t.value return t @staticmethod def t_STRINGNAME_LENGTH(t): r'![0-9a-zA-Z\-_]*[*]?(?!=)' t.value = t.value return t @staticmethod def t_FILESIZE_SIZE(t): r"\d+[KM]B" t.value = t.value return t @staticmethod def t_NUM(t): r'\d+(\.\d+)?|0x\d+' t.value = t.value return t def t_ID(self, t): r'[a-zA-Z_][a-zA-Z_0-9.]*' t.type = self.reserved.get(t.value, 'ID') # Check for reserved words return t @staticmethod def t_STRINGNAME_COUNT(t): r'\#([a-zA-Z][0-9a-zA-Z\-_]*[*]?)?' t.value = t.value 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 """ raise ParseTypeError('Illegal character {!r} at line {}'.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''' @staticmethod def p_rules(p): '''rules : rules rule | rule''' def p_rule(self, p): '''rule : scopes RULE ID tag_section LBRACE rule_body RBRACE''' logger.info('Matched rule: {}'.format(p[3])) if '.' in p[3]: message = 'Invalid rule name {}, on line {}'.format(p[3], p.lineno(1)) raise ParseTypeError(message, p.lineno, p.lexpos) logger.debug('Rule start: {}, Rule stop: {}'.format(p.lineno(2), p.lineno(7))) while self._rule_comments: comment = self._rule_comments.pop() if p.lexpos(5) < comment.lexpos < p.lexpos(7): 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) @staticmethod def p_imports(p): '''imports : imports import | import''' @staticmethod def p_includes(p): '''includes : includes include | include''' @staticmethod def p_scopes(p): '''scopes : scopes scope | scope | ''' def p_import(self, p): '''import : IMPORT STRING''' import_value = p[2].replace('"', '') logger.debug('Matched import: {}'.format(import_value)) self._add_element(ElementTypes.IMPORT, import_value) def p_include(self, p): '''include : INCLUDE STRING''' include_value = p[2].replace('"', '') logger.debug('Matched include: {}'.format(include_value)) self._add_element(ElementTypes.INCLUDE, include_value) def p_scope(self, p): '''scope : PRIVATE | GLOBAL''' logger.debug('Matched scope identifier: {}'.format(p[1])) self._add_element(ElementTypes.SCOPE, p[1]) @staticmethod def p_tag_section(p): '''tag_section : COLON tags | ''' @staticmethod def p_tags(p): '''tags : tags tag | tag''' def p_tag(self, p): '''tag : ID''' logger.debug('Matched tag: {}'.format(p[1])) self._add_element(ElementTypes.TAG, p[1]) @staticmethod def p_rule_body(p): '''rule_body : sections''' logger.info('Matched rule body') @staticmethod def p_rule_sections(p): '''sections : sections section | section''' @staticmethod def p_rule_section(p): '''section : meta_section | strings_section | condition_section''' @staticmethod def p_meta_section(p): '''meta_section : SECTIONMETA meta_kvs''' logger.info('Matched meta section') @staticmethod def p_strings_section(p): '''strings_section : SECTIONSTRINGS strings_kvs''' @staticmethod def p_condition_section(p): '''condition_section : SECTIONCONDITION expression''' # Meta elements @staticmethod def p_meta_kvs(p): '''meta_kvs : meta_kvs meta_kv | meta_kv''' logger.info('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''' key = p[1] value = p[3] if re.match(r'".*"', value): match = re.match('"(.*)"', value) if match: value = match.group(1) elif value in ('true', 'false'): value = True if value == 'true' else False else: value = int(value) logger.debug('Matched meta kv: {} equals {}'.format(key, 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''' logger.info('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: message = 'Duplicate string name key {} on line {}'.format(key, p.lineno(1)) raise ParseTypeError(message, p.lineno, p.lexpos) self._stringnames.add(key) logger.debug('Matched strings kv: {} equals {}'.format(key, 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''' 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''' 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''' self._parse_string_kv(p, StringTypes.REGEX) @staticmethod def p_text_string_modifiers(p): '''text_string_modifiers : text_string_modifiers text_string_modifier | text_string_modifier''' def p_text_string_modifier(self, p): '''text_string_modifier : NOCASE | ASCII | WIDE | FULLWORD | XOR_MOD | XOR_MOD xor_mod_args | BASE64 | BASE64WIDE | BASE64 base64_with_args | BASE64WIDE base64_with_args | PRIVATE''' self._add_string_modifier(p) @staticmethod def p_regex_text_string_modifiers(p): '''regex_string_modifiers : regex_string_modifiers regex_string_modifer | regex_string_modifer''' def p_regex_string_modifer(self, p): '''regex_string_modifer : NOCASE | ASCII | WIDE | FULLWORD | PRIVATE''' self._add_string_modifier(p) @staticmethod def p_byte_string_modifiers(p): '''byte_string_modifiers : byte_string_modifiers byte_string_modifer | byte_string_modifer''' def p_byte_string_modifer(self, p): '''byte_string_modifer : PRIVATE''' self._add_string_modifier(p) def p_xor_mod_args(self, p): '''xor_mod_args : LPAREN NUM RPAREN | LPAREN NUM HYPHEN NUM RPAREN | LPAREN HEXNUM RPAREN | LPAREN HEXNUM HYPHEN HEXNUM RPAREN | LPAREN NUM HYPHEN HEXNUM RPAREN | LPAREN HEXNUM HYPHEN NUM RPAREN''' logger.debug('Matched an xor arg: {}'.format(''.join(p[1:]))) mods = [x for x in p if x not in (None, '(', '-', ')')] mod_int_list = [] mod_lineidx = set() for i, x in enumerate(mods): mod_int = int(x, 16) if x.startswith('0x') else int(x) if 0 <= mod_int <= 255: mod_int_list.append(mod_int) mod_lineidx.add(i) else: message = 'String modification value {} not between 0-255 on line {}'.format(x, p.lineno(1 + i)) raise ParseTypeError(message, p.lineno, p.lexpos) if mod_int_list[0] > mod_int_list[-1]: mod_lineno = list({p.lineno(1 + i) for i in mod_lineidx}) mod_lineno.sort() line_no = ' and '.join(str(lno) for lno in mod_lineno) message = 'String modification lower bound exceeds upper bound on line {}'.format(line_no) raise ParseTypeError(message, p.lineno, p.lexpos) else: mod_str_mod = YaraXor(mod_int_list) logger.debug('Matched string modifier(s): {}'.format(mod_str_mod)) self._add_element(ElementTypes.STRINGS_MODIFIER, mod_str_mod) def p_base64_with_args(self, p): '''base64_with_args : LPAREN STRING RPAREN''' # Remove parens and leading/trailing quotes b64_mod = [x for x in p if x not in (None, '(', ')')][0].strip('"') b64_data = b64_mod.encode('ascii').decode('unicode-escape') if len(b64_data) != 64: raise Exception("Base64 dictionary length {}, must be 64 characters".format(len(b64_data))) if re.search(r'(.).*\1', b64_data): raise Exception("Duplicate character in Base64 dictionary") mod_str_mod = YaraBase64(b64_mod) logger.debug('Matched string modifier(s): {}'.format(b64_mod)) self._add_element(ElementTypes.STRINGS_MODIFIER, mod_str_mod) @staticmethod def p_comments(p): '''comments : COMMENT | MCOMMENT''' logger.debug('Matched a comment: {}'.format(p[1])) # Condition elements @staticmethod def p_expression(p): '''expression : expression term | term''' def p_condition(self, p): '''term : FILESIZE_SIZE | ID | STRING | NUM | HEXNUM | LPAREN | RPAREN | LBRACK | RBRACK | DOTDOT | EQUIVALENT | EQUALS | NEQUALS | PLUS | PIPE | BACKSLASH | FORWARDSLASH | COMMA | GREATERTHAN | LESSTHAN | GREATEREQUAL | LESSEQUAL | RIGHTBITSHIFT | LEFTBITSHIFT | MODULO | TILDE | XOR_OP | PERIOD | COLON | STAR | HYPHEN | AMPERSAND | ALL | AND | ANY | AT | CONTAINS | ENTRYPOINT | FALSE | FILESIZE | FOR | IN | INT8 | INT16 | INT32 | INT8BE | INT16BE | INT32BE | MATCHES | NOT | OR | OF | THEM | TRUE | UINT8 | UINT16 | UINT32 | UINT8BE | UINT16BE | UINT32BE | STRINGNAME | STRINGNAME_ARRAY | STRINGNAME_LENGTH | STRINGNAME_COUNT | REXSTRING''' logger.debug('Matched a condition term: {}'.format(p[1])) if p[1] == '$': message = 'Potential wrong use of anonymous string on line {}'.format(p.lineno(1)) logger.info(message) 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'): self.parser.errok() # This is a method from PLY to reset the error state from parsing a comment self._rule_comments.append(p) else: message = 'Unknown text {} for token of type {} on line {}'.format(p.value, p.type, p.lineno) raise ParseTypeError(message, p.lineno, p.lexpos) @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('Invalid hex character: {!r}, at line: {}'.format(t.value, t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) elif t.lexer.escape == 1: raise ParseTypeError('Invalid escape sequence: \\{}, at line: {}'.format(t.value, t.lexer.lineno), t.lexer.lineno, t.lexer.lexpos) def _add_string_modifier(self, p): mod_str = p[1] prev_mod_with_args = False if mod_str in self.string_modifiers: message = 'Duplicate string modifier {} on line {}'.format(mod_str, p.lineno(1)) raise ParseTypeError(message, p.lineno, p.lexpos) if mod_str in self.EXCLUSIVE_TEXT_MODIFIERS: prev_mods = {x for x in self.string_modifiers if isinstance(x, str)} excluded_modifiers = prev_mods & ({mod_str} ^ self.EXCLUSIVE_TEXT_MODIFIERS) if excluded_modifiers: prev_mod_str = excluded_modifiers.pop() message = ('Mutually exclusive string modifier use of {} on line {} after {} usage' .format(mod_str, p.lineno(1), prev_mod_str)) raise ParseTypeError(message, p.lineno, p.lexpos) if self.string_modifiers: # Convert previously created modifiers with args to strings if mod_str.startswith('base64') and isinstance(self.string_modifiers[-1], YaraBase64): if mod_str == 'base64wide': self.string_modifiers[-1].modifier_name = 'base64wide' logger.debug('Corrected base64 string modifier to base64wide') self.string_modifiers[-1] = str(self.string_modifiers[-1]) prev_mod_with_args = True elif mod_str == 'xor' and isinstance(self.string_modifiers[-1], YaraXor): self.string_modifiers[-1] = str(self.string_modifiers[-1]) logger.debug('Modified xor string was already added') prev_mod_with_args = True if not prev_mod_with_args: self._add_element(ElementTypes.STRINGS_MODIFIER, mod_str) logger.debug('Matched a string modifier: {}'.format(mod_str)) class YaraXor(str): """YARA xor string modifier.""" def __init__(self, xor_range=None): """Initialize XOR string modifier.""" str.__init__(self) self.modifier_name = 'xor' self.modifier_list = xor_range if xor_range is not None else [] def __str__(self): """Return the string representation.""" if len(self.modifier_list) == 0: return self.modifier_name return '{}({})'.format( self.modifier_name, '-'.join(['{0:#0{1}x}'.format(x, 4) for x in self.modifier_list]) ) def __repr__(self): """Return the object representation.""" if len(self.modifier_list) == 0: return '{}()'.format(self.__class__.__name__) else: return '{}({})'.format(self.__class__.__name__, self.modifier_list) class YaraBase64(str): """YARA base64 string modifier for easier printing.""" def __init__(self, modifier_alphabet=None, modifier_name='base64'): """Initialize base64 string modifier.""" str.__init__(self) self.modifier_name = 'base64' if modifier_name != 'base64wide' else 'base64wide' self.modifier_alphabet = modifier_alphabet def __str__(self): """Return the string representation.""" if self.modifier_alphabet is None: return '{}'.format(self.modifier_name) else: return '{}("{}")'.format(self.modifier_name, self.modifier_alphabet) def __repr__(self): """Return the object representation.""" if self.modifier_alphabet is None: return '{}()'.format(self.__class__.__name__) else: return '{}({})'.format(self.__class__.__name__, repr(self.modifier_alphabet)) plyara-2.1.1/plyara/command_line.py0000755000175000017500000000310213711334624016643 0ustar rhaistrhaist#!/usr/bin/env python3 # Copyright 2014 Christian Buia # Copyright 2020 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 from plyara.core import Plyara def main(arguments=None): """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 dictionary 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') if not arguments: args = parser.parse_args() else: args = parser.parse_args(arguments) with open(args.file, 'r', encoding='utf-8') as fh: input_string = fh.read() plyara = Plyara(console_logging=args.log) rules = plyara.parse_string(input_string) print(json.dumps(rules, sort_keys=True, indent=4)) if __name__ == '__main__': main() plyara-2.1.1/plyara/exceptions.py0000755000175000017500000000347513711334624016414 0ustar rhaistrhaist#!/usr/bin/env python3 # Copyright 2014 Christian Buia # Copyright 2020 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.1.1/docs/0000755000175000017500000000000013711334624013305 5ustar rhaistrhaistplyara-2.1.1/docs/conf.py0000644000175000017500000001235113711334624014606 0ustar rhaistrhaist# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/stable/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = u'plyara' copyright = u'2020, plyara' author = u'plyara' # The short X.Y version version = u'' # The full version, including alpha/beta/rc tags release = u'' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { 'github_user': 'plyara', 'github_repo': 'plyara', 'github_button': 'true', 'github_type': 'star', 'description': 'Parse YARA rules into a dictionary representation.', 'logo_name': 'plyara', } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # html_sidebars = { '**': [ 'about.html', 'localtoc.html', ] } # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'plyaradoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'plyara.tex', u'plyara Documentation', u'plyara', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'plyara', u'plyara Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'plyara', u'Plyara Documentation', author, 'plyara', 'Parse YARA rules and operate over them more easily.', 'Miscellaneous'), ] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} plyara-2.1.1/docs/Makefile0000644000175000017500000000113313711334624014743 0ustar rhaistrhaist# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = plyara SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)plyara-2.1.1/docs/make.bat0000644000175000017500000000145213711334624014714 0ustar rhaistrhaist@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=plyara if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd plyara-2.1.1/docs/index.rst0000644000175000017500000000125113711334624015145 0ustar rhaistrhaist.. plyara documentation master file, created by sphinx-quickstart on Thu Apr 19 10:17:12 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. include:: ../README.rst Module Documentation -------------------- .. autoclass:: plyara.Plyara :show-inheritance: :member-order: bysource .. autoclass:: plyara.core.Parser :undoc-members: :show-inheritance: :member-order: bysource .. automodule:: plyara.utils :members: :undoc-members: :show-inheritance: :member-order: bysource Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` plyara-2.1.1/LICENSE0000644000175000017500000002612413711334624013367 0ustar rhaistrhaistApache 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 2020 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.1.1/examples/0000755000175000017500000000000013711334624014173 5ustar rhaistrhaistplyara-2.1.1/examples/corpus_stats.py0000755000175000017500000000324613711334624017306 0ustar rhaistrhaist"""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()