├── tests ├── __init__.py └── data │ ├── comment_only.yar │ ├── issue99_1.yar │ ├── issue99_2.yar │ ├── bad_condition_string.yar │ ├── windows_newline_ruleset.yar │ ├── windows_newline_ruleset_with_error.yar │ ├── issue107.yar │ ├── include_ruleset.yar │ ├── windows_newline_ruleset_comment.yar │ ├── test_ruleset_1_rule.yar │ ├── import_ruleset_math.yar │ ├── import_ruleset_cuckoo.yar │ ├── import_ruleset_elf.yar │ ├── tag_ruleset.yar │ ├── scope_ruleset.yar │ ├── import_ruleset_magic.yar │ ├── test_ruleset_2_rules.yar │ ├── import_ruleset_hash.yar │ ├── mixed_ruleset.yar │ ├── import_ruleset_dotnet.yar │ ├── import_ruleset_pe.yar │ ├── import_ruleset_androguard.yar │ ├── rebuild_ruleset.yar │ ├── condition_ruleset.yar │ ├── metakv_test.yar │ ├── metadata_ruleset.yar │ ├── base64_modifier_ruleset.yar │ ├── test_file.txt │ ├── xor_modifier_ruleset.yar │ ├── logic_collision_ruleset_v2.0.0.yar │ ├── string_exclusive_modifiers.yar │ ├── logic_collision_ruleset.yar │ ├── string_ruleset.yar │ ├── detect_dependencies_ruleset.yar │ ├── rulehashes.txt │ ├── rulehashes_v2.0.0.txt │ └── test_rules_from_yara_project.yar ├── setup.cfg ├── requirements-dev.txt ├── .readthedocs.yml ├── docs ├── Makefile ├── index.rst ├── make.bat └── conf.py ├── plyara ├── __init__.py ├── command_line.py ├── exceptions.py ├── utils.py └── core.py ├── .travis.yml ├── examples └── corpus_stats.py ├── setup.py ├── .gitignore ├── README.rst └── LICENSE /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | max-line-length=120 3 | -------------------------------------------------------------------------------- /tests/data/comment_only.yar: -------------------------------------------------------------------------------- 1 | // This rule file has been deleted 2 | -------------------------------------------------------------------------------- /tests/data/issue99_1.yar: -------------------------------------------------------------------------------- 1 | rule test1 { 2 | condition: 3 | true 4 | } 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | nose 2 | coverage 3 | pycodestyle 4 | pydocstyle 5 | pyflakes 6 | -------------------------------------------------------------------------------- /tests/data/issue99_2.yar: -------------------------------------------------------------------------------- 1 | rule test2 { 2 | condition: 3 | false 4 | } 5 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | build: 2 | image: latest 3 | 4 | python: 5 | version: 3.6 6 | setup_py_install: true 7 | -------------------------------------------------------------------------------- /tests/data/bad_condition_string.yar: -------------------------------------------------------------------------------- 1 | rule sample 2 | { 3 | strings: 4 | $ = { 00 5 | 00 } 6 | conditio: 7 | all of them 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/windows_newline_ruleset.yar: -------------------------------------------------------------------------------- 1 | rule sample 2 | { 3 | strings: 4 | $ = { 00 5 | 00 } 6 | condition: 7 | all of them 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/windows_newline_ruleset_with_error.yar: -------------------------------------------------------------------------------- 1 | rule sample 2 | { 3 | strings: 4 | $ = { 00 5 | 00 } 6 | conditio: 7 | all of them 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/issue107.yar: -------------------------------------------------------------------------------- 1 | rule test{ 2 | strings: 3 | $TEST1 = "testy" 4 | $test2 = "tasty" 5 | condition: 6 | ( #TEST1 > 5 ) and ( #test2 > 5 ) 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/include_ruleset.yar: -------------------------------------------------------------------------------- 1 | include "string_ruleset.yar" 2 | 3 | rule includerule { 4 | strings: 5 | $a = "1aosidjoasidjaosidj" 6 | 7 | condition: 8 | all of them 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/windows_newline_ruleset_comment.yar: -------------------------------------------------------------------------------- 1 | /* 2 | Multiline comment 3 | */ 4 | 5 | rule sample 6 | { 7 | strings: 8 | $ = { 00 9 | 00 } 10 | condition: 11 | all of them 12 | } 13 | -------------------------------------------------------------------------------- /tests/data/test_ruleset_1_rule.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule rule_one 4 | { 5 | strings: 6 | $a = "one" 7 | condition: 8 | all of them 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_math.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "math" 4 | 5 | rule math_001 6 | { 7 | condition: 8 | uint16(0) == 0x5A4D and math.entropy(0, filesize) > 7.0 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_cuckoo.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "cuckoo" 4 | 5 | rule cuckoo_001 6 | { 7 | condition: 8 | cuckoo.network.http_request(/http:\/\/someone\.doingevil\.com/) 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_elf.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "elf" 4 | 5 | rule elf_001 6 | { 7 | condition: 8 | elf.number_of_sections == 1 9 | } 10 | 11 | rule elf_002 12 | { 13 | condition: 14 | elf.machine == elf.EM_X86_64 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/tag_ruleset.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule OneTag: tag1 4 | { 5 | condition: false 6 | } 7 | 8 | rule TwoTags : tag1 tag2 9 | { 10 | condition: false 11 | } 12 | 13 | rule ThreeTags : tag1 tag2 tag3 14 | { 15 | condition: false 16 | } 17 | -------------------------------------------------------------------------------- /tests/data/scope_ruleset.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | global rule GlobalScope 4 | { 5 | condition: false 6 | } 7 | 8 | private rule PrivateScope 9 | { 10 | condition: false 11 | } 12 | 13 | global private rule PrivateGlobalScope 14 | { 15 | condition: false 16 | } 17 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_magic.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "magic" 4 | 5 | rule magic_001 6 | { 7 | condition: 8 | magic.type() contains "PDF" 9 | } 10 | 11 | rule magic_002 12 | { 13 | condition: 14 | magic.mime_type() == "application/pdf" 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/test_ruleset_2_rules.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule rule_two 4 | { 5 | strings: 6 | $a = "two" 7 | condition: 8 | all of them 9 | } 10 | 11 | rule rule_three 12 | { 13 | strings: 14 | $a = "three" 15 | condition: 16 | all of them 17 | } 18 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_hash.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "hash" 4 | 5 | rule hash_001 6 | { 7 | condition: 8 | hash.md5("dummy") == "275876e34cf609db118f3d84b799a790" 9 | } 10 | 11 | rule hash_002 12 | { 13 | condition: 14 | hash.md5(0, filesize) == "feba6c919e3797e7778e8f2e85fa033d" 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/mixed_ruleset.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule FirstRule 4 | { 5 | meta: 6 | author = "Andrés Iniesta" 7 | date = "2015-01-01" 8 | strings: 9 | $a = "hark, a \"string\" here" fullword ascii 10 | $b = { 00 22 44 66 88 aa cc ee } 11 | condition: 12 | all of them 13 | } 14 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_dotnet.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "dotnet" 4 | 5 | rule dotnet_001 6 | { 7 | condition: 8 | dotnet.number_of_streams != 5 9 | } 10 | 11 | rule dotnet_002 12 | { 13 | condition: 14 | for any i in (0..dotnet.number_of_streams - 1): 15 | (dotnet.streams[i].name == "#Blop") 16 | } 17 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_pe.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "pe" 4 | 5 | rule pe_001 6 | { 7 | condition: 8 | pe.number_of_sections == 1 9 | } 10 | 11 | rule pe_002 12 | { 13 | condition: 14 | pe.exports("CPlApplet") 15 | } 16 | 17 | rule pe_003 18 | { 19 | condition: 20 | pe.characteristics & pe.DLL 21 | } 22 | -------------------------------------------------------------------------------- /tests/data/import_ruleset_androguard.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | import "androguard" 4 | 5 | rule androguard_001 6 | { 7 | condition: 8 | androguard.package_name(/videogame/) 9 | } 10 | 11 | rule androguard_002 12 | { 13 | condition: 14 | androguard.activity(/\.sms\./) or 15 | androguard.activity("com.package.name.sendSMS") 16 | } 17 | -------------------------------------------------------------------------------- /tests/data/rebuild_ruleset.yar: -------------------------------------------------------------------------------- 1 | rule FirstRule 2 | { 3 | strings: 4 | $a = "hark, a \"string\" here" fullword ascii 5 | $b = { 00 22 44 66 88 aa cc ee } 6 | 7 | condition: 8 | all of them 9 | } 10 | rule SecondRule : aTag 11 | { 12 | strings: 13 | $x = "hi" 14 | $y = /state: (on|off)/ wide 15 | $z = "bye" 16 | 17 | condition: 18 | for all of them : (#>2) 19 | } 20 | rule ForthRule 21 | { 22 | condition: 23 | uint8(0)^unit8(1)==0x12 24 | } 25 | -------------------------------------------------------------------------------- /tests/data/condition_ruleset.yar: -------------------------------------------------------------------------------- 1 | rule image_filetype { 2 | condition: 3 | ( 4 | uint32be(0x00) == 0x89504E47 or // PNG 5 | uint16be(0x00) == 0xFFD8 or // JPEG 6 | uint32be(0x00) == 0x47494638 // GIF 7 | ) 8 | and 9 | ( 10 | $eval or 1 of ($key*) 11 | ) 12 | and 13 | ( 14 | @a[1] or !a[1] 15 | ) 16 | and 17 | not filename matches /[0-9a-zA-Z]{30,}/ 18 | } 19 | -------------------------------------------------------------------------------- /tests/data/metakv_test.yar: -------------------------------------------------------------------------------- 1 | import "pe" 2 | 3 | rule meta_test 4 | { 5 | meta: 6 | author = "Malware Utkonos" 7 | date = "2020-01-04" 8 | tlp = "Green" 9 | strings: 10 | $op = { 55 8B EC 81 [2] 00 00 00 89 [5] 89 } 11 | condition: 12 | pe.exports("initTest") and all of them 13 | } 14 | 15 | rule meta_test2 16 | { 17 | meta: 18 | author = "Malware Utkonos" 19 | date = "2020-01-04" 20 | tlp = "Green" 21 | author = "Someone else" 22 | strings: 23 | $op = { 55 8B EC 81 [2] 00 00 00 89 [5] 89 } 24 | condition: 25 | pe.exports("initTest") and all of them 26 | } 27 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = plyara 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /tests/data/metadata_ruleset.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule StringTypeMetadata 4 | { 5 | meta: 6 | string_value = "String Metadata" 7 | 8 | condition: false 9 | } 10 | 11 | rule IntegerTypeMetadata 12 | { 13 | meta: 14 | integer_value = 100 15 | 16 | condition: false 17 | } 18 | 19 | rule BooleanTypeMetadata 20 | { 21 | meta: 22 | boolean_value = true 23 | 24 | condition: false 25 | } 26 | 27 | rule AllTypesMetadata 28 | { 29 | meta: 30 | string_value = "Different String Metadata" 31 | integer_value = 33 32 | boolean_value = false 33 | 34 | condition: false 35 | } 36 | -------------------------------------------------------------------------------- /plyara/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2014 Christian Buia 3 | # Copyright 2020 plyara Maintainers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | __all__ = ['Plyara'] 17 | from plyara.core import * 18 | -------------------------------------------------------------------------------- /tests/data/base64_modifier_ruleset.yar: -------------------------------------------------------------------------------- 1 | // The following work with YARA >= 3.12.0 2 | 3 | rule base64_unmodified 4 | { 5 | strings: 6 | $a = "one" base64 7 | condition: 8 | all of them 9 | } 10 | 11 | rule base64wide_unmodified 12 | { 13 | strings: 14 | $a = "one" base64wide 15 | condition: 16 | all of them 17 | } 18 | 19 | rule base64_mod_custom 20 | { 21 | strings: 22 | $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") 23 | condition: 24 | all of them 25 | } 26 | 27 | rule base64wide_mod_custom 28 | { 29 | strings: 30 | $a = "one" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") 31 | condition: 32 | all of them 33 | } 34 | -------------------------------------------------------------------------------- /tests/data/test_file.txt: -------------------------------------------------------------------------------- 1 | rule FirstRule { 2 | // test comment 3 | meta: 4 | author = "Andrés Iniesta" 5 | date = "2015-01-01" 6 | strings: 7 | $a = "hark, a \"string\" here" fullword ascii 8 | $b = { 00 22 44 66 88 aa cc ee } 9 | condition: 10 | all of them 11 | } 12 | 13 | import "bingo" 14 | import "bango" 15 | rule SecondRule : aTag { 16 | meta: 17 | author = "Ivan Rakitić" 18 | date = "2015-02-01" 19 | strings: 20 | /* test 21 | multiline 22 | comment 23 | */ 24 | $x = "hi" 25 | $y = /state: (on|off)/ wide 26 | $z = "bye" 27 | condition: 28 | for all of them : ( # > 2 ) 29 | } 30 | 31 | rule ThirdRule {condition: false} 32 | 33 | rule ForthRule { 34 | condition: 35 | uint8(0) ^ unit8(1) == 0x12 36 | } 37 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. plyara documentation master file, created by 2 | sphinx-quickstart on Thu Apr 19 10:17:12 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.rst 7 | 8 | Module Documentation 9 | -------------------- 10 | 11 | .. autoclass:: plyara.Plyara 12 | :show-inheritance: 13 | :member-order: bysource 14 | 15 | .. autoclass:: plyara.core.Parser 16 | :undoc-members: 17 | :show-inheritance: 18 | :member-order: bysource 19 | 20 | .. automodule:: plyara.utils 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | :member-order: bysource 25 | 26 | Indices and tables 27 | ================== 28 | 29 | * :ref:`genindex` 30 | * :ref:`modindex` 31 | * :ref:`search` 32 | -------------------------------------------------------------------------------- /tests/data/xor_modifier_ruleset.yar: -------------------------------------------------------------------------------- 1 | rule xor_unmodified 2 | { 3 | strings: 4 | $a = "one" xor wide 5 | condition: 6 | all of them 7 | } 8 | 9 | // The following work with YARA >= 3.11.0 10 | 11 | rule xor_mod_num_single 12 | { 13 | strings: 14 | $a = "one" xor(16) 15 | condition: 16 | all of them 17 | } 18 | 19 | rule xor_mod_num_range 20 | { 21 | strings: 22 | $a = "one" xor( 16 - 128 ) 23 | condition: 24 | all of them 25 | } 26 | 27 | rule xor_mod_hexnum_single 28 | { 29 | strings: 30 | $a = "one" xor(0x10) 31 | condition: 32 | all of them 33 | } 34 | 35 | rule xor_mod_hexnum_range 36 | { 37 | strings: 38 | $a = "one" xor( 0x10 - 0x80 ) 39 | condition: 40 | all of them 41 | } 42 | 43 | rule xor_mod_mixed_range 44 | { 45 | strings: 46 | $a = "one" xor( 16 - 0x80 ) 47 | condition: 48 | all of them 49 | } 50 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=plyara 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /tests/data/logic_collision_ruleset_v2.0.0.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule Set001_Rule001 4 | { 5 | strings: 6 | $a = "foobar" 7 | 8 | condition: 9 | $a 10 | } 11 | 12 | rule Set001_Rule002 13 | { 14 | strings: 15 | $b = "foobar" 16 | 17 | condition: 18 | $b 19 | } 20 | 21 | rule Set001_Rule003 22 | { 23 | strings: 24 | $aaa = "foobar" 25 | 26 | condition: 27 | $* 28 | } 29 | 30 | rule Set001_Rule004 31 | { 32 | strings: 33 | $ = "foobar" 34 | 35 | condition: 36 | $* 37 | } 38 | 39 | 40 | rule Set002_Rule001 41 | { 42 | strings: 43 | $b = "foo" 44 | $a = "bar" 45 | 46 | condition: 47 | all of them 48 | } 49 | 50 | rule Set002_Rule002 51 | { 52 | strings: 53 | $b = "bar" 54 | $a = "foo" 55 | 56 | condition: 57 | all of $* 58 | } 59 | 60 | rule Set002_Rule003 61 | { 62 | strings: 63 | $ = "bar" 64 | $ = "foo" 65 | 66 | condition: 67 | all of $* 68 | } 69 | 70 | rule Set002_Rule004 71 | { 72 | strings: 73 | $ = "bar" 74 | $ = "foo" 75 | 76 | condition: 77 | all of them 78 | } 79 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6.11" 4 | - "3.7.8" 5 | - "3.8.3" 6 | sudo: required 7 | dist: focal 8 | install: 9 | - "pip install nose" 10 | - "pip install pycodestyle pydocstyle pyflakes" 11 | - "pip install coverage" 12 | - "pip install codacy-coverage" 13 | - "pip install collective.checkdocs Pygments" 14 | - "python setup.py install" 15 | script: 16 | - nosetests --with-coverage --cover-package=plyara --cover-xml 17 | - pycodestyle plyara/*.py 18 | - pycodestyle setup.py 19 | - pycodestyle tests/unit_tests.py 20 | - pydocstyle --ignore=D104 plyara/__init__.py 21 | - pydocstyle --ignore=D203,D205,D208,D209,D213,D300,D400,D401,D403,D406,D407,D413,D415 plyara/core.py 22 | - pydocstyle --ignore=D203,D213 plyara/exceptions.py 23 | - pydocstyle --ignore=D203,D213,D406,D407,D413 plyara/utils.py 24 | - pydocstyle --ignore=D101,D102,D203,D213 tests/unit_tests.py 25 | - pydocstyle plyara/command_line.py 26 | - pydocstyle setup.py 27 | - pyflakes plyara/core.py 28 | - pyflakes plyara/exceptions.py 29 | - pyflakes plyara/command_line.py 30 | - pyflakes plyara/utils.py 31 | - pyflakes setup.py 32 | - pyflakes tests/unit_tests.py 33 | - python setup.py checkdocs 34 | after_success: 35 | - python-codacy-coverage 36 | -------------------------------------------------------------------------------- /tests/data/string_exclusive_modifiers.yar: -------------------------------------------------------------------------------- 1 | rule duplicate_modifier 2 | { 3 | strings: 4 | $a = "one" xor xor 5 | condition: 6 | all of them 7 | } 8 | 9 | rule invalid_xor_modifier 10 | { 11 | strings: 12 | $a = /AA/ xor(500) 13 | condition: 14 | all of them 15 | } 16 | 17 | rule base64_error_nocase 18 | { 19 | strings: 20 | $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") nocase 21 | $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") nocase 22 | condition: 23 | all of them 24 | } 25 | 26 | rule base64_error_xor 27 | { 28 | strings: 29 | $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") xor 30 | $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstu") xor 31 | condition: 32 | all of them 33 | } 34 | 35 | rule base64_error_xor 36 | { 37 | strings: 38 | $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstuxyz") xor 39 | $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZabcdefghijklmnopqrstuxyz") xor 40 | condition: 41 | all of them 42 | } 43 | 44 | rule base64_error_xor 45 | { 46 | strings: 47 | $a = "one" base64("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZ") xor 48 | $b = "two" base64wide("!@#$%^&*(){}[].,|ABCDEFGHIJ\x09LMNOPQRSTUVWXYZ") xor 49 | condition: 50 | all of them 51 | } 52 | -------------------------------------------------------------------------------- /plyara/command_line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2014 Christian Buia 3 | # Copyright 2020 plyara Maintainers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """plyara command line script. 17 | 18 | This module contains command line script for parsing rules. 19 | """ 20 | import argparse 21 | import json 22 | 23 | from plyara.core import Plyara 24 | 25 | 26 | def main(arguments=None): 27 | """Run the command line process to parse a yara rule file and output pretty printed JSON.""" 28 | parser = argparse.ArgumentParser(description='Parse YARA rules into a dictionary representation.') 29 | parser.add_argument('file', metavar='FILE', help='File containing YARA rules to parse.') 30 | parser.add_argument('--log', help='Enable debug logging to the console.', action='store_true') 31 | if not arguments: 32 | args = parser.parse_args() 33 | else: 34 | args = parser.parse_args(arguments) 35 | 36 | with open(args.file, 'r', encoding='utf-8') as fh: 37 | input_string = fh.read() 38 | 39 | plyara = Plyara(console_logging=args.log) 40 | rules = plyara.parse_string(input_string) 41 | 42 | print(json.dumps(rules, sort_keys=True, indent=4)) 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /tests/data/logic_collision_ruleset.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests for hashing strings and condition - Modification will require test updates 2 | 3 | rule Set001_Rule001 4 | { 5 | strings: 6 | $a = "foobar" 7 | 8 | condition: 9 | $a 10 | } 11 | 12 | rule Set001_Rule002 13 | { 14 | strings: 15 | $b = "foobar" 16 | 17 | condition: 18 | $b 19 | } 20 | 21 | /* 22 | // Although they match identical content as the above two rules, 23 | // the following four rules do not yet return the same hash. 24 | 25 | rule Set001_Rule003 26 | { 27 | strings: 28 | $aaa = "foobar" 29 | 30 | condition: 31 | any of ($*) 32 | } 33 | 34 | rule Set001_Rule004 35 | { 36 | strings: 37 | $ = "foobar" 38 | 39 | condition: 40 | any of them 41 | } 42 | 43 | rule Set001_Rule005 44 | { 45 | strings: 46 | $ = "foobar" 47 | 48 | condition: 49 | all of ($*) 50 | } 51 | 52 | rule Set001_Rule006 53 | { 54 | strings: 55 | $ = "foobar" 56 | 57 | condition: 58 | all of them 59 | } 60 | 61 | */ 62 | 63 | rule Set002_Rule001 64 | { 65 | strings: 66 | $b = "foo" 67 | $a = "bar" 68 | 69 | condition: 70 | all of them 71 | } 72 | 73 | rule Set002_Rule002 74 | { 75 | strings: 76 | $b = "bar" 77 | $a = "foo" 78 | 79 | condition: 80 | all of ($*) 81 | } 82 | 83 | rule Set002_Rule003 84 | { 85 | strings: 86 | $ = "bar" 87 | $ = "foo" 88 | 89 | condition: 90 | all of ($*) 91 | } 92 | 93 | rule Set002_Rule004 94 | { 95 | strings: 96 | $ = "bar" 97 | $ = "foo" 98 | 99 | condition: 100 | all of them 101 | } 102 | -------------------------------------------------------------------------------- /examples/corpus_stats.py: -------------------------------------------------------------------------------- 1 | """Example script that demonstrates using plyara.""" 2 | import argparse 3 | import operator 4 | 5 | import plyara 6 | 7 | 8 | def example(): 9 | """Execute the example code.""" 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument('file', metavar='FILE', help='File containing YARA rules to parse.') 12 | args = parser.parse_args() 13 | 14 | print('Parsing file...') 15 | with open(args.file, 'r') as fh: 16 | data = fh.read() 17 | 18 | parser = plyara.Plyara() 19 | rules_dict = parser.parse_string(data) 20 | print('Analyzing dictionary...') 21 | 22 | imps = {} 23 | tags = {} 24 | rule_count = 0 25 | 26 | for rule in rules_dict: 27 | rule_count += 1 28 | 29 | # Imports 30 | if 'imports' in rule: 31 | for imp in rule['imports']: 32 | imp = imp.replace('"', '') 33 | if imp in imps: 34 | imps[imp] += 1 35 | else: 36 | imps[imp] = 1 37 | 38 | # Tags 39 | if 'tags' in rule: 40 | for tag in rule['tags']: 41 | if tag in tags: 42 | tags[tag] += 1 43 | else: 44 | tags[tag] = 1 45 | 46 | print('\n======================\n') 47 | print('Number of rules in file: {}'.format(rule_count)) 48 | 49 | ordered_imps = sorted(imps.items(), key=operator.itemgetter(1), reverse=True) 50 | 51 | ordered_tags = sorted(tags.items(), key=operator.itemgetter(1), reverse=True) 52 | 53 | print('\n======================\n') 54 | print('Top imports:') 55 | for i in range(5): 56 | if i < len(ordered_imps): 57 | print(ordered_imps[i]) 58 | 59 | print('\n======================\n') 60 | print('Top tags:') 61 | for i in range(5): 62 | if i < len(ordered_tags): 63 | print(ordered_tags[i]) 64 | 65 | 66 | if __name__ == '__main__': 67 | example() 68 | -------------------------------------------------------------------------------- /plyara/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2014 Christian Buia 3 | # Copyright 2020 plyara Maintainers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """plyara exceptions. 17 | 18 | This module contains the set of plyara's exceptions. 19 | """ 20 | 21 | 22 | class ParseError(Exception): 23 | """Base parsing error exception type. 24 | 25 | It stores also the line number and lex position as instance 26 | attributes 'lineno' and 'lexpos' respectively. 27 | """ 28 | 29 | def __init__(self, message, lineno, lexpos): 30 | """Initialize exception object.""" 31 | self.lineno = lineno 32 | self.lexpos = lexpos 33 | super().__init__(message) 34 | 35 | 36 | class ParseTypeError(ParseError): 37 | """Error emmited during parsing when a wrong token type is encountered. 38 | 39 | It stores also the line number and lex position as instance 40 | attributes 'lineno' and 'lexpos' respectively. 41 | """ 42 | 43 | def __init__(self, message, lineno, lexpos): 44 | """Initialize exception object.""" 45 | super().__init__(message, lineno, lexpos) 46 | 47 | 48 | class ParseValueError(ParseError): 49 | """Error emmited during parsing when a wrong value is encountered. 50 | 51 | It stores also the line number and lex position as instance 52 | attributes 'lineno' and 'lexpos' respectively. 53 | """ 54 | 55 | def __init__(self, message, lineno, lexpos): 56 | """Initialize exception object.""" 57 | super().__init__(message, lineno, lexpos) 58 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2014 Christian Buia 3 | # Copyright 2020 plyara Maintainers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """A setuptools based setup module. 17 | 18 | See: 19 | https://packaging.python.org/guides/distributing-packages-using-setuptools/ 20 | https://github.com/pypa/sampleproject 21 | """ 22 | import pathlib 23 | from setuptools import find_packages, setup 24 | 25 | here = pathlib.Path().cwd() 26 | 27 | # Get the long description from the README file 28 | with here.joinpath('README.rst').open(encoding='utf-8') as fh: 29 | long_description = fh.read() 30 | 31 | setup( 32 | name='plyara', 33 | version='2.1.1', 34 | description='Parse YARA rules.', 35 | long_description=long_description, 36 | url='https://github.com/plyara/plyara', 37 | author='plyara Maintainers', 38 | license='Apache License 2.0', 39 | test_suite='tests.unit_tests', 40 | classifiers=[ 41 | 'Development Status :: 5 - Production/Stable', 42 | 'Intended Audience :: Developers', 43 | 'Topic :: Software Development :: Build Tools', 44 | 'License :: OSI Approved :: Apache Software License', 45 | 'Programming Language :: Python :: 3', 46 | 'Programming Language :: Python :: 3.6', 47 | 'Programming Language :: Python :: 3.7', 48 | 'Programming Language :: Python :: 3.8', 49 | ], 50 | keywords='malware analysis yara', 51 | packages=find_packages(exclude=['docs', 'examples', 'tests']), 52 | install_requires=[ 53 | 'ply>=3.11' 54 | ], 55 | entry_points={ 56 | 'console_scripts': [ 57 | 'plyara=plyara.command_line:main', 58 | ], 59 | }, 60 | project_urls={ 61 | 'Bug Reports': 'https://github.com/plyara/plyara/issues', 62 | 'Source': 'https://github.com/plyara/plyara', 63 | }, 64 | ) 65 | -------------------------------------------------------------------------------- /tests/data/string_ruleset.yar: -------------------------------------------------------------------------------- 1 | // This ruleset is used for unit tests - Modification will require test updates 2 | 3 | rule Text 4 | { 5 | strings: 6 | $text_string = "foobar" 7 | 8 | condition: 9 | $text_string 10 | } 11 | 12 | rule FullwordText 13 | { 14 | strings: 15 | $text_string = "foobar" fullword 16 | 17 | condition: 18 | $text_string 19 | } 20 | 21 | rule CaseInsensitiveText 22 | { 23 | strings: 24 | $text_string = "foobar" nocase 25 | 26 | condition: 27 | $text_string 28 | } 29 | 30 | rule WideCharText 31 | { 32 | strings: 33 | $wide_string = "Borland" wide 34 | 35 | condition: 36 | $wide_string 37 | } 38 | 39 | rule WideCharAsciiText 40 | { 41 | strings: 42 | $wide_and_ascii_string = "Borland" wide ascii 43 | 44 | condition: 45 | $wide_and_ascii_string 46 | } 47 | 48 | rule HexWildcard 49 | { 50 | strings: 51 | $hex_string = { E2 34 ?? C8 A? FB } 52 | 53 | condition: 54 | $hex_string 55 | } 56 | 57 | rule HexJump 58 | { 59 | strings: 60 | $hex_string = { F4 23 [4-6] 62 B4 } 61 | 62 | condition: 63 | $hex_string 64 | } 65 | 66 | rule HexAlternatives 67 | { 68 | strings: 69 | $hex_string = { F4 23 ( 62 B4 | 56 ) 45 } 70 | 71 | condition: 72 | $hex_string 73 | } 74 | 75 | rule HexMultipleAlternatives 76 | { 77 | strings: 78 | $hex_string = { F4 23 ( 62 B4 | 56 | 45 ?? 67 ) 45 } 79 | 80 | condition: 81 | $hex_string 82 | } 83 | 84 | rule RegExp 85 | { 86 | strings: 87 | $re1 = /md5: [0-9a-fA-F]{32}/nocase // no case for hash 88 | $re2 = /state: (on|off)/i//no case for state 89 | $re3 = /\x00https?:\/\/[^\x00]{4,500}\x00\x00\x00/ 90 | 91 | condition: 92 | $re1 and $re2 and $re3 93 | } 94 | 95 | rule Xor 96 | { 97 | strings: 98 | $xor_string = "This program cannot" xor 99 | 100 | condition: 101 | $xor_string 102 | } 103 | 104 | rule WideXorAscii 105 | { 106 | strings: 107 | $xor_string = "This program cannot" xor wide ascii 108 | 109 | condition: 110 | $xor_string 111 | } 112 | 113 | rule WideXor 114 | { 115 | strings: 116 | $xor_string = "This program cannot" xor wide 117 | 118 | condition: 119 | $xor_string 120 | } 121 | 122 | rule DoubleBackslash 123 | { 124 | strings: 125 | $bs = "\"\\\\\\\"" 126 | 127 | condition: 128 | $bs 129 | } 130 | 131 | rule DoubleQuote 132 | { 133 | strings: 134 | $text_string = "foobar\"" 135 | 136 | condition: 137 | $text_string 138 | } 139 | 140 | rule HorizontalTab 141 | { 142 | strings: 143 | $text_string = "foo\tbar" 144 | 145 | condition: 146 | $text_string 147 | } 148 | 149 | rule Newline 150 | { 151 | strings: 152 | $text_string = "foo\nbar" 153 | 154 | condition: 155 | $text_string 156 | } 157 | 158 | rule HexEscape 159 | { 160 | strings: 161 | $text_string = "foo\x00bar" 162 | 163 | condition: 164 | $text_string 165 | } 166 | -------------------------------------------------------------------------------- /tests/data/detect_dependencies_ruleset.yar: -------------------------------------------------------------------------------- 1 | /* 2 | License: 3 | This file contains rules licensed under the GNU-GPLv2 license (http://www.gnu.org/licenses/gpl-2.0.html) 4 | Version 1-20180211, author:unixfreaxjp 5 | */ 6 | 7 | private rule is__osx 8 | { 9 | meta: 10 | date = "2018-02-12" 11 | author = "@unixfreaxjp" 12 | condition: 13 | uint32(0) == 0xfeedface or uint32(0) == 0xcafebabe 14 | or uint32(0) == 0xbebafeca or uint32(0) == 0xcefaedfe 15 | or uint32(0) == 0xfeedfacf or uint32(0) == 0xcffaedfe 16 | } 17 | 18 | private rule priv01 { 19 | meta: 20 | date = "2018-02-11" 21 | author = "@unixfreaxjp" 22 | strings: 23 | $vara01 = { 73 3A 70 3A 00 } 24 | $vara02 = "Usage: %s" fullword nocase wide ascii 25 | $vara03 = "[ -s secret ]" fullword nocase wide ascii 26 | $vara04 = "[ -p port ]" fullword nocase wide ascii 27 | condition: 28 | all of them 29 | } 30 | 31 | private rule priv03 { 32 | meta: 33 | date = "2018-02-10" 34 | author = "@unixfreaxjp" 35 | strings: 36 | $varb01 = { 41 57 41 56 41 55 41 54 55 53 0F B6 06 } 37 | $varb02 = { 48 C7 07 00 00 00 00 48 C7 47 08 00 00 } 38 | $vard01 = { 55 48 89 E5 41 57 41 56 41 55 41 54 53 } 39 | $vard02 = { 55 48 89 E5 48 C7 47 08 00 00 00 00 48 } 40 | // can be added 41 | condition: 42 | (2 of ($varb*)) or (2 of ($vard*)) 43 | } 44 | rule MALW_TinyShell_backconnect_OSX { 45 | meta: 46 | date = "2018-02-10" 47 | author = "@unixfreaxjp" 48 | condition: 49 | is__osx 50 | and priv01 51 | and priv02 52 | and priv03 53 | and priv04 54 | and filesize < 100KB 55 | } 56 | 57 | rule MALW_TinyShell_backconnect_ELF { 58 | meta: 59 | date = "2018-02-10" 60 | author = "@unixfreaxjp" 61 | condition: 62 | is__elf 63 | and priv01 64 | and ((priv02) 65 | or ((priv03) 66 | or (priv04))) 67 | and filesize < 100KB 68 | } 69 | 70 | rule MALW_TinyShell_backconnect_Gen { 71 | meta: 72 | date = "2018-02-11" 73 | author = "@unixfreaxjp" 74 | condition: 75 | ((is__elf) or (is__osx)) 76 | and priv01 77 | and priv02 78 | and filesize < 100KB 79 | } 80 | 81 | rule MALW_TinyShell_backdoor_Gen { 82 | meta: 83 | date = "2018-02-11" 84 | author = "@unixfreaxjp" 85 | condition: 86 | ((is__elf) or (is__osx)) 87 | and priv01 88 | and filesize > 20KB 89 | } 90 | 91 | rule test_rule_01 { 92 | condition: 93 | (is__elf) 94 | } 95 | 96 | rule test_rule_02 { 97 | condition: 98 | is__osx and is__elf 99 | } 100 | 101 | rule test_rule_03 { 102 | condition: 103 | is__osx 104 | } 105 | 106 | rule test_rule_04 { 107 | condition: 108 | (is__elf or is__osx) 109 | } 110 | 111 | rule loop1 112 | { 113 | strings: 114 | $a = "dummy1" 115 | $b = "dummy2" 116 | 117 | condition: 118 | is__osx and 119 | for all i in (1,2,3) : ( @a[i] + 10 == @b[i] ) 120 | } 121 | 122 | rule ExternalVariableExample3 123 | { 124 | condition: 125 | string_ext_var contains "text" 126 | } 127 | 128 | rule ExternalVariableExample4 129 | { 130 | condition: 131 | string_ext_var matches /[a-z]+/ 132 | } 133 | 134 | rule ExternalVariableExample3 135 | { 136 | condition: 137 | is__osx and 138 | string_ext_var contains "text" 139 | } 140 | 141 | rule ExternalVariableExample4 142 | { 143 | condition: 144 | is__osx and 145 | string_ext_var matches /[a-z]+/ 146 | } 147 | 148 | private rule WINDOWS_UPDATE_BDC 149 | { 150 | condition: 151 | (uint32be(0) == 0x44434d01 and // magic: DCM PA30 152 | uint32be(4) == 0x50413330) 153 | or 154 | (uint32be(0) == 0x44434401 and 155 | uint32be(12)== 0x50413330) // magic: DCD PA30 156 | } 157 | 158 | rule SndVol_ANOMALY { 159 | strings: 160 | $s1 = "Volume Control Applet" fullword wide 161 | condition: 162 | filename == "sndvol.exe" 163 | and uint16(0) == 0x5a4d 164 | and not 1 of ($s*) 165 | and not WINDOWS_UPDATE_BDC 166 | } 167 | 168 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Python Section 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # Environments 83 | .env 84 | .venv 85 | env/ 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | .spyproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | # mkdocs documentation 97 | /site 98 | 99 | # mypy 100 | .mypy_cache/ 101 | 102 | ## Sublime Text Section 103 | # Cache files for Sublime Text 104 | *.tmlanguage.cache 105 | *.tmPreferences.cache 106 | *.stTheme.cache 107 | 108 | # Workspace files are user-specific 109 | *.sublime-workspace 110 | 111 | # Project files should be checked into the repository, unless a significant 112 | # proportion of contributors will probably not be using Sublime Text 113 | *.sublime-project 114 | 115 | # sftp configuration file 116 | sftp-config.json 117 | 118 | # Package control specific files 119 | Package Control.last-run 120 | Package Control.ca-list 121 | Package Control.ca-bundle 122 | Package Control.system-ca-bundle 123 | Package Control.cache/ 124 | Package Control.ca-certs/ 125 | Package Control.merged-ca-bundle 126 | Package Control.user-ca-bundle 127 | oscrypto-ca-bundle.crt 128 | bh_unicode_properties.cache 129 | 130 | # Sublime-github package stores a github token in this file 131 | # https://packagecontrol.io/packages/sublime-github 132 | GitHub.sublime-settings 133 | 134 | ## Archive Section 135 | # It's better to unpack these files and commit the raw source because 136 | # git has its own built in compression methods. 137 | *.7z 138 | *.jar 139 | *.rar 140 | *.zip 141 | *.gz 142 | *.tgz 143 | *.bzip 144 | *.bz2 145 | *.xz 146 | *.lzma 147 | *.cab 148 | 149 | # Packing-only formats 150 | *.iso 151 | *.tar 152 | 153 | # Package management formats 154 | *.dmg 155 | *.xpi 156 | *.gem 157 | *.egg 158 | *.deb 159 | *.rpm 160 | *.msi 161 | *.msm 162 | *.msp 163 | 164 | ## Vagrant Section 165 | .vagrant/ 166 | 167 | ## Vi Section 168 | # Swap 169 | [._]*.s[a-v][a-z] 170 | [._]*.sw[a-p] 171 | [._]s[a-v][a-z] 172 | [._]sw[a-p] 173 | 174 | # Session 175 | Session.vim 176 | 177 | # Temporary 178 | .netrwhist 179 | *~ 180 | 181 | # Auto-generated tag files 182 | tags 183 | 184 | ## Virtualenv Section 185 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 186 | .Python 187 | #[Bb]in 188 | [Ii]nclude 189 | [Ll]ib 190 | [Ll]ib64 191 | [Ll]ocal 192 | [Ss]cripts 193 | pyvenv.cfg 194 | .venv 195 | pip-selfcheck.json 196 | 197 | ## macOS Section 198 | # General 199 | *.DS_Store 200 | .AppleDouble 201 | .LSOverride 202 | 203 | # Icon must end with two \r 204 | Icon 205 | 206 | # Thumbnails 207 | ._* 208 | 209 | # Files that might appear in the root of a volume 210 | .DocumentRevisions-V100 211 | .fseventsd 212 | .Spotlight-V100 213 | .TemporaryItems 214 | .Trashes 215 | .VolumeIcon.icns 216 | .com.apple.timemachine.donotpresent 217 | 218 | # Directories potentially created on remote AFP share 219 | .AppleDB 220 | .AppleDesktop 221 | Network Trash Folder 222 | Temporary Items 223 | .apdisk 224 | 225 | ## Local plyara Section 226 | parsetab.py 227 | test.yar 228 | test.py 229 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/stable/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = u'plyara' 23 | copyright = u'2020, plyara' 24 | author = u'plyara' 25 | 26 | # The short X.Y version 27 | version = u'' 28 | # The full version, including alpha/beta/rc tags 29 | release = u'' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.intersphinx', 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | # source_suffix = ['.rst', '.md'] 53 | source_suffix = '.rst' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | # 61 | # This is also used if you do content translation via gettext catalogs. 62 | # Usually you set "language" from the command line for these cases. 63 | language = None 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | # This pattern also affects html_static_path and html_extra_path . 68 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 69 | 70 | # The name of the Pygments (syntax highlighting) style to use. 71 | pygments_style = 'sphinx' 72 | 73 | 74 | # -- Options for HTML output ------------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. See the documentation for 77 | # a list of builtin themes. 78 | # 79 | html_theme = 'alabaster' 80 | 81 | # Theme options are theme-specific and customize the look and feel of a theme 82 | # further. For a list of options available for each theme, see the 83 | # documentation. 84 | # 85 | html_theme_options = { 86 | 'github_user': 'plyara', 87 | 'github_repo': 'plyara', 88 | 'github_button': 'true', 89 | 'github_type': 'star', 90 | 'description': 'Parse YARA rules into a dictionary representation.', 91 | 'logo_name': 'plyara', 92 | } 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | # Custom sidebar templates, must be a dictionary that maps document names 100 | # to template names. 101 | # 102 | # The default sidebars (for documents that don't match any pattern) are 103 | # defined by theme itself. Builtin themes are using these templates by 104 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 105 | # 'searchbox.html']``. 106 | # 107 | html_sidebars = { 108 | '**': [ 109 | 'about.html', 110 | 'localtoc.html', 111 | ] 112 | } 113 | 114 | 115 | # -- Options for HTMLHelp output --------------------------------------------- 116 | 117 | # Output file base name for HTML help builder. 118 | htmlhelp_basename = 'plyaradoc' 119 | 120 | 121 | # -- Options for LaTeX output ------------------------------------------------ 122 | 123 | latex_elements = { 124 | # The paper size ('letterpaper' or 'a4paper'). 125 | # 126 | # 'papersize': 'letterpaper', 127 | 128 | # The font size ('10pt', '11pt' or '12pt'). 129 | # 130 | # 'pointsize': '10pt', 131 | 132 | # Additional stuff for the LaTeX preamble. 133 | # 134 | # 'preamble': '', 135 | 136 | # Latex figure (float) alignment 137 | # 138 | # 'figure_align': 'htbp', 139 | } 140 | 141 | # Grouping the document tree into LaTeX files. List of tuples 142 | # (source start file, target name, title, 143 | # author, documentclass [howto, manual, or own class]). 144 | latex_documents = [ 145 | (master_doc, 'plyara.tex', u'plyara Documentation', 146 | u'plyara', 'manual'), 147 | ] 148 | 149 | 150 | # -- Options for manual page output ------------------------------------------ 151 | 152 | # One entry per manual page. List of tuples 153 | # (source start file, name, description, authors, manual section). 154 | man_pages = [ 155 | (master_doc, 'plyara', u'plyara Documentation', 156 | [author], 1) 157 | ] 158 | 159 | 160 | # -- Options for Texinfo output ---------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | (master_doc, 'plyara', u'Plyara Documentation', 167 | author, 'plyara', 'Parse YARA rules and operate over them more easily.', 168 | 'Miscellaneous'), 169 | ] 170 | 171 | 172 | # -- Extension configuration ------------------------------------------------- 173 | 174 | # -- Options for intersphinx extension --------------------------------------- 175 | 176 | # Example configuration for intersphinx: refer to the Python standard library. 177 | intersphinx_mapping = {'https://docs.python.org/': None} 178 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | plyara 2 | ====== 3 | 4 | .. image:: https://travis-ci.com/plyara/plyara.svg?branch=master 5 | :target: https://travis-ci.com/plyara/plyara 6 | :alt: Build Status 7 | .. image:: https://readthedocs.org/projects/plyara/badge/?version=latest 8 | :target: http://plyara.readthedocs.io/en/latest/?badge=latest 9 | :alt: Documentation Status 10 | .. image:: https://api.codacy.com/project/badge/Grade/7bd0be1749804f0a8dd3d57f69888f68 11 | :target: https://www.codacy.com/app/plyara/plyara 12 | :alt: Code Health 13 | .. image:: https://api.codacy.com/project/badge/Coverage/1c234b3d1ff349fa9dea7b4048dbc115 14 | :target: https://app.codacy.com/app/plyara/plyara 15 | :alt: Test Coverage 16 | .. image:: http://img.shields.io/pypi/v/plyara.svg 17 | :target: https://pypi.python.org/pypi/plyara 18 | :alt: PyPi Version 19 | 20 | Parse YARA_ rules into a dictionary representation. 21 | 22 | Plyara is a script and library that lexes and parses a file consisting of one more YARA rules 23 | into a python dictionary representation. The goal of this tool is to make it easier to perform 24 | bulk operations or transformations of large sets of YARA rules, such as extracting indicators, 25 | updating attributes, and analyzing a corpus. Other applications include linters and dependency 26 | checkers. 27 | 28 | Plyara leverages the Python module PLY_ for lexing YARA rules. 29 | 30 | This is a community-maintained fork of the `original plyara`_ by 8u1a_. The "plyara" trademark 31 | is used with permission. 32 | 33 | Installation 34 | ------------ 35 | 36 | Plyara requires Python 3.6+. 37 | 38 | Install with pip:: 39 | 40 | pip3 install plyara 41 | 42 | Usage 43 | ----- 44 | 45 | Use the plyara Python library in your own applications: 46 | 47 | .. code-block:: python 48 | 49 | >>> import plyara 50 | >>> parser = plyara.Plyara() 51 | >>> mylist = parser.parse_string('rule MyRule { strings: $a="1" \n condition: false }') 52 | >>> 53 | >>> import pprint 54 | >>> pprint.pprint(mylist) 55 | [{'condition_terms': ['false'], 56 | 'raw_condition': 'condition: false ', 57 | 'raw_strings': 'strings: $a="1" \n ', 58 | 'rule_name': 'MyRule', 59 | 'start_line': 1, 60 | 'stop_line': 2, 61 | 'strings': [{'name': '$a', 'type': 'text', 'value': '1'}]}] 62 | >>> 63 | 64 | Or, use the included ``plyara`` script from the command line:: 65 | 66 | $ plyara -h 67 | usage: plyara [-h] [--log] FILE 68 | 69 | Parse YARA rules into a dictionary representation. 70 | 71 | positional arguments: 72 | FILE File containing YARA rules to parse. 73 | 74 | optional arguments: 75 | -h, --help show this help message and exit 76 | --log Enable debug logging to the console. 77 | 78 | The command-line tool will print valid JSON output when parsing rules:: 79 | 80 | $ cat example.yar 81 | rule silent_banker : banker 82 | { 83 | meta: 84 | description = "This is just an example" 85 | thread_level = 3 86 | in_the_wild = true 87 | strings: 88 | $a = {6A 40 68 00 30 00 00 6A 14 8D 91} 89 | $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} 90 | $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" 91 | condition: 92 | $a or $b or $c 93 | } 94 | 95 | $ plyara example.yar 96 | [ 97 | { 98 | "condition_terms": [ 99 | "$a", 100 | "or", 101 | "$b", 102 | "or", 103 | "$c" 104 | ], 105 | "metadata": [ 106 | { 107 | "description": "This is just an example" 108 | }, 109 | { 110 | "thread_level": 3 111 | }, 112 | { 113 | "in_the_wild": true 114 | } 115 | ], 116 | "raw_condition": "condition:\n $a or $b or $c\n", 117 | "raw_meta": "meta:\n description = \"This is just an example\"\n thread_level = 3\n in_the_wild = true\n ", 118 | "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 ", 119 | "rule_name": "silent_banker", 120 | "start_line": 1, 121 | "stop_line": 13, 122 | "strings": [ 123 | { 124 | "name": "$a", 125 | "type": "byte", 126 | "value": "{6A 40 68 00 30 00 00 6A 14 8D 91}" 127 | }, 128 | { 129 | "name": "$b", 130 | "type": "byte", 131 | "value": "{8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9}" 132 | }, 133 | { 134 | "name": "$c", 135 | "type": "text", 136 | "value": "UVODFRYSIHLNWPEJXQZAKCBGMT" 137 | } 138 | ], 139 | "tags": [ 140 | "banker" 141 | ] 142 | } 143 | ] 144 | 145 | Reusing The Parser 146 | ------------------ 147 | 148 | If you want to reuse a single instance of the parser object for efficiency when 149 | parsing large quantities of rule or rulesets, the new clear() method must be 150 | used. 151 | 152 | .. code-block:: python 153 | 154 | rules = list() 155 | parser = plyara.Plyara() 156 | 157 | for file in files: 158 | with open(file, 'r') as fh: 159 | yararules = parser.parse_string(fh.read()) 160 | rules += yararules 161 | parser.clear() 162 | 163 | Migration 164 | --------- 165 | 166 | If you used an older version of plyara, and want to migrate to this version, 167 | there will be some changes required. Most importantly, the parser object 168 | instantiation has changed. It was: 169 | 170 | .. code-block:: python 171 | 172 | # Old style - don't do this! 173 | import plyara.interp as interp 174 | rules_list = interp.parseString(open('myfile.yar').read()) 175 | 176 | But is now: 177 | 178 | .. code-block:: python 179 | 180 | # New style - do this instead! 181 | import plyara 182 | parser = plyara.Plyara() 183 | rules_list = parser.parse_string(open('myfile.yar').read()) 184 | 185 | The existing parsed keys have stayed the same, and new ones have been added. 186 | 187 | When reusing a ``parser`` for multiple rules and/or files, be aware that 188 | imports are now shared across all rules - if one rule has an import, that 189 | import will be added to all rules in your parser object. 190 | 191 | Contributing 192 | ------------ 193 | 194 | * If you find a bug, or would like to see a new feature, Pull Requests and 195 | Issues_ are always welcome. 196 | * By submitting changes, you agree to release those changes under the terms 197 | of the LICENSE_. 198 | * Writing passing unit tests for your changes, while not required, is highly 199 | encouraged and appreciated. 200 | * Please run all code contributions through each of the linters that we use 201 | for this project: pycodestyle, pydocstyle, and pyflakes. See the 202 | .travis.yml file for exact use. For more information on these linters, 203 | please refer to the Python Code Quality Authority: 204 | http://meta.pycqa.org/en/latest/ 205 | 206 | Discussion 207 | ------------ 208 | 209 | * You may join our IRC channel on irc.freenode.net #plyara 210 | 211 | .. _PLY: http://www.dabeaz.com/ply/ 212 | .. _YARA: http://plusvic.github.io/yara/ 213 | .. _plyara.readthedocs.io: https://plyara.readthedocs.io/en/latest/ 214 | .. _original plyara: https://github.com/8u1a/plyara 215 | .. _8u1a: https://github.com/8u1a 216 | .. _Issues: https://github.com/plyara/plyara/issues 217 | .. _LICENSE: https://github.com/plyara/plyara/blob/master/LICENSE 218 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 Christian Buia 190 | Copyright 2020 plyara Maintainers 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /plyara/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2014 Christian Buia 3 | # Copyright 2020 plyara Maintainers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """plyara utility functions. 17 | 18 | This module contains various utility functions for working with plyara output. 19 | """ 20 | import hashlib 21 | import logging 22 | import re 23 | 24 | from plyara.core import Parser 25 | 26 | # Initialize the logger 27 | logger = logging.getLogger(__name__) 28 | 29 | 30 | def is_valid_rule_name(entry): 31 | """Check to see if entry is a valid rule name. 32 | 33 | Args: 34 | entry: String containing rule name. 35 | 36 | Returns: 37 | bool 38 | """ 39 | # Check if entry is blank 40 | if not entry: 41 | return False 42 | 43 | # Check length 44 | if len(entry) > 128: 45 | return False 46 | 47 | # Ensure doesn't start with a digit 48 | if entry[0].isdigit(): 49 | return False 50 | 51 | # Accept only alphanumeric and underscores 52 | if not re.match(r'[a-zA-Z_][a-zA-Z_0-9]*$', entry): 53 | return False 54 | 55 | # Verify not in keywords 56 | if entry in Parser.KEYWORDS: 57 | return False 58 | 59 | return True 60 | 61 | 62 | def is_valid_rule_tag(entry): 63 | """Check to see if entry is a valid rule tag. 64 | 65 | Args: 66 | entry: String containing tag. 67 | 68 | Returns: 69 | bool 70 | """ 71 | # Same lexical conventions as name 72 | return is_valid_rule_name(entry) 73 | 74 | 75 | def detect_imports(rule): 76 | """Take a parsed yararule and provide a list of required imports based on condition. 77 | 78 | Args: 79 | rule: Dict output from a parsed rule. 80 | 81 | Returns: 82 | list: Imports that are required. 83 | """ 84 | detected_imports = list() 85 | condition_terms = rule['condition_terms'] 86 | 87 | for imp in Parser.IMPORT_OPTIONS: 88 | imp_module = '{}.'.format(imp) 89 | 90 | for term in condition_terms: 91 | if term.startswith(imp_module): 92 | detected_imports.append(imp) 93 | break 94 | 95 | return detected_imports 96 | 97 | 98 | def detect_dependencies(rule): 99 | """Take a parsed yararule and provide a list of external rule dependencies. 100 | 101 | Args: 102 | rule: Dict output from a parsed rule. 103 | 104 | Returns: 105 | list: External rule dependencies. 106 | """ 107 | dependencies = list() 108 | string_iteration_variables = list() 109 | condition_terms = rule['condition_terms'] 110 | 111 | # Number of terms for index iteration and reference 112 | term_count = len(condition_terms) 113 | 114 | for index in range(0, term_count): 115 | # Grab term by index 116 | term = condition_terms[index] 117 | 118 | if is_valid_rule_name(term) and (term not in Parser.IMPORT_OPTIONS): 119 | # Grab reference to previous term for logic checks 120 | if index > 0: 121 | previous_term = condition_terms[index - 1] 122 | else: 123 | previous_term = None 124 | 125 | # Grab reference to next term for logic checks 126 | if index < (term_count - 1): 127 | next_term = condition_terms[index + 1] 128 | else: 129 | next_term = None 130 | 131 | # Extend term indexes beyond wrapping parentheses for logic checks 132 | if previous_term == '(' and next_term == ')': 133 | if (index - 2) >= 0: 134 | previous_term = condition_terms[index - 2] 135 | else: 136 | previous_term = None 137 | 138 | if (index + 2) < term_count: 139 | next_term = condition_terms[index + 2] 140 | else: 141 | next_term = None 142 | 143 | # Check if reference is a variable for string iteration 144 | if term in string_iteration_variables: 145 | continue 146 | 147 | if previous_term in ('any', 'all', ) and next_term == 'in': 148 | string_iteration_variables.append(term) 149 | continue 150 | 151 | # Check for external string variable dependency 152 | if next_term in ('matches', 'contains',) or previous_term in ('matches', 'contains',): 153 | continue 154 | 155 | # Check for external integer variable dependency 156 | if next_term in Parser.COMPARISON_OPERATORS or previous_term in Parser.COMPARISON_OPERATORS: 157 | continue 158 | 159 | # Check for external boolean dependency may not be possible without stripping out valid rule references 160 | 161 | # Checks for likely rule reference 162 | if previous_term is None and next_term is None: 163 | dependencies.append(term) 164 | elif previous_term in ('and', 'or', 'not', ) or next_term in ('and', 'or', 'not', ): 165 | dependencies.append(term) 166 | 167 | return dependencies 168 | 169 | 170 | def generate_logic_hash(rule): 171 | """Calculate hash value of rule strings and condition. 172 | 173 | Args: 174 | rule: Dict output from a parsed rule. 175 | 176 | Returns: 177 | str: Hexdigest SHA-256. 178 | """ 179 | import warnings 180 | warnings.warn( 181 | 'Utility generate_logic_hash() may be deprecated, see generate_hash()', 182 | PendingDeprecationWarning 183 | ) 184 | strings = rule.get('strings', list()) 185 | conditions = rule['condition_terms'] 186 | 187 | string_values = list() 188 | condition_mapping = list() 189 | string_mapping = {'anonymous': list(), 'named': dict()} 190 | 191 | for entry in strings: 192 | name = entry['name'] 193 | modifiers = entry.get('modifiers', list()) 194 | 195 | # Handle string modifiers 196 | if modifiers: 197 | value = '{}{}'.format(entry['value'], ' & '.join(sorted(modifiers))) 198 | else: 199 | value = entry['value'] 200 | 201 | if name == '$': 202 | # Track anonymous strings 203 | string_mapping['anonymous'].append(value) 204 | else: 205 | # Track named strings 206 | string_mapping['named'][name] = value 207 | 208 | # Track all string values 209 | string_values.append(value) 210 | 211 | # Sort all string values 212 | sorted_string_values = sorted(string_values) 213 | 214 | for condition in conditions: 215 | # All string references (sort for consistency) 216 | if condition == 'them' or condition == '$*': 217 | condition_mapping.append('{}'.format(' | '.join(sorted_string_values))) 218 | 219 | elif condition.startswith('$') and condition != '$': 220 | # Exact Match 221 | if condition in string_mapping['named']: 222 | condition_mapping.append('{}'.format(string_mapping['named'][condition])) 223 | # Wildcard Match 224 | elif '*' in condition: 225 | wildcard_strings = list() 226 | condition = condition.replace('$', r'\$').replace('*', '.*') 227 | pattern = re.compile(condition) 228 | 229 | for name, value in string_mapping['named'].items(): 230 | if pattern.match(name): 231 | wildcard_strings.append(value) 232 | 233 | wildcard_strings.sort() 234 | condition_mapping.append('{}'.format(' | '.join(wildcard_strings))) 235 | else: 236 | logger.error('[!] Unhandled String Condition {}'.format(condition)) 237 | 238 | # Count Match 239 | elif condition.startswith('#') and condition != '#': 240 | condition = condition.replace('#', '$') 241 | 242 | if condition in string_mapping['named']: 243 | condition_mapping.append('{}'.format(string_mapping['named'][condition])) 244 | else: 245 | logger.error('[!] Unhandled String Count Condition {}'.format(condition)) 246 | 247 | else: 248 | condition_mapping.append(condition) 249 | 250 | logic_hash = hashlib.sha256(''.join(condition_mapping).encode()).hexdigest() 251 | return logic_hash 252 | 253 | 254 | def generate_hash(rule, secure_hash=None): 255 | """Calculate a secure hash of the logic in the rule strings and condition. 256 | 257 | If the resultant hashes are identical for two YARA rules, the rules will match on identical content. 258 | The reverse it not true, so two rules that match the same content may not generate the same hash. 259 | For example, if a rule only contains one string, the logic for 'any of' and 'all of' generate different hashes, 260 | but the rules contain the same logic. 261 | 262 | Args: 263 | rule: Dict output from a parsed rule. 264 | secure_hash: Alternate hash function, defaults to SHA-256 265 | 266 | Returns: 267 | str: hexdigest 268 | """ 269 | condition_string_prefaces = ('$', '!', '#', '@') 270 | if secure_hash is None: 271 | hf = hashlib.sha256() 272 | else: 273 | hf = secure_hash() 274 | 275 | strings = rule.get('strings', list()) 276 | conditions = rule['condition_terms'] 277 | 278 | string_values = list() 279 | condition_mapping = list() 280 | string_mapping = {'anonymous': list(), 'named': dict()} 281 | 282 | for entry in strings: 283 | name = entry['name'] 284 | modifiers = entry.get('modifiers', list()) 285 | 286 | if entry['type'] == 'byte': 287 | value = re.sub(r'[^-a-fA-F?0-9\[\]{}]+', '', entry['value']) 288 | elif entry['type'] == 'text': 289 | value = '{}'.format(entry['value']) 290 | else: 291 | value = entry['value'] 292 | 293 | # Handle string modifiers 294 | if modifiers: 295 | value += '{}'.format(' & '.join(sorted(modifiers))) 296 | 297 | if name == '$': 298 | # Track anonymous strings 299 | string_mapping['anonymous'].append(value) 300 | else: 301 | # Track named strings 302 | string_mapping['named'][name] = value 303 | 304 | # Track all string values 305 | string_values.append(value) 306 | 307 | # Sort all string values 308 | string_values.sort() 309 | 310 | for condition in conditions: 311 | # All string references (sort for consistency) 312 | if condition == 'them' or condition == '$*': 313 | all_values = '{}'.format(' | '.join(string_values)) 314 | if condition == 'them': 315 | condition_mapping.extend(['(', all_values, ')']) 316 | else: 317 | condition_mapping.append(all_values) 318 | 319 | elif condition.startswith('$') and condition != '$': 320 | # Exact Match 321 | if condition in string_mapping['named']: 322 | condition_mapping.append('{}'.format(string_mapping['named'][condition])) 323 | # Wildcard Match 324 | elif '*' in condition: 325 | wildcard_strings = list() 326 | condition = condition.replace('$', r'\$').replace('*', '.*') 327 | pattern = re.compile(condition) 328 | 329 | for name, value in string_mapping['named'].items(): 330 | if pattern.match(name): 331 | wildcard_strings.append(value) 332 | 333 | wildcard_strings.sort() 334 | condition_mapping.append('{}'.format(' | '.join(wildcard_strings))) 335 | else: 336 | logger.error('[!] Unhandled String Condition "{}" in "{}"'.format(condition, ' '.join(conditions))) 337 | 338 | # Count Match 339 | elif condition[:1] in condition_string_prefaces and condition not in ('#', '!='): 340 | symbol = condition[:1] 341 | condition = '${}'.format(condition[1:]) 342 | if symbol == '#': 343 | symbol_type = 'COUNTOFSTRING' 344 | elif symbol == '@': 345 | symbol_type = 'POSITIONOFSTRING' 346 | elif symbol == '!': 347 | symbol_type = 'LENGTHOFSTRING' 348 | elif symbol == condition == '$': 349 | symbol_type = 'ANONYMOUSSTRING' 350 | else: 351 | symbol_type = 'UNKNOWN' 352 | 353 | if condition in string_mapping['named']: 354 | condition_mapping.append('<{}>{}'.format(symbol_type, string_mapping['named'][condition])) 355 | else: 356 | condition_mapping.append('<{}>{}'.format(symbol_type, condition)) 357 | logger.error('[!] Unhandled {} Condition "{}" in "{}"'.format( 358 | symbol_type, symbol, ' '.join(conditions)) 359 | ) 360 | 361 | else: 362 | condition_mapping.append(condition) 363 | hf.update(''.join(condition_mapping).encode()) 364 | hexdigest = hf.hexdigest() 365 | 366 | return hexdigest 367 | 368 | 369 | def rebuild_yara_rule(rule, condition_indents=False): 370 | """Take a parsed yararule and rebuild it into a usable one. 371 | 372 | Args: 373 | rule: Dict output from a parsed rule. 374 | condition_indents: Use nested indentation for condition 375 | 376 | Returns: 377 | str: Formatted text string of YARA rule. 378 | """ 379 | rule_format = "{imports}{scopes}rule {rulename}{tags}\n{{{meta}{strings}{condition}\n}}\n" 380 | 381 | rule_name = rule['rule_name'] 382 | 383 | # Rule Imports 384 | if rule.get('imports'): 385 | unpacked_imports = ['import "{}"\n'.format(entry) for entry in rule['imports']] 386 | rule_imports = '{}\n'.format(''.join(unpacked_imports)) 387 | else: 388 | rule_imports = str() 389 | 390 | # Rule Scopes 391 | if rule.get('scopes'): 392 | rule_scopes = '{} '.format(' '.join(rule['scopes'])) 393 | else: 394 | rule_scopes = str() 395 | 396 | # Rule Tags 397 | if rule.get('tags'): 398 | rule_tags = ' : {}'.format(' '.join(rule['tags'])) 399 | else: 400 | rule_tags = str() 401 | 402 | # Rule Metadata 403 | if rule.get('metadata'): 404 | unpacked_meta = [] 405 | kv_list = [(k, ) + (v, ) for dic in rule['metadata'] for k, v in dic.items()] 406 | 407 | # Check for and handle correctly quoting string metadata 408 | for k, v in kv_list: 409 | if isinstance(v, bool): 410 | v = str(v).lower() 411 | elif isinstance(v, int): 412 | v = str(v) 413 | else: 414 | v = '"{}"'.format(v) 415 | unpacked_meta.append('\n\t\t{key} = {value}'.format(key=k, value=v)) 416 | rule_meta = '\n\tmeta:{}\n'.format(''.join(unpacked_meta)) 417 | else: 418 | rule_meta = str() 419 | 420 | # Rule Strings 421 | if rule.get('strings'): 422 | 423 | string_container = list() 424 | 425 | for rule_string in rule['strings']: 426 | if 'modifiers' in rule_string: 427 | string_modifiers = [x for x in rule_string['modifiers'] if isinstance(x, str)] 428 | 429 | if rule_string['type'] == 'text': 430 | string_format = '\n\t\t{} = "{}" {}' 431 | else: 432 | string_format = '\n\t\t{} = {} {}' 433 | fstring = string_format.format(rule_string['name'], rule_string['value'], ' '.join(string_modifiers)) 434 | 435 | else: 436 | if rule_string['type'] == 'text': 437 | string_format = '\n\t\t{} = "{}"' 438 | else: 439 | string_format = '\n\t\t{} = {}' 440 | fstring = string_format.format(rule_string['name'], rule_string['value']) 441 | 442 | string_container.append(fstring) 443 | 444 | rule_strings = '\n\tstrings:{}\n'.format(''.join(string_container)) 445 | else: 446 | rule_strings = str() 447 | 448 | if rule.get('condition_terms'): 449 | # Format condition with appropriate whitespace between keywords 450 | cond = list() 451 | indents = '\n\t\t' 452 | for term in rule['condition_terms']: 453 | 454 | if condition_indents: 455 | if term == '(': 456 | indents = indents + '\t' 457 | if term == ')' and len(indents) > 3: 458 | indents = indents[:-1] 459 | 460 | if not cond: 461 | 462 | if term in Parser.FUNCTION_KEYWORDS: 463 | cond.append(term) 464 | 465 | elif term in Parser.KEYWORDS: 466 | cond.append(term) 467 | cond.append(' ') 468 | 469 | else: 470 | cond.append(term) 471 | 472 | else: 473 | 474 | if cond[-1][-1] in (' ', '\t') and term in Parser.FUNCTION_KEYWORDS: 475 | cond.append(term) 476 | 477 | elif cond[-1][-1] not in (' ', '\t') and term in Parser.FUNCTION_KEYWORDS: 478 | cond.append(' ') 479 | cond.append(term) 480 | 481 | elif cond[-1][-1] in (' ', '\t') and term in Parser.KEYWORDS: 482 | cond.append(term) 483 | cond.append(' ') 484 | if condition_indents and term in ('and', 'or'): 485 | cond.append(indents) 486 | 487 | elif cond[-1][-1] not in (' ', '\t') and term in Parser.KEYWORDS: 488 | cond.append(' ') 489 | cond.append(term) 490 | cond.append(' ') 491 | if condition_indents and term in ('and', 'or'): 492 | cond.append(indents) 493 | 494 | elif cond[-1][-1] in (' ', '\t') and term == ':': 495 | cond.append(term) 496 | cond.append(' ') 497 | 498 | elif cond[-1][-1] not in (' ', '\t') and term == ':': 499 | cond.append(' ') 500 | cond.append(term) 501 | cond.append(' ') 502 | 503 | else: 504 | cond.append(term) 505 | 506 | fcondition = ''.join(cond).rstrip(' ') 507 | rule_condition = '\n\tcondition:{}{}'.format('\n\t\t', fcondition) 508 | else: 509 | rule_condition = str() 510 | 511 | formatted_rule = rule_format.format(imports=rule_imports, 512 | rulename=rule_name, 513 | tags=rule_tags, 514 | meta=rule_meta, 515 | scopes=rule_scopes, 516 | strings=rule_strings, 517 | condition=rule_condition) 518 | 519 | return formatted_rule 520 | -------------------------------------------------------------------------------- /tests/data/rulehashes.txt: -------------------------------------------------------------------------------- 1 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 2 | d625c96fb568e0c09a1dd30ffb7131fdc3283beb3765efabb1550b7749534f28 3 | 6e4235b85c28a5588e4aee487208fffe3396252ca3b8fbd8b380be2aa7ffaba8 4 | 48d5066ef3a8892e8b46d265c7965c7d7e32c94eda6fda9a22bb5ba8511c9ba5 5 | fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa 6 | 35c7123f53f3ff008f2c2b4a597dfb21a201d42e231f5c4d079858f1c107097a 7 | 7e60301adaa02cff63834e821e2fba8e13aeb0cbc3d40f35911a71598e844657 8 | 337fedbc074ec447a95ba4b50e36f11dbbca513821e939a7f5779a0bdb980032 9 | cafcf706500f101c7c2c3361c43e7ce85d3aa839e73443f0b6c943041c2c52a7 10 | 6e678a811f3695e52111271d3aa97189cfcf8d230ca67dc2d442ff851e417cb5 11 | 233ab605c528c99f351f0a70b7b5b685fe78fa3ee7629ea041cf45343173624c 12 | ab28b5e9c550a0c41f837544c56de18696c363a3b7d5f80f82a28783c573f167 13 | 31497df25b6b1557dccb9fe11b20ee00b867d30f5a4e9b5a83275a71371cdabc 14 | 21a4f89e07b0d233a4e536e600b22740da6e87ebac4efe9221561f763d20347d 15 | d5ed2224d36719855f93d535a6305dd80a2e585211b82504efcd5464b17d2e3d 16 | 24e897d1960c73fc2c0ccb8539189123381fa72e65981468c5cde4c6e7f44f79 17 | 23414016f63d10cf38e28a8a7853b0be821984f2eb3b3e801abbdcb7413e31e4 18 | 3f0459a62a3437d3da09fa05491214c33360e90da77416bb0eaaa4bd3ad73b4a 19 | 744c817b4cfb184693f02e5d320f5fea20c06027b9501c34c9ca2c138c8f1719 20 | eec9e3a8fe93a554edad5559bf73faa2ea8448e416e50af063807cdbea1282d6 21 | 850f7b8ab28ae4fd099de78cb5f652d212bb34e20be0d4c4297202993cd90686 22 | fa69df58aff909bcb74bf01b0211e775f4a18b33f010368f9dde8339a306d274 23 | 3390d9d8d6d030dab5d1f9910985d75e02bf168adfbe0e131c447603b21a8dba 24 | c7c6d5a6cd37fbc61bb0273aa3cae578855f1695fe8489e35572ecda1644e153 25 | 9ad757632d401c10e85117cda6a91e1243bf33fe65822db7788ed31ba17979d2 26 | 52f4dcdbbe287eada284a7ac870d7850455f9316692d2a783cd85dfac89e7f21 27 | dd0aa31717bacb2f692a72a6f58088a7c97d35b4b0f35c1a5cfed41c7d4bc8e0 28 | 2caa7c62df6c10bc55dfcbca4df69bbb035101bd5e90f2db95a92b952ad72743 29 | e50919daa2cbfd3410a2cf936804dbebf5ef8e26e06e036f93c6e781e410e5d4 30 | 978521088871b4476b78edc63ce97d323e53b7e63036c69ee915c9b627062106 31 | fece85a1563e5d9c30ab923148f7332a779212184ac06b406d03bd9b31df3507 32 | a46a61c2c9004858a99c68e1d8b417e582ed9eccd64d3b066ef6a182abdfd6ee 33 | b6a7c7c3b8a87d2238d1f678c2e77affd6ddd59aed0c343061da4c3d2e5a5c7b 34 | b6058483da2793a09cdaefa90ac4f678849d46f7ef8972a06f8f10000e6c7da8 35 | f1e2aee6541de036c6e5de385798a2cf3ff1c74b6b89ae5b65d92c884cbd0b81 36 | 93ce1adb7bceb7a0852c4de97b293a9d8c770d676b2ecd7c38059d80a77fbb8a 37 | a2bd69c2b74a634b8e596a2ced695f4ecf84facef2201d5c48321f3a6b82db73 38 | d0d7621722b19407c20ad8c8cfbc7ffc926e68ca6bc18b31da2b0a8119264665 39 | 14b3670ce59c25a7af65d7d043530028da62badaa5f44a448a9a19491972a720 40 | b49fa4eadfba58752c83a7aed680d48498febcb945c1fd75c45d6addbfa328da 41 | 59233c200a123f105877fe3b4497b68ef744594e58295f43bf37bdea3bed7af0 42 | f7fd2c110d24c4d4f5a78f03fde32a4833d3ffc7443171268d46da8fa910ac68 43 | 6d36773a867e51b78dc2c48f1332d9a4041fe3f2665ad7698bc63f6df6925d9d 44 | a6565988a60034e0e58a2515f27502573569ae0d6de0aaccd7ff61f9c6742999 45 | 77110ef9e36e7ecbcdc7c94c6a94740c48d5d1fdc9f50d372b4b4fea7b4db0bd 46 | 0937e7c5e5be2fcd0923aa9a964ddb4b197c24c3c3c212242bd2eae41e8c07dc 47 | 858bc0c9ab0b5eeae5bcd9f9e23c230bddcb6c57e639f8677c564b7cb82d1f37 48 | 34ed4ea989005f3a52429c683ff3885c62cd61377006cf3e516485cf71b3e435 49 | 454320053a83d8cf3fed72ccddf8a8c3ebcb4b9c88893c3d182281081fda5352 50 | 233918f5b8c0992b20ef5b6508cb4e0c9a777528a7c86150c75bcdbaa1200c0f 51 | f7d8471b7b6cebabf76bcace1a3d984b067444d9ee4cc01b9eebc6b94b61d62c 52 | 4eefab5519d908aa13b9f66ad161c3f555e395c17605262bb08c618017aa3ba8 53 | 7a344f3f20d58c33df3ab2803c57c13e4b03a52537819aee002a7798d2f3c051 54 | 8fc690c1356029c213d06ad0b12a161b019ba4fe49f559b0827251a91a8977eb 55 | 622b4b2cdab5a85f10562b12f93c19b6a7e8c9795aab0a02602c4f77e4f1f97a 56 | 5867a8cd0d5d2ff73ea4262eff75e032f0143ee96f23d1a8ac11f14afa50d228 57 | 33f391851dc7dbd13e244e65d7e4b03f37a0b953e28cb3ac2cd7e12605a18df2 58 | 1332a1c0f8bdb7856c1eaaac3e9232fcf85d3ebb0278207f68c0f2022e19c170 59 | d29abf9dc66fc3e211078947f10bd8de5df1e841e3c5eacd1ddb23ae27cc9588 60 | 8344a0ee5758dcbb11f3324889b000fce105c3fd751d3b4630681db0e6c5c797 61 | 438b7e7ba068932af2347af0caf7642fc7614dee240323f81e0db8b71af5531e 62 | b4c70e39cbfae94108c3f6817301e46a8d5d5175988760739a38b2591ec4e73c 63 | 855f0f0b68cd069648114327dc7e46e4675b9bfaefa6fdae5577b521dbdb3a5d 64 | 59c0b5e5001a5b5970783081cf2fb82251e8020300e5571e970160eedce9d11a 65 | ea6ba6b705a6cddf11fb0af4f52cea3ec45c2aead28dbd16f20b0e2b54bff2fd 66 | 9c744cafd13de55ef58c031ccb1695425e33cc3f6eeee7618cefefc62fd565d7 67 | 94c5b8876151ce981171db9bd5b305fc69aac566403601714f12dcd4791b6e51 68 | bc4aba77900a563f21975e3be936786a4bbe9eec2d61546ccba548ad973dcee5 69 | 80fd1b14e764bc6181b4920cd8e3f7c0d567bce37b818d067e66341026b0c3f2 70 | 4b65099b1bcbdd2aaecd3e9b917c6d76dbeb9a367ab6e15c73ad624f15efbb1b 71 | 1b5146472ca54388a973229defce53f0319b90d2ca73ff6c739c24ff2b2a5fe0 72 | b61e5fcd412651889aafdc83a6a36195ef8806cee320dfef3a28d5b057707424 73 | 56f31be82f5fa1ed932b42574c91b5d57866eece83193afe01551033d340d5af 74 | 8a562ac9831e27743fa69622072726c91cf593ef8cd65380e2b6208045218e03 75 | 76c38b6b9c91c970140eb5aca4ce7aa1475f55d9e8b2f1fc04b51f5eb96bab63 76 | cb941ffac208d3617d17f2ffe812808eb00f7533b1bd817388bbb64c5cef6fbf 77 | e5527ad7feda7128a8da438e1b44b4e56ff2db621f1b2d10c98e006614cdd3a9 78 | 823ce68a445823098e6b7acb18c70318c95f7853de2d8c5968a530b53c266464 79 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 80 | 28d94acbee4119701830f2a5bde19c0b4e62157dcf7b526a9a16d96bedc6e861 81 | 2cb2837744913f3504de915e72cd4905672c4f648f3a82954d6cb3213cd7d88b 82 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 83 | 448c13819aefbd6cb88a0baef7d195cdc299c766c98ef65fa2d26cf9103886a3 84 | 0ee55483f4da8116aa12790859016fc499ff139e5c3bbeaf58936780bb1d5194 85 | 42c5c50301f7a09feccbc6733f8258d377112c8a6194a0f1a152a0c02f025742 86 | a3fa72ced02134135a4b597e3e1b4e8b1509c63d7cc7875b3815ec861401c587 87 | b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 88 | 0f083d0561f6293c712b219dd7876101bc35622366715967277b3fea9e796677 89 | d7d55842bb7847ea099d3ccb9ad7d4f9ea5005a6b54c1cfba08223af81bea44e 90 | 2d900fe9142d858d025e58bce6777839c3ac56b08f91db1df01ffb56c00ce78b 91 | 61c54d7eab99ca2b145279c4cdc79cb7a7362f09b998271cf8b4aa851cff7e25 92 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 93 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 94 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 95 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 96 | b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 97 | 3b7d00c8bc4d8d9927ab67cd945bf6a06ab2b35e53f7213612db44bcb2211c52 98 | 57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 99 | 57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 100 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 101 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 102 | b4b177dddaa32cefe81e39074ec09e9863613a48589d20289ffe1e48c6475f59 103 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 104 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 105 | 1d9857c194ac02b204ab40f6e410597b88c8e9fea398977d243832d3f27af8cc 106 | 79535bcef9252a3f988515bd33a963ff5db119b3b42c6c6b263f60fa6bf0fff7 107 | a8c95988f4ee0d3a8347bc71347d8d3e0144663034ffd4ce3a61c96c7378c247 108 | 61507c8074e56e0bb9dc6e17b87dfc0f27cddec39f8ee5fea66da282af5dbd7f 109 | ff0159a9e24f5bed4486fc206b87e6861ace224b69cbb2a15a3660d6c1c78ab2 110 | 8d8937a492ee8bf6b7752acf833f040f5d0df54f25416c6e34f55ac034243c50 111 | 534e2bbec22b7fdc5bcf64d1aa9c4ba18511b3bc64af76c333954c663c166f53 112 | 373e1cca8482e2f91fc8c7f24871de37a20cd0d0157957668a17f518adda954a 113 | 7055d18035295d7e79f5fb7d48cec88241006f26cdd3a4a56e38634f03caed4b 114 | b98960cdf0dc1e4341ed411bf35ca9d2a5d5930a0f6fe9339b1777a475e3b671 115 | 6f7e951df4257cd339ee5d0dc753bc4b5c84d4ef377a2abb429e90bd2bc249c0 116 | 4a5802a54145b5c528f28060ebba76f99b09e38d5c85453b41bd39333f548dc0 117 | ea1fbccbc92d8a893a08b8725eda9708fff27201809c4bb54c584bd4590053f2 118 | e9aecb612643021ba59416b8acba38b18311f33507e9de4290ec73b17ac79f6d 119 | f77b3e9a82e7d8fee7e90a441946cde5bb34e9ca140dfd9867ae1969df132e8f 120 | 4137b404f831ad09946d9670aab06eaa79efb5a1f8e5d9bec788244a8b91b416 121 | 0ee02a3dd03826273764d6c8d99e084f0e43d0a9a8c1f4fef9e7be6ebf076235 122 | d2c6532a650dfdb5add9ac35ac2812abc79901bb53efceeda811f66f6f36e6bc 123 | bdd70e3a96a094b10114cf2de544513221bdf4817b372d3673d9812bc3014706 124 | 602acca95d424aa7b3bc303ea6f48468f150bf3446237afadb664f9e5d43ea96 125 | 0ee02a3dd03826273764d6c8d99e084f0e43d0a9a8c1f4fef9e7be6ebf076235 126 | 07b4f1381e939ff4863946df80415382b5571d61e5802b4fa22f94b262837698 127 | a38b2b1f0d8743d7e7842d2e828ea00c491b4a2fec94a4964e3fc04455716e30 128 | a3427bc7cad83a0310619dc84d6ab03170424a24c96c957950ba8a86044d1fa4 129 | efd56f96a8a9280e841cfc54773b53e88a7654855f1d14224f1367114cda01f8 130 | fdd9520246f00efb95b1dde38571e56f2fe636bface44d5e99da73317a536031 131 | 10733fffd6cb51f75729733b160fbb6eb95ef10b6ad63f0dc83717df0d4f3645 132 | 8b45632c84dffa3b00c1b5165c71f88d83535570e0b64bed51885b7db937e189 133 | bcb52f695698f6d87c4028588b60e87d7fd3e4ec033a9752d1300e3aa170826a 134 | e2aec810a7e0fd6c7bf95777699258c1be5032ca037c1ecc9c21ff0e58f9815e 135 | 1f147d73dc5b2a432c26a4b2b8bc4f59000556e343313232d7dd8f41c0c7c9f1 136 | a82c5f118c138990383dc8385dd645809b9bb9f4fa13e51196a0572b8103b582 137 | 1a12f768609126da3937b4aaf53ea0d2f1c7945cff1f8371ae9ac8128eba82ab 138 | 87df8988524de69102e410b5e33bd5a2333762994963fb6ee2b731c8aec9f6f5 139 | e51821108d9312a616571a24bac528dd67f7ec625a2fd28509f4690fd7512baa 140 | f20a932dbf6502ccef7ec8494aa226ade59c1f9a988c01c7a8dcfb5aae3ea159 141 | 93d9037e7529cd7e50573d7d36222ae2db2baab6173f95774c4f1fcd2644d565 142 | dba3ea7749ce8afa934d7d3dd55eff8afe095f09e33e69836118d494eac0df9b 143 | 373ef4ea5952433e6ec8a1ea7c5bda6e4edd69d62d6f2dbee68d2c8b9a760713 144 | 000fd941376b243d4ce19839aa6e0798c610da99c47bc0cbbea3a81b967a4ace 145 | 141b164cab2d321f3f07a1e2ea68bc04bf9073aa5c2ad701c212e415eb2dc65c 146 | 77ae236316021cf25914361fbb16ff4d3f24951edebd6cf6aa652c26654d1d69 147 | e462fcbef2160111b45e2de3aaf99c4c1daa2aa71c5e64ff307702e453d1a5b5 148 | 5584764a0fbdddde431909b5e84568c0ec4308a27bf6b93230984caff3cc05c4 149 | e5ac97648e6fcb58cae4be7fcf39229a5700cb95086098faa5eadc4a8ba0a238 150 | 777bf0f5bcfc3bd8233eb93dd6ec29a03c9fd780c706234a9cb856a2306d29c2 151 | e0379d82f6be001031c63966c98cd9e6a40fe4e4573b80b79949b4245aac65dd 152 | 98c6a60db8d5638e36acb79d36a0ecd759e238b051eba2f5cb2968d593ea8b84 153 | 36ffd44421550f5d49155500ac4c3ea949eba8926aa6c28a12708b73df4d2dbe 154 | 2096e4cd078307e9f6dfd5896f7983afc84810332c43a0e13f9c75ab4a5e1fff 155 | 2a2ca2716342acde21cdb2139ba341d8cd5d1ad19bf86577fe2478ac4c9f75a4 156 | 75190af954fbb55013a946d5c89c362a7f8893f0277ce97187eb5c95995e6f2f 157 | 25fb21923178b636cf351ca8cf1db0bb402a44b539179a0a8bf82fd86bb61f7f 158 | 90255ab5a0bf48636e14bfbf5ef457fb7bdb7f3b852b97f6296c0d0aa5d597c6 159 | 65c86b2d669db528d4899bf28b842557403bee2b4caadaa97856fd9fad51cddf 160 | 82e13ec666ef22eafac1e0fc9819256c9b2e54c6144e1195c40e9f273116825d 161 | 08eefdf3eb717d2da91eea6578dd13aeee6b91f60160a1476db39e59a23c9b89 162 | cb43bd091f9c115755381f011d55758b9cfbb168252a9cf214082113d352d34f 163 | 90731b250381c7f5fd78a1d0ab090f84c2771490209a6aa15379a2a2cbfdbfd0 164 | 9c4759f03806f3e1fb335a842f42aa5b95f2f99662bb1b05996f81644b7a002c 165 | e7c1f9c8fcb2d700334ad4233ddbe938a722a9db34817fa30f17d392d1feeac6 166 | ff34ea0308125bd1d9691f054c32c6d7b686ed63538d0b9b10e3859a45e2ea83 167 | 60218558c22a6ae24f7be02c25fd926f2d24727173e5359cf083c762e369bb72 168 | c9e6e99561727771205c1b2c2f59d20eb9ad8f390f1556c7621720731ab945e4 169 | fc023deca888bf41e3625e570a7d6ab4ded1fa7ec40c88a221931f77a2632820 170 | 1951b49eb53765753bdedfed9802cff70701134ddf661f5d45487fa1bf6ed97f 171 | d1b3bf70935250b7551d15ff12dc799166e6cc2a7cf26f0e6c90fd01225035e1 172 | a063a5852652ce4303c6ac4e0db71aa1fc28e06276f2945edbe0eca482892301 173 | 2253d4c27f53a5376da2f59beb5268d8557b9ed80e258fde0406263af17f4b90 174 | ea5534cbedbf39fb4b0d025a2cc1b2eac486d8139616eef224946a1265765a53 175 | 7daafcb0a11658fb619fc091fb7a9ddc8896e2275bf4c7389bfc1763ec2092a8 176 | 227f854c373fd03ad8c9ba9eb6492c012e7b73c81da20c1a32b3d958f765627b 177 | 7c16dc84fb0976c8a4a1f0d4d0709d778fc03e0d7bdb18086b2945e5aac9fdd5 178 | 393cae7c30d75c6b2636bf7ed4ecda0f56c23ba0a3b662b3419b1e69aeb3c3e8 179 | cb0640a3c09db87889bdcb3fa96dbb2906e685bf04488501b0b90f977b63f72b 180 | 83acd4899678786dbbf0873134f7ddde8594ccdf2779727fe3786c041976ac58 181 | a9528ef5de417405b51299592fa4d6ca5ce82ca4eb2ca6cee53dcce7f1924d45 182 | b3f685e297ed0f1ca40c56af73a404390d0a696e2795b8ae9f689d4a64bb6a05 183 | 34a1a7ddb9305549582f643a8d52dcd8749ebb80e8aba7f65d725252dc197afb 184 | dfb967a004d4406ca866b81d4e20ea0e0d4052f4be24f530f28316c0c92e0890 185 | c2017f3c2c3ee624c3cac69a0f7b4c395403a03ac3c31ef8e12ada0db3cbc07b 186 | 00a3df13f1738e43765dd7bf0b267938cd5f4ae7731c71cd1dce22449ea4aacc 187 | 3ba991991f4268c4879000d0c507861544d33c2456ae8d228db2134a08226802 188 | 678c8b3a894fa60fb9d068e32701457f5c4f9c5bc9e5a2b84bca66c13a796cfe 189 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 190 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 191 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 192 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 193 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 194 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 195 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 196 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 197 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 198 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 199 | 84f26476b729bca519d3377ddfcac1d39ef452dee83c812cd4a8b606fab28f03 200 | abc532eb35417b44d41bbfc290ca648b5f4c9f0441cb5ab24e7fe13765c58fd3 201 | 8049e401e74c66f3ad7d694e1cbb7c304509560d63645cab433dcad8955c74a9 202 | 647fe40400cf861e6bff4c5d2456aca94c118d6aad82c2f3e1a35339b7807661 203 | 9bbe09f5cf618f97a90e8ab1c481e320e9467f7133fd7d9e488aac1988f664d9 204 | 7725771151d04ff2a1220b0b894da0a75b879a2a2b14610c1c386d82f3f06778 205 | 543228495ffa272921c5c0b0f3954a59aa172f3a1da885aa5923eb41b98ba21d 206 | 0a87829d3c185186b86409f94105c1e934c18d0ab3c7075e97e6c517591d264e 207 | 6c1b9842ffb35a63d75a69a04108bf981556f5aec700210d11b16ecbe17e67f7 208 | ac9a31d1154848e295e2e84981aca08af87c7ba314bff33e637d634085edfd4d 209 | 15313b49f2ba25f02d15f0923cc3b87751956b9722b001d809413ca328cdc872 210 | 7e630d99a4428479116d0adc203ad2154c4c8f70f5cd5e09188caea9d3fa5c9c 211 | b469368104f3366ee79762a2727cd0be07cb54949ecbd9823e2bc4b0a138772e 212 | 9337c0094e1ffc38ac06dc78dada7b64454b70b2d9e762233a67402cc74cda99 213 | 9337c0094e1ffc38ac06dc78dada7b64454b70b2d9e762233a67402cc74cda99 214 | cae0929cfe6df92972e3c5af3a9391e2b233fb67e66d7a0678b71c5a13a5aeaf 215 | 22ec92647bf126cb5cb56ba6bd0a26cd7e2dba2e10885c5196127b251d299304 216 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 217 | 83495ded3182c806af4bcabbf0f88d3492512ec6644b6e61a66b55ec275c8356 218 | 6a1b453172b5c6ddf5bb7aac5b779d54984fff7aabc3ecc7bc77374bd5a05ed9 219 | 3f4b79f4acd358ee7ac0d65dc9aa72fc10121aa44eca1557ab68d29d93192098 220 | e493d84edd738a7747563514ed9cf887f52ef650e3b0927b2065faeeb5ba3963 221 | bf7f60162b81afeba583a859778e09bb5e25e663f2d6289185452d587065ad44 222 | 8fef447823662ec28d860ded8a176662333c7bce7b465badbad21ebc017e0ef2 223 | d57401bd04eb69dac0788c8b97dfdb221769449d9ed9da80c6bf9faeecd21a2e 224 | f79a895121a165a0ba07aca991675fcd4ef63b554fef3e7976893dea15896cc9 225 | 08c7b537f3751506b66ba75f70abb52081d13baa047666c0c4e8faa1091496cf 226 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 227 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 228 | fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa 229 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 230 | 195a92c503f1a8d4465d051363df5746cfa02a188ce2e8683b7502832706781b 231 | 6b0439d2fbe124cfdef39cbc2badfd9f4e6d87321dba765bce860fb50c509d40 232 | 9e22d038b1a14e227518a3efd692dc9c2c01bca8e7490828086c69b03ef781bc 233 | 9bd63d1d658e7ba00c2400bf927ab5e21e65d0c1d84b2d713ffe124766edda3b 234 | 07e920470ed49261940474cdf52868ab6785f1365db988ac8d01a121bf2c0951 235 | 9cf7def3df501e4531cb12a6168e1bb17ec5388cc146a7bdb5eb789de98f45de 236 | 74c03afc643e23d4c252132174feba2048f16a24c9dabf1c76ec189209fda5b7 237 | 048852e5cb9b794a0cb60478628b4fa496f384f4586321bf5e7e9426f608f9d7 238 | 650a28cca27d6d9f1369cde754e5cb31a0d496dd8a6c280fe1394f5b1d14622b 239 | e88f29f8e36d3f625899719a181350c9735b479b9eeb3b29fb69ce250faf11d5 240 | dec4444c09a6e648e055abbd792395ffcc02b7e2993f221748fbe8f820616bbf 241 | 90212f3a2db9b90dddf892044a2949809f8e43975f83fc51bcdf21a122be5757 242 | 85f3c58d9aa3a4acaeec282d978ff919d2136bc6497362b42a186d698d8e7bca 243 | 243fe6ffa1fe9fd0fd5e97a0581d0a0c132e0458a005068744693bc97b452086 244 | 8873d18aff0febad3ac97ae0121dc9b51579a2f8c5f1206f76e7dc785e721523 245 | 7a7375655345a76157528f8d46d757858aae8f98aa47c86df82e1602c7c57726 246 | 091db68698872b305269e71844eae258d1ee9d54b2e8dc79aa59bd1255a47716 247 | ba9cd09a6f7619f20008af4724f86467b3ecd2204c50eeba99a58c0af7096a18 248 | 5a89a844a878d69806b9acacb559ebae3a409a61decd565df4d6aab1e9d05c00 249 | 6a8d0af8ecffd0f1315d4c304155902847a0552a02bc570628d46ddaf363cfac 250 | 4d1708171b7eedc79ab63aebe7fe3c0f080a395928f437dfdef33c6f3a6cab09 251 | abdfcb713d625f078b18c3530d6293d326e1eef7c71563d2edf8f2e43c420883 252 | e1415753a9779876ea386bdb0c97837dbc14dcd329b9654e4cb6636879c4bbd6 253 | e0a6497c077728b3cd8092ffdbc5c072c7851bf9948b728e74b06aff69d9979d 254 | 4b2c195c00461f1289824a23b01b4082b5399a28cbf484eb8684cd4c5642dd63 255 | 8dc93de2d5be827c403938de24e1209617655375a335e9712411a1a78c8e5d6b 256 | 03fb73fa03f0997bdd710602bf2ff34ee2ae817e992eb757023e6148b59ddef2 257 | 3d3693535b1b8d39f1b22c93ce72b1778c081e329bd2746ad136f52459a72fab 258 | e2fc6f4a096740a4b37c8c619fdcfd3a1a836524e820bdc6ce7243651b5cfcca 259 | 34c18562e795db0135e75e258d591a680d241a979676168253d9172fa8c57891 260 | 4f1a07ee6130b972713252d03afa5425196e5b8838a8d9dace2aeb13f1af5bc1 261 | 3ab81c6f76af477197298cb3b948f82ee667bc3459e5ef2e1d483645798c939c 262 | 051fa38db3ab34e2109468a91bc2c9b48644cc3341e09ec3a42783745246efed 263 | 0400692620c9f76024f6c620dc1f08e7ef096c3b32813e0bba634bc632acde6d 264 | bb78dcfc33c508efb8a6c37e0851f13dbfa095ad924fb96974e08e4d5cbfafe2 265 | 209f925f914bfa21e188f2c67449082e266f5411949579cc3d33be4de9dc9c88 266 | baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096 267 | 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae 268 | cde09d9924582456f4022a27b6f2b43080ca722721e997510e62e5dab00a220b 269 | 6d2162d52ceb5a49b973342b2c5532b47fe26c7a39c3a77ca1a96542af4de4e7 270 | 5dc9c149c4ee909628cea1a5fca55a70d04fc72692029ea992bee87ec7a22313 271 | 0f54dcef969c12adc88d3987edb7f2310b9ccd95212465d1e07da98e99907990 272 | 335caedddaa0272ca4b1409a338a2016c28d51d88de0514bf98876b56da57b0a 273 | 856993134e200ade9c9991b501cb7d103088118a171c326b7c568bf38da7cfc0 274 | faa0c53569ac72006a6697340cefecd1b4e107d53f6a218da29f6a54f1e866c6 275 | 1e25ddc3f35544fad7bb1daf717671a1253041e002cfe324c6794bb8c7b18d7e 276 | 9291105935716f9b96060bbb20ae8bb2578caaf405e1529a34bfb639218f1b5a 277 | 9438ec66d37819e100c16293d5979256ccb0265be5b742958ddc486c9376a7f4 278 | ba4b5f286996a1c97acf010bd68cd0181fbc51e5f371800480c00c97bfcecff1 279 | 83d28f03580f29c776a9cd7dd10e1a1f73148571c7fe03dae39818578b9bf8a8 280 | 6729ef639d305b90f22a57d54ace399b8503c74a444acb83063b18a4a487cef4 281 | edf93172d42d267daecebd739cd52a3b50ddda4e20d5b75ec5912b6944ee5a58 282 | e5da659f8d12683b36a17ca32140f2c9249574265415264e8d696637c81c4c1d 283 | 2d6a681e2d3bc492d4bf5ed25f1375455ce760fc85fc58113a981d1aaa43e66a 284 | 146772e9dba5a5943b388b2aa623342e3f50f4263416dfb25002351864ae27e2 285 | 86b093c2a6451c574101f851b768051c5c43c9e4253444b7a427f94700a9d95b 286 | 371ecc9d767911382f9ee7300f47625e77f7002425b2d8f39cea847475e69202 287 | 6dea7742e96283114a4eb283ac243d35a0bd6385a355822b727e3ed44958ceb4 288 | 25e9820f0f37bb50f3be0ce639f2ab0fa929b2037be9cc538e2b75bc3b1fe18b 289 | 286176b7076d9ae726621ec164f3519bd88878494b65594af69662f1ef0271be 290 | c56f0c6e097ae6e06c209ff87c6fb4120360d420e88c7dd644eb0c4f4efeaf5a 291 | c5980e92efed926d845ce25f2938e119f0c49cb0f36f838f88ad1dd9d463c7e8 292 | 05cf61808c189c20669e597580449198f6c7521a82684267aede755068693423 293 | d555878089f04bf2e738c5d6fc1f046e1e17ac7ec6fb66d04a3a9f8118682ca2 294 | -------------------------------------------------------------------------------- /tests/data/rulehashes_v2.0.0.txt: -------------------------------------------------------------------------------- 1 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 2 | d625c96fb568e0c09a1dd30ffb7131fdc3283beb3765efabb1550b7749534f28 3 | 6e4235b85c28a5588e4aee487208fffe3396252ca3b8fbd8b380be2aa7ffaba8 4 | 48d5066ef3a8892e8b46d265c7965c7d7e32c94eda6fda9a22bb5ba8511c9ba5 5 | fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa 6 | 35c7123f53f3ff008f2c2b4a597dfb21a201d42e231f5c4d079858f1c107097a 7 | 7e60301adaa02cff63834e821e2fba8e13aeb0cbc3d40f35911a71598e844657 8 | 337fedbc074ec447a95ba4b50e36f11dbbca513821e939a7f5779a0bdb980032 9 | cafcf706500f101c7c2c3361c43e7ce85d3aa839e73443f0b6c943041c2c52a7 10 | 6e678a811f3695e52111271d3aa97189cfcf8d230ca67dc2d442ff851e417cb5 11 | 233ab605c528c99f351f0a70b7b5b685fe78fa3ee7629ea041cf45343173624c 12 | ab28b5e9c550a0c41f837544c56de18696c363a3b7d5f80f82a28783c573f167 13 | 31497df25b6b1557dccb9fe11b20ee00b867d30f5a4e9b5a83275a71371cdabc 14 | 21a4f89e07b0d233a4e536e600b22740da6e87ebac4efe9221561f763d20347d 15 | d5ed2224d36719855f93d535a6305dd80a2e585211b82504efcd5464b17d2e3d 16 | 24e897d1960c73fc2c0ccb8539189123381fa72e65981468c5cde4c6e7f44f79 17 | 23414016f63d10cf38e28a8a7853b0be821984f2eb3b3e801abbdcb7413e31e4 18 | 3f0459a62a3437d3da09fa05491214c33360e90da77416bb0eaaa4bd3ad73b4a 19 | 744c817b4cfb184693f02e5d320f5fea20c06027b9501c34c9ca2c138c8f1719 20 | eec9e3a8fe93a554edad5559bf73faa2ea8448e416e50af063807cdbea1282d6 21 | 850f7b8ab28ae4fd099de78cb5f652d212bb34e20be0d4c4297202993cd90686 22 | fa69df58aff909bcb74bf01b0211e775f4a18b33f010368f9dde8339a306d274 23 | 3390d9d8d6d030dab5d1f9910985d75e02bf168adfbe0e131c447603b21a8dba 24 | c7c6d5a6cd37fbc61bb0273aa3cae578855f1695fe8489e35572ecda1644e153 25 | 9ad757632d401c10e85117cda6a91e1243bf33fe65822db7788ed31ba17979d2 26 | 52f4dcdbbe287eada284a7ac870d7850455f9316692d2a783cd85dfac89e7f21 27 | dd0aa31717bacb2f692a72a6f58088a7c97d35b4b0f35c1a5cfed41c7d4bc8e0 28 | 2caa7c62df6c10bc55dfcbca4df69bbb035101bd5e90f2db95a92b952ad72743 29 | e50919daa2cbfd3410a2cf936804dbebf5ef8e26e06e036f93c6e781e410e5d4 30 | 978521088871b4476b78edc63ce97d323e53b7e63036c69ee915c9b627062106 31 | fece85a1563e5d9c30ab923148f7332a779212184ac06b406d03bd9b31df3507 32 | a46a61c2c9004858a99c68e1d8b417e582ed9eccd64d3b066ef6a182abdfd6ee 33 | b6a7c7c3b8a87d2238d1f678c2e77affd6ddd59aed0c343061da4c3d2e5a5c7b 34 | b6058483da2793a09cdaefa90ac4f678849d46f7ef8972a06f8f10000e6c7da8 35 | f1e2aee6541de036c6e5de385798a2cf3ff1c74b6b89ae5b65d92c884cbd0b81 36 | 93ce1adb7bceb7a0852c4de97b293a9d8c770d676b2ecd7c38059d80a77fbb8a 37 | a2bd69c2b74a634b8e596a2ced695f4ecf84facef2201d5c48321f3a6b82db73 38 | d0d7621722b19407c20ad8c8cfbc7ffc926e68ca6bc18b31da2b0a8119264665 39 | 14b3670ce59c25a7af65d7d043530028da62badaa5f44a448a9a19491972a720 40 | b49fa4eadfba58752c83a7aed680d48498febcb945c1fd75c45d6addbfa328da 41 | 59233c200a123f105877fe3b4497b68ef744594e58295f43bf37bdea3bed7af0 42 | f7fd2c110d24c4d4f5a78f03fde32a4833d3ffc7443171268d46da8fa910ac68 43 | 6d36773a867e51b78dc2c48f1332d9a4041fe3f2665ad7698bc63f6df6925d9d 44 | a6565988a60034e0e58a2515f27502573569ae0d6de0aaccd7ff61f9c6742999 45 | 77110ef9e36e7ecbcdc7c94c6a94740c48d5d1fdc9f50d372b4b4fea7b4db0bd 46 | 0937e7c5e5be2fcd0923aa9a964ddb4b197c24c3c3c212242bd2eae41e8c07dc 47 | 858bc0c9ab0b5eeae5bcd9f9e23c230bddcb6c57e639f8677c564b7cb82d1f37 48 | 34ed4ea989005f3a52429c683ff3885c62cd61377006cf3e516485cf71b3e435 49 | 454320053a83d8cf3fed72ccddf8a8c3ebcb4b9c88893c3d182281081fda5352 50 | 233918f5b8c0992b20ef5b6508cb4e0c9a777528a7c86150c75bcdbaa1200c0f 51 | f7d8471b7b6cebabf76bcace1a3d984b067444d9ee4cc01b9eebc6b94b61d62c 52 | 4eefab5519d908aa13b9f66ad161c3f555e395c17605262bb08c618017aa3ba8 53 | 7a344f3f20d58c33df3ab2803c57c13e4b03a52537819aee002a7798d2f3c051 54 | 8fc690c1356029c213d06ad0b12a161b019ba4fe49f559b0827251a91a8977eb 55 | 622b4b2cdab5a85f10562b12f93c19b6a7e8c9795aab0a02602c4f77e4f1f97a 56 | 5867a8cd0d5d2ff73ea4262eff75e032f0143ee96f23d1a8ac11f14afa50d228 57 | 33f391851dc7dbd13e244e65d7e4b03f37a0b953e28cb3ac2cd7e12605a18df2 58 | 1332a1c0f8bdb7856c1eaaac3e9232fcf85d3ebb0278207f68c0f2022e19c170 59 | d29abf9dc66fc3e211078947f10bd8de5df1e841e3c5eacd1ddb23ae27cc9588 60 | 8344a0ee5758dcbb11f3324889b000fce105c3fd751d3b4630681db0e6c5c797 61 | 438b7e7ba068932af2347af0caf7642fc7614dee240323f81e0db8b71af5531e 62 | b4c70e39cbfae94108c3f6817301e46a8d5d5175988760739a38b2591ec4e73c 63 | 855f0f0b68cd069648114327dc7e46e4675b9bfaefa6fdae5577b521dbdb3a5d 64 | 59c0b5e5001a5b5970783081cf2fb82251e8020300e5571e970160eedce9d11a 65 | ea6ba6b705a6cddf11fb0af4f52cea3ec45c2aead28dbd16f20b0e2b54bff2fd 66 | 9c744cafd13de55ef58c031ccb1695425e33cc3f6eeee7618cefefc62fd565d7 67 | 94c5b8876151ce981171db9bd5b305fc69aac566403601714f12dcd4791b6e51 68 | bc4aba77900a563f21975e3be936786a4bbe9eec2d61546ccba548ad973dcee5 69 | 80fd1b14e764bc6181b4920cd8e3f7c0d567bce37b818d067e66341026b0c3f2 70 | 4b65099b1bcbdd2aaecd3e9b917c6d76dbeb9a367ab6e15c73ad624f15efbb1b 71 | 1b5146472ca54388a973229defce53f0319b90d2ca73ff6c739c24ff2b2a5fe0 72 | c5b0a508548f28a6bdb4ae81b113e824af800a1c9e40f7b3cebe2033974f80c0 73 | 56f31be82f5fa1ed932b42574c91b5d57866eece83193afe01551033d340d5af 74 | 8a562ac9831e27743fa69622072726c91cf593ef8cd65380e2b6208045218e03 75 | 76c38b6b9c91c970140eb5aca4ce7aa1475f55d9e8b2f1fc04b51f5eb96bab63 76 | cb941ffac208d3617d17f2ffe812808eb00f7533b1bd817388bbb64c5cef6fbf 77 | e5527ad7feda7128a8da438e1b44b4e56ff2db621f1b2d10c98e006614cdd3a9 78 | 823ce68a445823098e6b7acb18c70318c95f7853de2d8c5968a530b53c266464 79 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 80 | 28d94acbee4119701830f2a5bde19c0b4e62157dcf7b526a9a16d96bedc6e861 81 | 2cb2837744913f3504de915e72cd4905672c4f648f3a82954d6cb3213cd7d88b 82 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 83 | 448c13819aefbd6cb88a0baef7d195cdc299c766c98ef65fa2d26cf9103886a3 84 | 0ee55483f4da8116aa12790859016fc499ff139e5c3bbeaf58936780bb1d5194 85 | 42c5c50301f7a09feccbc6733f8258d377112c8a6194a0f1a152a0c02f025742 86 | a3fa72ced02134135a4b597e3e1b4e8b1509c63d7cc7875b3815ec861401c587 87 | b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 88 | 0f083d0561f6293c712b219dd7876101bc35622366715967277b3fea9e796677 89 | d7d55842bb7847ea099d3ccb9ad7d4f9ea5005a6b54c1cfba08223af81bea44e 90 | 2d900fe9142d858d025e58bce6777839c3ac56b08f91db1df01ffb56c00ce78b 91 | 61c54d7eab99ca2b145279c4cdc79cb7a7362f09b998271cf8b4aa851cff7e25 92 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 93 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 94 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 95 | 6fd7eeaa45c6a252c2a92db673a05a3003a512492979ce807bc4eb0f395f88e3 96 | b0b40be80f2f1a67fd123955f652e9715621bc4218936f0699d3dbbb6559efa6 97 | 3b7d00c8bc4d8d9927ab67cd945bf6a06ab2b35e53f7213612db44bcb2211c52 98 | 57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 99 | 57625816354f35b2b94d34a5e1c7c46e42ae969cfa7a19752cc7fd4a67c9c5a6 100 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 101 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 102 | b4b177dddaa32cefe81e39074ec09e9863613a48589d20289ffe1e48c6475f59 103 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 104 | eccb679cc824d0e83b1c9a8594469abbacb1c5e4eedf01193f21a84d6f028a38 105 | 321ec9ac18d58d3d2b25fcf306a08bae799e59d35f31773dd96323656e80d7d9 106 | 79535bcef9252a3f988515bd33a963ff5db119b3b42c6c6b263f60fa6bf0fff7 107 | a8c95988f4ee0d3a8347bc71347d8d3e0144663034ffd4ce3a61c96c7378c247 108 | 61507c8074e56e0bb9dc6e17b87dfc0f27cddec39f8ee5fea66da282af5dbd7f 109 | ff0159a9e24f5bed4486fc206b87e6861ace224b69cbb2a15a3660d6c1c78ab2 110 | 7311ff08867cfc0c21f65fde14e2d74cb1ec4a530004e7e1134fa638a6885aad 111 | 3914181944d4eb8e612fdabf962234680acfffb9779a00aadf0fbf3cd14b6dbd 112 | 97563d55ed98cb23bda5abb17d9c2c3093f422d400255e7c99f2376713d66a9d 113 | 87624c65c6f419ab8e73a86e0a922de1767b1aa05e113fb38acd4583c3b55161 114 | 2ec61650d214cef487df3d11d8a959e339947fe7047fcfadebca1af9348a2d1d 115 | 7068ddfa0218c6ecdb0bc16d49c98094bbaae0dcb59e52c2527059cdff3916de 116 | 79088de2a8f5f9d8dc7e9abb7da7ea07594918f69becf82de09fd7ef8f91f178 117 | a2aabe8079f2639ea09c9640e4dc94e1039042a4573c4070ff68e72d90f1630c 118 | 67e68d2f1674a2bd02cef3f83f535712832176da4839d9d9e56debf5d6e7737b 119 | d73242285645e991e55039628c1d3876b3b1abca808a0e5704bae11e42997069 120 | 6384018af7c8859a954eb3d3cb8ac10e4c6c419a2aff5c9394a5742f0feb42c4 121 | 437e4c099c0e3d2debdeee79eb82d82e78e956545d13e8857ae5ea261e14e490 122 | eac5441aabadcd3899fe0ddb640f88de58bc49b2c8b319e2808716a256192bf8 123 | 88e84856df54e85c6287784865809ac1ad635e1b710997ab19333cd44baf3ffe 124 | 63386b4232419c5d7db9f02b7f81bb59924bf55743aa3838d63e3e7c767b9346 125 | b1c3fd978bf1790db220df34e55bcb5a390f0232acda571a33cbe4fcb4306801 126 | 718c48f1ade4c467cdd16e63f3cc81e484842ec45b6aa77edf9c1355136ef4bd 127 | 36e8425d7c36b8dda4e6ac4b03050f060a1854bc888d4c17a7bdafea1c5c8dc5 128 | 6377bd86000b35ecb0d939fb5711c257b871dbd55d5472918404dd01575306d2 129 | 2f5cad1857f3320f193137427276c07e99fb7459bb3957782bf4b7a82bd65102 130 | 16f732af664af14ac8ab4dd398aab44943a59d91e6ab2ae320fa6e5a73dd13b6 131 | 01f4621f2c5b5269f3644c150eb89191759f201a47fc443e9acbe7a805e5f7db 132 | 7897117d4ebaf57077b25f9107b3d244f48c22df40940a99d39ed53d601d81c2 133 | 7897117d4ebaf57077b25f9107b3d244f48c22df40940a99d39ed53d601d81c2 134 | 6978ec7145806c870fc17164fe163af9682a36d22f6684f115781819aac8229c 135 | 2c6cbf5096a2ace88525d926776fe6c294bc5a8d47d9c64faf31a73109db4722 136 | effdfc0d750450666e8fc5200b807219a7086d068b1f85ddf2953cf2ab539f79 137 | 0985616fbf23e37981bdca3eab65f29fcd75d5b4a6b9592a86a6e4e98b3174b9 138 | a1f0e1d30fb43a5a3721ecb4470ea3400337002b7961c68e920972407f84cdfb 139 | 73d29ae7cde57a1b04cf639b144477c11e46bbef9998c9f1932dea9a1a80af4d 140 | 173212a17efbd33ead807fc464742f674692b6d297c1851d4f3439ce944b1564 141 | b599dcdf28e0d614aec914d8a8c0ccfb559045f0d029ee1cd72886a159497901 142 | 866f50777c5967e2055445dd5ea3d3fb5707419f5d46bb7c2af9a8cb95f4b9fe 143 | 00567f292137497bc2b9c234b8e80d53aa9ae6e549c03ed3b6dfd71d684747b3 144 | 000fd941376b243d4ce19839aa6e0798c610da99c47bc0cbbea3a81b967a4ace 145 | 141b164cab2d321f3f07a1e2ea68bc04bf9073aa5c2ad701c212e415eb2dc65c 146 | 77ae236316021cf25914361fbb16ff4d3f24951edebd6cf6aa652c26654d1d69 147 | 85a90b1be58158aef163390b5b1abfcc11bda1c64ce379d0ca8759a9cd776787 148 | 83c516b4501d8f2f53bdaf71e3df3b7a2818574b526530ee569bb0d27087d391 149 | 3b24d472de4c339a5b330c3cb271d186702f22379c5cfefb9c6bffb8b5b03785 150 | 2f658c197bce380fc4b9d3c1657e5175c0daef2f678f30ce3696b6cc84d339f2 151 | 3861bbee0a10b02efa3dd1f4916c428fd23aa5a913fc15d1e8a3017d31d11e28 152 | 9ef48bf75666e9dfdf4904921c8a357fafc391faf69de1a598caeee2c8b16825 153 | 0e461d65857aea54fadf627c5cff83533785064c82eeefda8a1289fea68a406d 154 | ba66129d335b5ee04e50fe4a6cadab907ee876f537f16dfe364d06e8b597844a 155 | 3c38daa7b6b52345994a3c57e8596eabea74ef7709b988c1d3c20a261887e163 156 | 10badf3477c0b3061f46e90f667816039163be301d4ca93756a0988e5c310efb 157 | 684d1218ff3d6a57317277a3d4281a10432fb1cab074395c469901275735ed71 158 | 9ef48bf75666e9dfdf4904921c8a357fafc391faf69de1a598caeee2c8b16825 159 | 9ef48bf75666e9dfdf4904921c8a357fafc391faf69de1a598caeee2c8b16825 160 | d4550a3c660a96f9a788b9fa9c8df2403b044741f5be3da4ac2409f69e67cf4a 161 | d4550a3c660a96f9a788b9fa9c8df2403b044741f5be3da4ac2409f69e67cf4a 162 | 268374581666c0a7b3a9482892f15cb0500a5e48592b2f116cc03655ced6a782 163 | a4fd473a0216048ffc7c21d71c0d6e72305c03f90682b303f4b1ac232b2215c0 164 | cf035fdf5d441e3d5d4dc56557c96dd7cd1c75e01c98bf91736c7cbc4569aab5 165 | e7c1f9c8fcb2d700334ad4233ddbe938a722a9db34817fa30f17d392d1feeac6 166 | 5bf4290172017a28c9bad170d64e194281e166afb24cd9940ebc3f5d248b002c 167 | b1ee98c789583fbf8b5a0fe969b490425446a491e3dcf4b1df7af455dbe5261c 168 | 80647734fab6b68255f13bb4ff2547138dbe3337d28dafa0303ec1f07049781f 169 | e31ed1540da07094fad4e4ccf51be251e0fe2d8c95995131b0de6c4f09f950f5 170 | 230a9f6947d31c7477a7eb441ed9d551709b2afee0c6b0f46be04147e248d5e3 171 | d1b3bf70935250b7551d15ff12dc799166e6cc2a7cf26f0e6c90fd01225035e1 172 | a063a5852652ce4303c6ac4e0db71aa1fc28e06276f2945edbe0eca482892301 173 | 2253d4c27f53a5376da2f59beb5268d8557b9ed80e258fde0406263af17f4b90 174 | ea5534cbedbf39fb4b0d025a2cc1b2eac486d8139616eef224946a1265765a53 175 | 7daafcb0a11658fb619fc091fb7a9ddc8896e2275bf4c7389bfc1763ec2092a8 176 | 227f854c373fd03ad8c9ba9eb6492c012e7b73c81da20c1a32b3d958f765627b 177 | 7c16dc84fb0976c8a4a1f0d4d0709d778fc03e0d7bdb18086b2945e5aac9fdd5 178 | 393cae7c30d75c6b2636bf7ed4ecda0f56c23ba0a3b662b3419b1e69aeb3c3e8 179 | cb0640a3c09db87889bdcb3fa96dbb2906e685bf04488501b0b90f977b63f72b 180 | 83acd4899678786dbbf0873134f7ddde8594ccdf2779727fe3786c041976ac58 181 | a9528ef5de417405b51299592fa4d6ca5ce82ca4eb2ca6cee53dcce7f1924d45 182 | b3f685e297ed0f1ca40c56af73a404390d0a696e2795b8ae9f689d4a64bb6a05 183 | 34a1a7ddb9305549582f643a8d52dcd8749ebb80e8aba7f65d725252dc197afb 184 | dfb967a004d4406ca866b81d4e20ea0e0d4052f4be24f530f28316c0c92e0890 185 | c2017f3c2c3ee624c3cac69a0f7b4c395403a03ac3c31ef8e12ada0db3cbc07b 186 | 00a3df13f1738e43765dd7bf0b267938cd5f4ae7731c71cd1dce22449ea4aacc 187 | 866f50777c5967e2055445dd5ea3d3fb5707419f5d46bb7c2af9a8cb95f4b9fe 188 | ba66129d335b5ee04e50fe4a6cadab907ee876f537f16dfe364d06e8b597844a 189 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 190 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 191 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 192 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 193 | 9ee32fb8c241cc059753475f990ed981187531d1479617aaee4881cdb8a3f4bb 194 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 195 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 196 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 197 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 198 | b3101e695276d4bf074993a7df44b4fbe6b22129789ae514181b934bfe3cdd94 199 | 84f26476b729bca519d3377ddfcac1d39ef452dee83c812cd4a8b606fab28f03 200 | 5b5218dc5e3a8deb146183952fd468495f7d805e7b2e80db046f59221da79209 201 | 32a5cdd4b1f10f63257863591c07fe4f1b3e2dee4eada109bb7a6882ae03d8c3 202 | 647fe40400cf861e6bff4c5d2456aca94c118d6aad82c2f3e1a35339b7807661 203 | 9bbe09f5cf618f97a90e8ab1c481e320e9467f7133fd7d9e488aac1988f664d9 204 | 7725771151d04ff2a1220b0b894da0a75b879a2a2b14610c1c386d82f3f06778 205 | 543228495ffa272921c5c0b0f3954a59aa172f3a1da885aa5923eb41b98ba21d 206 | 0a87829d3c185186b86409f94105c1e934c18d0ab3c7075e97e6c517591d264e 207 | 6c1b9842ffb35a63d75a69a04108bf981556f5aec700210d11b16ecbe17e67f7 208 | ac9a31d1154848e295e2e84981aca08af87c7ba314bff33e637d634085edfd4d 209 | 15313b49f2ba25f02d15f0923cc3b87751956b9722b001d809413ca328cdc872 210 | 7e630d99a4428479116d0adc203ad2154c4c8f70f5cd5e09188caea9d3fa5c9c 211 | 801e9e61c91b95c3295b83e2bf553b929d1710116608f40dbfddc317135c4b13 212 | f306468e7781bbbfdf50f3ac1906118fe4a21aa9c0049b94845c497640da5de9 213 | f306468e7781bbbfdf50f3ac1906118fe4a21aa9c0049b94845c497640da5de9 214 | cae0929cfe6df92972e3c5af3a9391e2b233fb67e66d7a0678b71c5a13a5aeaf 215 | 22ec92647bf126cb5cb56ba6bd0a26cd7e2dba2e10885c5196127b251d299304 216 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 217 | 83495ded3182c806af4bcabbf0f88d3492512ec6644b6e61a66b55ec275c8356 218 | 6a1b453172b5c6ddf5bb7aac5b779d54984fff7aabc3ecc7bc77374bd5a05ed9 219 | 3f4b79f4acd358ee7ac0d65dc9aa72fc10121aa44eca1557ab68d29d93192098 220 | e493d84edd738a7747563514ed9cf887f52ef650e3b0927b2065faeeb5ba3963 221 | bf7f60162b81afeba583a859778e09bb5e25e663f2d6289185452d587065ad44 222 | 8fef447823662ec28d860ded8a176662333c7bce7b465badbad21ebc017e0ef2 223 | d57401bd04eb69dac0788c8b97dfdb221769449d9ed9da80c6bf9faeecd21a2e 224 | f79a895121a165a0ba07aca991675fcd4ef63b554fef3e7976893dea15896cc9 225 | 08c7b537f3751506b66ba75f70abb52081d13baa047666c0c4e8faa1091496cf 226 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 227 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 228 | fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa 229 | b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b 230 | 195a92c503f1a8d4465d051363df5746cfa02a188ce2e8683b7502832706781b 231 | 6b0439d2fbe124cfdef39cbc2badfd9f4e6d87321dba765bce860fb50c509d40 232 | 9e22d038b1a14e227518a3efd692dc9c2c01bca8e7490828086c69b03ef781bc 233 | 9bd63d1d658e7ba00c2400bf927ab5e21e65d0c1d84b2d713ffe124766edda3b 234 | 07e920470ed49261940474cdf52868ab6785f1365db988ac8d01a121bf2c0951 235 | 9cf7def3df501e4531cb12a6168e1bb17ec5388cc146a7bdb5eb789de98f45de 236 | 74c03afc643e23d4c252132174feba2048f16a24c9dabf1c76ec189209fda5b7 237 | 048852e5cb9b794a0cb60478628b4fa496f384f4586321bf5e7e9426f608f9d7 238 | 650a28cca27d6d9f1369cde754e5cb31a0d496dd8a6c280fe1394f5b1d14622b 239 | e88f29f8e36d3f625899719a181350c9735b479b9eeb3b29fb69ce250faf11d5 240 | dec4444c09a6e648e055abbd792395ffcc02b7e2993f221748fbe8f820616bbf 241 | 90212f3a2db9b90dddf892044a2949809f8e43975f83fc51bcdf21a122be5757 242 | 85f3c58d9aa3a4acaeec282d978ff919d2136bc6497362b42a186d698d8e7bca 243 | 243fe6ffa1fe9fd0fd5e97a0581d0a0c132e0458a005068744693bc97b452086 244 | 8873d18aff0febad3ac97ae0121dc9b51579a2f8c5f1206f76e7dc785e721523 245 | 7a7375655345a76157528f8d46d757858aae8f98aa47c86df82e1602c7c57726 246 | 091db68698872b305269e71844eae258d1ee9d54b2e8dc79aa59bd1255a47716 247 | ba9cd09a6f7619f20008af4724f86467b3ecd2204c50eeba99a58c0af7096a18 248 | 5a89a844a878d69806b9acacb559ebae3a409a61decd565df4d6aab1e9d05c00 249 | 6a8d0af8ecffd0f1315d4c304155902847a0552a02bc570628d46ddaf363cfac 250 | 4d1708171b7eedc79ab63aebe7fe3c0f080a395928f437dfdef33c6f3a6cab09 251 | abdfcb713d625f078b18c3530d6293d326e1eef7c71563d2edf8f2e43c420883 252 | e1415753a9779876ea386bdb0c97837dbc14dcd329b9654e4cb6636879c4bbd6 253 | e0a6497c077728b3cd8092ffdbc5c072c7851bf9948b728e74b06aff69d9979d 254 | 4b2c195c00461f1289824a23b01b4082b5399a28cbf484eb8684cd4c5642dd63 255 | 8dc93de2d5be827c403938de24e1209617655375a335e9712411a1a78c8e5d6b 256 | 03fb73fa03f0997bdd710602bf2ff34ee2ae817e992eb757023e6148b59ddef2 257 | 3d3693535b1b8d39f1b22c93ce72b1778c081e329bd2746ad136f52459a72fab 258 | e2fc6f4a096740a4b37c8c619fdcfd3a1a836524e820bdc6ce7243651b5cfcca 259 | 34c18562e795db0135e75e258d591a680d241a979676168253d9172fa8c57891 260 | 4f1a07ee6130b972713252d03afa5425196e5b8838a8d9dace2aeb13f1af5bc1 261 | 3ab81c6f76af477197298cb3b948f82ee667bc3459e5ef2e1d483645798c939c 262 | 051fa38db3ab34e2109468a91bc2c9b48644cc3341e09ec3a42783745246efed 263 | 0400692620c9f76024f6c620dc1f08e7ef096c3b32813e0bba634bc632acde6d 264 | bb78dcfc33c508efb8a6c37e0851f13dbfa095ad924fb96974e08e4d5cbfafe2 265 | 209f925f914bfa21e188f2c67449082e266f5411949579cc3d33be4de9dc9c88 266 | baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096 267 | 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae 268 | 9ac915fbb2a19b0a642525f09b9949aef69831d3780460c6b4bdcd2744f9a048 269 | 347d415e2689fe86aecd739e8f800fdb6f2aa8c8d63399d2707ebe7ca4202b36 270 | 1799fdbf703258f99a9d1b368b6d6b0d787e1cb78bf2967a20cb0adf145ed25d 271 | 2308c0bfdfa71a71591108bb40932fb2870f8c8404ca2f0ccb1f96e86952558e 272 | 5898ab0ddaeedef07afb632368a41f8cbd4b8de37e6b68e4310cbcaa53441fc1 273 | bdf5744eb35cecada1527ad94901190cc2c9c46afe08f3e55a9af322e15ff08f 274 | b7f14ae60f237c23cfd75e973bf6b61a779df6f4c5c48ee398e8f0054415819e 275 | dad7f8157bc5a03cbc05b18b4acd36565b67d8af5c43a0c423f9ed0f9c985bcd 276 | 861882ea647abc9efda7a14e4308761dbc5586363b5328ebacbaf1ba930f3a43 277 | e68fd71fc673469f511da3c27aa1c08b174547ae90042dcaf59d1d710802f476 278 | 5d1f712d0942750a6d6cdb802ee681c85ff9ee81eb1fa2d24ac0317975ce027c 279 | add3fe697c5bdff3ef8123476ba1dd4146961798295ed91f53234e3f78f546dc 280 | 19dc5f6aaf1b563bf634320ee18fbed35bf791f452d4038a677fa3c877148cf2 281 | 9e6f6f329f7028755324da253dd8e1b786c20f0b4c22339c3cb02fc10d733373 282 | 6646b8166a297404b5bc8deb098c01bc11fbf7230983fff0da4d2c43650e6528 283 | d45e660f108ca2f9cd24f2f569e6ff5e264cf603a74bcfb9ddc9073a3f790748 284 | 6f11883a2871690c650d8a4b23c261390389b58382996880f734407edda671ed 285 | 937e0d2c2d0e762a5806f96c75766cc66fff8b85bc1ddf2d4d3ad075ee12346c 286 | d4efc2020d74816eef5e59faedee11bf9e7c712fc6f0f148f9ffdffe2022dba6 287 | 5c71bdf46c8ed0de55aba4ba871df28d969a3cdd082134ccc069e264b675c183 288 | c23bd22368d03adc58de77e5e2e24151a02c72749f1bd26e15fe55028c03a908 289 | 5cf2a1e47bf3d8234d3530ba77b0ccf4e40d1fb6264bcc7f81f04701ded530cc 290 | b56d737653140daffcd11f0d97397816f2e389e1a5c92ea36f8da5f0defafef8 291 | caabb09e779f9c79723fc3439f70f3c69f5372cdc648c6c5f1dca48023692d30 292 | 05cf61808c189c20669e597580449198f6c7521a82684267aede755068693423 293 | d555878089f04bf2e738c5d6fc1f046e1e17ac7ec6fb66d04a3a9f8118682ca2 294 | -------------------------------------------------------------------------------- /tests/data/test_rules_from_yara_project.yar: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016. The YARA Authors. All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | // This file of YARA rules is derived from 31 | // https://raw.githubusercontent.com/VirusTotal/yara/master/tests/test-rules.c 32 | // License left intact 33 | // Rules that test for errors in rule syntax are removed 34 | 35 | 36 | // Boolean operators 37 | rule test { condition: true } 38 | 39 | rule test { condition: true or false } 40 | 41 | rule test { condition: true and true } 42 | 43 | rule test { condition: 0x1 and 0x2} 44 | 45 | rule test { condition: false } 46 | 47 | rule test { condition: true and false } 48 | 49 | rule test { condition: false or false } 50 | 51 | 52 | // Comparison operators 53 | rule test { condition: 2 > 1 } 54 | 55 | rule test { condition: 1 < 2 } 56 | 57 | rule test { condition: 2 >= 1 } 58 | 59 | rule test { condition: 1 <= 1 } 60 | 61 | rule test { condition: 1 == 1 } 62 | 63 | rule test { condition: 1.5 == 1.5} 64 | 65 | rule test { condition: 1.0 == 1} 66 | 67 | rule test { condition: 1.5 >= 1.0} 68 | 69 | rule test { condition: 1.0 != 1.000000000000001 } 70 | 71 | rule test { condition: 1.0 < 1.000000000000001 } 72 | 73 | rule test { condition: 1.0 >= 1.000000000000001 } 74 | 75 | rule test { condition: 1.000000000000001 > 1 } 76 | 77 | rule test { condition: 1.000000000000001 <= 1 } 78 | 79 | rule test { condition: 1.0 == 1.0000000000000001 } 80 | 81 | rule test { condition: 1.0 >= 1.0000000000000001 } 82 | 83 | rule test { condition: 1.5 >= 1} 84 | 85 | rule test { condition: 1.0 >= 1} 86 | 87 | rule test { condition: 0.5 < 1} 88 | 89 | rule test { condition: 0.5 <= 1} 90 | 91 | rule test { condition: 1.0 <= 1} 92 | 93 | rule test { condition: "abc" == "abc"} 94 | 95 | rule test { condition: "abc" <= "abc"} 96 | 97 | rule test { condition: "abc" >= "abc"} 98 | 99 | rule test { condition: "ab" < "abc"} 100 | 101 | rule test { condition: "abc" > "ab"} 102 | 103 | rule test { condition: "abc" < "abd"} 104 | 105 | rule test { condition: "abd" > "abc"} 106 | 107 | rule test { condition: 1 != 1} 108 | 109 | rule test { condition: 1 != 1.0} 110 | 111 | rule test { condition: 2 > 3} 112 | 113 | rule test { condition: 2.1 < 2} 114 | 115 | rule test { condition: "abc" != "abc"} 116 | 117 | rule test { condition: "abc" > "abc"} 118 | 119 | rule test { condition: "abc" < "abc"} 120 | 121 | 122 | // Arithmetic operators 123 | rule test { condition: (1 + 1) * 2 == (9 - 1) \\ 2 } 124 | 125 | rule test { condition: 5 % 2 == 1 } 126 | 127 | rule test { condition: 1.5 + 1.5 == 3} 128 | 129 | rule test { condition: 3 \\ 2 == 1} 130 | 131 | rule test { condition: 3.0 \\ 2 == 1.5} 132 | 133 | rule test { condition: 1 + -1 == 0} 134 | 135 | rule test { condition: -1 + -1 == -2} 136 | 137 | rule test { condition: 4 --2 * 2 == 8} 138 | 139 | rule test { condition: -1.0 * 1 == -1.0} 140 | 141 | rule test { condition: 1-1 == 0} 142 | 143 | rule test { condition: -2.0-3.0 == -5} 144 | 145 | rule test { condition: --1 == 1} 146 | 147 | rule test { condition: 1--1 == 2} 148 | 149 | rule test { condition: 2 * -2 == -4} 150 | 151 | rule test { condition: -4 * 2 == -8} 152 | 153 | rule test { condition: -4 * -4 == 16} 154 | 155 | rule test { condition: -0x01 == -1} 156 | 157 | rule test { condition: 0o10 == 8 } 158 | 159 | rule test { condition: 0o100 == 64 } 160 | 161 | rule test { condition: 0o755 == 493 } 162 | 163 | 164 | // Bitwise operators 165 | rule test { condition: 0x55 | 0xAA == 0xFF } 166 | 167 | rule test { condition: ~0xAA ^ 0x5A & 0xFF == (~0xAA) ^ (0x5A & 0xFF) } 168 | 169 | rule test { condition: ~0x55 & 0xFF == 0xAA } 170 | 171 | rule test { condition: 8 >> 2 == 2 } 172 | 173 | rule test { condition: 1 << 3 == 8 } 174 | 175 | rule test { condition: 1 << 64 == 0 } 176 | 177 | rule test { condition: 1 >> 64 == 0 } 178 | 179 | rule test { condition: 1 | 3 ^ 3 == 1 | (3 ^ 3) } 180 | 181 | rule test { condition: ~0xAA ^ 0x5A & 0xFF == 0x0F } 182 | 183 | rule test { condition: 1 | 3 ^ 3 == (1 | 3) ^ 3} 184 | 185 | 186 | // Anonymous strings 187 | rule test { strings: $ = "a" $ = "b" condition: all of them } 188 | 189 | 190 | // Strings 191 | rule test { strings: $a = "a" condition: $a } 192 | 193 | rule test { strings: $a = "ab" condition: $a } 194 | 195 | rule test { strings: $a = "abc" condition: $a } 196 | 197 | rule test { strings: $a = "xyz" condition: $a } 198 | 199 | rule test { strings: $a = "abc" nocase fullword condition: $a } 200 | 201 | rule test { strings: $a = "aBc" nocase condition: $a } 202 | 203 | rule test { strings: $a = "abc" fullword condition: $a } 204 | 205 | rule test { strings: $a = "a" fullword condition: $a } 206 | 207 | rule test { strings: $a = "ab" fullword condition: $a } 208 | 209 | rule test { strings: $a = "abc" wide fullword condition: $a } 210 | 211 | rule test { strings: $a = "a" wide condition: $a } 212 | 213 | rule test { strings: $a = "a" wide ascii condition: $a } 214 | 215 | rule test { strings: $a = "ab" wide condition: $a } 216 | 217 | rule test { strings: $a = "ab" wide ascii condition: $a } 218 | 219 | rule test { strings: $a = "abc" wide condition: $a } 220 | 221 | rule test { strings: $a = "abc" wide nocase fullword condition: $a } 222 | 223 | rule test { strings: $a = "aBc" wide nocase condition: $a } 224 | 225 | rule test { strings: $a = "aBc" wide ascii nocase condition: $a } 226 | 227 | rule test { strings: $a = "---xyz" wide nocase condition: $a } 228 | 229 | rule test { strings: $a = "abc" fullword condition: $a } 230 | 231 | rule test { strings: $a = "abc" fullword condition: $a } 232 | 233 | rule test { strings: $a = "abc" fullword condition: $a } 234 | 235 | rule test { strings: $a = "abc" fullword condition: $a } 236 | 237 | rule test { strings: $a = "abc" wide condition: $a } 238 | 239 | rule test { strings: $a = "abcdef" wide condition: $a } 240 | 241 | rule test { strings: $a = "abc" ascii wide fullword condition: $a } 242 | 243 | rule test { strings: $a = "abc" ascii wide fullword condition: $a } 244 | 245 | rule test { strings: $a = "abc" wide fullword condition: $a } 246 | 247 | rule test { strings: $a = "abc" wide fullword condition: $a } 248 | 249 | rule test { strings: $a = "ab" wide fullword condition: $a } 250 | 251 | rule test { strings: $a = "abc" wide fullword condition: $a } 252 | 253 | rule test { strings: $a = "abc" wide fullword condition: $a } 254 | 255 | rule test { 256 | strings: 257 | $a = "abcdef" 258 | $b = "cdef" 259 | $c = "ef" 260 | condition: 261 | all of them 262 | } 263 | 264 | rule test { 265 | strings: 266 | $a = "This program cannot" xor 267 | condition: 268 | #a == 255 269 | } 270 | 271 | rule test { 272 | strings: 273 | $a = "This program cannot" xor ascii 274 | condition: 275 | #a == 256 276 | } 277 | 278 | rule test { 279 | strings: 280 | $a = "This program cannot" xor wide 281 | condition: 282 | #a == 256 283 | } 284 | 285 | rule test { 286 | strings: 287 | $a = "ab" xor fullword 288 | condition: 289 | #a == 1084 290 | } 291 | 292 | 293 | // Wildcard strings 294 | rule test { 295 | strings: 296 | $s1 = "abc" 297 | $s2 = "xyz" 298 | condition: 299 | for all of ($*) : ($) 300 | } 301 | 302 | 303 | // Hex strings 304 | rule test { 305 | strings: $a = { 64 01 00 00 60 01 } 306 | condition: $a } 307 | 308 | rule test { 309 | strings: $a = { 64 0? 00 00 ?0 01 } 310 | condition: $a } 311 | 312 | rule test { 313 | strings: $a = { 6? 01 00 00 60 0? } 314 | condition: $a } 315 | 316 | rule test { 317 | strings: $a = { 64 01 [1-3] 60 01 } 318 | condition: $a } 319 | 320 | rule test { 321 | strings: $a = { 64 01 [1-3] (60|61) 01 } 322 | condition: $a } 323 | 324 | rule test { 325 | strings: $a = { 4D 5A [-] 6A 2A [-] 58 C3} 326 | condition: $a } 327 | 328 | rule test { 329 | strings: $a = { 4D 5A [300-] 6A 2A [-] 58 C3} 330 | condition: $a } 331 | 332 | rule test { 333 | strings: $a = { 2e 7? (65 | ?? ) 78 } 334 | condition: $a } 335 | 336 | rule test { 337 | strings: $a = { 4D 5A [0-300] 6A 2A } 338 | condition: $a } 339 | 340 | rule test { 341 | strings: $a = { 4D 5A [0-128] 45 [0-128] 01 [0-128] C3 } 342 | condition: $a } 343 | 344 | rule test { 345 | strings: $a = { 31 32 [-] 38 39 } 346 | condition: $a } 347 | 348 | rule test { 349 | strings: $a = { 31 32 [-] // Inline comment 350 | 38 39 } 351 | condition: $a } 352 | 353 | rule test { 354 | strings: $a = { 31 32 /* Inline comment */ [-] 38 39 } 355 | condition: $a } 356 | 357 | rule test { 358 | strings: $a = { 31 32 /* Inline multi-line 359 | comment */ [-] 38 39 } 360 | condition: $a } 361 | 362 | rule test { 363 | strings: $a = { 364 | 31 32 [-] 38 39 365 | } 366 | condition: $a } 367 | 368 | rule test { 369 | strings: $a = { 31 32 [-] 33 34 [-] 38 39 } 370 | condition: $a } 371 | 372 | rule test { 373 | strings: $a = { 31 32 [1] 34 35 [2] 38 39 } 374 | condition: $a } 375 | 376 | rule test { 377 | strings: $a = { 31 32 [1-] 34 35 [1-] 38 39 } 378 | condition: $a } 379 | 380 | rule test { 381 | strings: $a = { 31 32 [0-3] 34 35 [1-] 38 39 } 382 | condition: $a } 383 | 384 | rule test { 385 | strings: $a = { 31 32 [0-2] 35 [1-] 37 38 39 } 386 | condition: $a } 387 | 388 | rule test { 389 | strings: $a = { 31 32 [0-1] 33 } 390 | condition: !a == 3} 391 | 392 | rule test { 393 | strings: $a = { 31 32 [0-1] 34 } 394 | condition: !a == 4} 395 | 396 | rule test { 397 | strings: $a = { 31 32 [0-2] 34 } 398 | condition: !a == 4 } 399 | 400 | rule test { 401 | strings: $a = { 31 32 [-] 38 39 } 402 | condition: all of them } 403 | 404 | rule test { 405 | strings: $a = { 31 32 [-] 32 33 } 406 | condition: $a } 407 | 408 | rule test { 409 | strings: $a = { 35 36 [-] 31 32 } 410 | condition: $a } 411 | 412 | rule test { 413 | strings: $a = { 31 32 [2-] 34 35 } 414 | condition: $a } 415 | 416 | rule test { 417 | strings: $a = { 31 32 [0-1] 33 34 [0-2] 36 37 } 418 | condition: $a } 419 | 420 | rule test { 421 | strings: $a = { 31 32 [0-1] 34 35 [0-2] 36 37 } 422 | condition: $a } 423 | 424 | rule test { 425 | strings: $a = { 31 32 [0-3] 37 38 } 426 | condition: $a } 427 | 428 | rule test { 429 | strings: $a = { 31 32 [1] 33 34 } 430 | condition: $a } 431 | 432 | rule test { 433 | strings: $a = {31 32 [3-6] 32} 434 | condition: !a == 6 } 435 | 436 | rule test { 437 | strings: $a = {31 [0-3] (32|33)} 438 | condition: !a == 2 } 439 | 440 | 441 | // Test count 442 | rule test { strings: $a = "ssi" condition: #a == 2 } 443 | 444 | 445 | // Test at 446 | rule test { 447 | strings: $a = "ssi" 448 | condition: $a at 2 and $a at 5 } 449 | 450 | rule test { 451 | strings: $a = "mis" 452 | condition: $a at ~0xFF & 0xFF } 453 | 454 | rule test { 455 | strings: $a = { 00 00 00 00 ?? 74 65 78 74 } 456 | condition: $a at 308} 457 | 458 | 459 | // Test in 460 | rule test { 461 | strings: $a = { 6a 2a 58 c3 } 462 | condition: $a in (entrypoint .. entrypoint + 1) } 463 | 464 | 465 | // Test offset 466 | rule test { strings: $a = "ssi" condition: @a == 2 } 467 | 468 | rule test { strings: $a = "ssi" condition: @a == @a[1] } 469 | 470 | rule test { strings: $a = "ssi" condition: @a[2] == 5 } 471 | 472 | 473 | // Test length 474 | rule test { strings: $a = /m.*?ssi/ condition: !a == 5 } 475 | 476 | rule test { strings: $a = /m.*?ssi/ condition: !a[1] == 5 } 477 | 478 | rule test { strings: $a = /m.*ssi/ condition: !a == 8 } 479 | 480 | rule test { strings: $a = /m.*ssi/ condition: !a[1] == 8 } 481 | 482 | rule test { strings: $a = /ssi.*ppi/ condition: !a[1] == 9 } 483 | 484 | rule test { strings: $a = /ssi.*ppi/ condition: !a[2] == 6 } 485 | 486 | rule test { strings: $a = { 6D [1-3] 73 73 69 } condition: !a == 5} 487 | 488 | rule test { strings: $a = { 6D [-] 73 73 69 } condition: !a == 5} 489 | 490 | rule test { strings: $a = { 6D [-] 70 70 69 } condition: !a == 11} 491 | 492 | rule test { strings: $a = { 6D 69 73 73 [-] 70 69 } condition: !a == 11} 493 | 494 | 495 | // Test of 496 | rule test { strings: $a = "ssi" $b = "mis" $c = "oops" 497 | condition: any of them } 498 | 499 | rule test { strings: $a = "ssi" $b = "mis" $c = "oops" 500 | condition: 1 of them } 501 | 502 | rule test { strings: $a = "ssi" $b = "mis" $c = "oops" 503 | condition: 2 of them } 504 | 505 | rule test { strings: $a1 = "dummy1" $b1 = "dummy1" $b2 = "ssi" 506 | condition: any of ($a*, $b*) } 507 | 508 | rule test { 509 | strings: 510 | $ = /abc/ 511 | $ = /def/ 512 | $ = /ghi/ 513 | condition: 514 | for any of ($*) : ( for any i in (1..#): (uint8(@[i] - 1) == 0x00) ) 515 | } 516 | 517 | rule test { 518 | strings: 519 | $a = "ssi" 520 | $b = "mis" 521 | $c = "oops" 522 | condition: 523 | all of them 524 | } 525 | 526 | 527 | // Test for 528 | rule test { 529 | strings: 530 | $a = "ssi" 531 | condition: 532 | for all i in (1..#a) : (@a[i] >= 2 and @a[i] <= 5) 533 | } 534 | 535 | rule test { 536 | strings: 537 | $a = "ssi" 538 | $b = "mi" 539 | condition: 540 | for all i in (1..#a) : ( for all j in (1..#b) : (@a[i] >= @b[j])) 541 | } 542 | 543 | rule test { 544 | strings: 545 | $a = "ssi" 546 | condition: 547 | for all i in (1..#a) : (@a[i] == 5) 548 | } 549 | 550 | 551 | // Test re 552 | rule test { strings: $a = /ssi/ condition: $a } 553 | 554 | rule test { strings: $a = /ssi(s|p)/ condition: $a } 555 | 556 | rule test { strings: $a = /ssim*/ condition: $a } 557 | 558 | rule test { strings: $a = /ssa?/ condition: $a } 559 | 560 | rule test { strings: $a = /Miss/ nocase condition: $a } 561 | 562 | rule test { strings: $a = /(M|N)iss/ nocase condition: $a } 563 | 564 | rule test { strings: $a = /[M-N]iss/ nocase condition: $a } 565 | 566 | rule test { strings: $a = /(Mi|ssi)ssippi/ nocase condition: $a } 567 | 568 | rule test { strings: $a = /ppi\\tmi/ condition: $a } 569 | 570 | rule test { strings: $a = /ppi\\.mi/ condition: $a } 571 | 572 | rule test { strings: $a = /^mississippi/ fullword condition: $a } 573 | 574 | rule test { strings: $a = /mississippi.*mississippi$/s condition: $a } 575 | 576 | rule test { strings: $a = /^ssi/ condition: $a } 577 | 578 | rule test { strings: $a = /ssi$/ condition: $a } 579 | 580 | rule test { strings: $a = /ssissi/ fullword condition: $a } 581 | 582 | rule test { strings: $a = /^[isp]+/ condition: $a } 583 | 584 | rule test { strings: $a = /a.{1,2}b/ wide condition: !a == 6 } 585 | 586 | rule test { strings: $a = /a.{1,2}b/ wide condition: !a == 8 } 587 | 588 | rule test { strings: $a = /\\babc/ wide condition: $a } 589 | 590 | rule test { strings: $a = /\\babc/ wide condition: $a } 591 | 592 | rule test { strings: $a = /\\babc/ wide condition: $a } 593 | 594 | rule test { strings: $a = /\\babc/ wide condition: $a } 595 | 596 | rule test { strings: $a = /\\babc/ wide condition: $a } 597 | 598 | rule test { strings: $a = /abc\\b/ wide condition: $a } 599 | 600 | rule test { strings: $a = /abc\\b/ wide condition: $a } 601 | 602 | rule test { strings: $a = /abc\\b/ wide condition: $a } 603 | 604 | rule test { strings: $a = /abc\\b/ wide condition: $a } 605 | 606 | rule test { strings: $a = /abc\\b/ wide condition: $a } 607 | 608 | rule test { strings: $a = /\\b/ wide condition: $a } 609 | 610 | rule test { 611 | strings: $a = /MZ.{300,}t/ 612 | condition: !a == 317 } 613 | 614 | rule test { 615 | strings: $a = /MZ.{300,}?t/ 616 | condition: !a == 314 } 617 | 618 | rule test { strings: $a = /abc[^d]/ nocase condition: $a } 619 | 620 | rule test { strings: $a = /abc[^d]/ condition: $a } 621 | 622 | rule test { strings: $a = /abc[^D]/ nocase condition: $a } 623 | 624 | rule test { strings: $a = /abc[^D]/ condition: $a } 625 | 626 | rule test { strings: $a = /abc[^f]/ nocase condition: $a } 627 | 628 | rule test { strings: $a = /abc[^f]/ condition: $a } 629 | 630 | rule test { strings: $a = /abc[^F]/ nocase condition: $a } 631 | 632 | rule test { strings: $a = /abc[^F]/ condition: $a } 633 | 634 | rule test { strings: $a = " cmd.exe " nocase wide condition: $a } 635 | 636 | 637 | // Test entry point 638 | rule test { 639 | strings: $a = { 6a 2a 58 c3 } 640 | condition: $a at entrypoint } 641 | 642 | rule test { 643 | strings: $a = { b8 01 00 00 00 bb 2a } 644 | condition: $a at entrypoint } 645 | 646 | rule test { 647 | strings: $a = { b8 01 00 00 00 bb 2a } 648 | condition: $a at entrypoint } 649 | 650 | rule test { condition: entrypoint >= 0 } 651 | 652 | 653 | // Test file size 654 | rule test { condition: filesize == %zd } 655 | 656 | 657 | // Test comments 658 | rule test { 659 | condition: 660 | // this is a comment 661 | /*** this is a comment ***/ 662 | /* /* /* 663 | this is a comment 664 | */ 665 | true 666 | } 667 | 668 | 669 | // Test matches operator 670 | rule test { condition: "foo" matches /foo/ } 671 | 672 | rule test { condition: "foo" matches /bar/ } 673 | 674 | rule test { condition: "FoO" matches /fOo/i } 675 | 676 | rule test { condition: "xxFoOxx" matches /fOo/i } 677 | 678 | rule test { condition: "xxFoOxx" matches /^fOo/i } 679 | 680 | rule test { condition: "xxFoOxx" matches /fOo$/i } 681 | 682 | rule test { condition: "foo" matches /^foo$/i } 683 | 684 | rule test { condition: "foo\\nbar" matches /foo.*bar/s } 685 | 686 | rule test { condition: "foo\\nbar" matches /foo.*bar/ } 687 | 688 | 689 | // Test global rules 690 | global private rule global_rule { 691 | condition: 692 | true 693 | } 694 | rule test { 695 | condition: true 696 | } 697 | 698 | global private rule global_rule { 699 | condition: 700 | false 701 | } 702 | rule test { 703 | condition: true 704 | } 705 | 706 | 707 | // Test modules 708 | import "tests" 709 | rule test { 710 | condition: tests.constants.one + 1 == tests.constants.two 711 | } 712 | 713 | import "tests" 714 | rule test { 715 | condition: tests.constants.foo == "foo" 716 | } 717 | 718 | import "tests" 719 | rule test { 720 | condition: tests.constants.empty == "" 721 | } 722 | 723 | import "tests" 724 | rule test { 725 | condition: tests.empty() == "" 726 | } 727 | 728 | import "tests" 729 | rule test { 730 | condition: tests.struct_array[1].i == 1 731 | } 732 | 733 | import "tests" 734 | rule test { 735 | condition: tests.struct_array[0].i == 1 or true 736 | } 737 | 738 | import "tests" 739 | rule test { 740 | condition: tests.integer_array[0] == 0 741 | } 742 | 743 | import "tests" 744 | rule test { 745 | condition: tests.integer_array[1] == 1 746 | } 747 | 748 | import "tests" 749 | rule test { 750 | condition: tests.integer_array[256] == 256 751 | } 752 | 753 | import "tests" 754 | rule test { 755 | condition: tests.string_array[0] == "foo" 756 | } 757 | 758 | import "tests" 759 | rule test { 760 | condition: tests.string_array[2] == "baz" 761 | } 762 | 763 | import "tests" 764 | rule test { 765 | condition: tests.string_dict["foo"] == "foo" 766 | } 767 | 768 | import "tests" 769 | rule test { 770 | condition: tests.string_dict["bar"] == "bar" 771 | } 772 | 773 | import "tests" 774 | rule test { 775 | condition: tests.isum(1,2) == 3 776 | } 777 | 778 | import "tests" 779 | rule test { 780 | condition: tests.isum(1,2,3) == 6 781 | } 782 | 783 | import "tests" 784 | rule test { 785 | condition: tests.fsum(1.0,2.0) == 3.0 786 | } 787 | 788 | import "tests" 789 | rule test { 790 | condition: tests.fsum(1.0,2.0,3.0) == 6.0 791 | } 792 | 793 | import "tests" 794 | rule test { 795 | condition: tests.foobar(1) == tests.foobar(1) 796 | } 797 | 798 | import "tests" 799 | rule test { 800 | condition: tests.foobar(1) != tests.foobar(2) 801 | } 802 | 803 | import "tests" 804 | rule test { 805 | condition: tests.length("dummy") == 5 806 | } 807 | 808 | import "tests" 809 | rule test { condition: tests.struct_array[0].i == 1 810 | } 811 | 812 | import "tests" 813 | rule test { condition: tests.isum(1,1) == 3 814 | } 815 | 816 | import "tests" 817 | rule test { condition: tests.fsum(1.0,1.0) == 3.0 818 | } 819 | 820 | import "tests" 821 | rule test { condition: tests.match(/foo/,"foo") == 3 822 | } 823 | 824 | import "tests" 825 | rule test { condition: tests.match(/foo/,"bar") == -1 826 | } 827 | 828 | import "tests" 829 | rule test { condition: tests.match(/foo.bar/i,"FOO\\nBAR") == -1 830 | } 831 | 832 | import "tests" 833 | rule test { condition: tests.match(/foo.bar/is,"FOO\\nBAR") == 7 834 | } 835 | 836 | 837 | // Test time module 838 | import "time" 839 | rule test { condition: time.now() > 0 } 840 | 841 | 842 | // Test hash module 843 | import "hash" 844 | rule test { 845 | condition: 846 | hash.md5(0, filesize) == 847 | "ab56b4d92b40713acc5af89985d4b786" 848 | and 849 | hash.md5(1, filesize) == 850 | "e02cfbe5502b64aa5ae9f2d0d69eaa8d" 851 | and 852 | hash.sha1(0, filesize) == 853 | "03de6c570bfe24bfc328ccd7ca46b76eadaf4334" 854 | and 855 | hash.sha1(1, filesize) == 856 | "a302d65ae4d9e768a1538d53605f203fd8e2d6e2" 857 | and 858 | hash.sha256(0, filesize) == 859 | "36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c" 860 | and 861 | hash.sha256(1, filesize) == 862 | "aaaaf2863e043b9df604158ad5c16ff1adaf3fd7e9fcea5dcb322b6762b3b59a" 863 | } 864 | 865 | import "hash" 866 | rule test { 867 | condition: 868 | hash.md5(0, filesize) == 869 | "ab56b4d92b40713acc5af89985d4b786" 870 | and 871 | hash.md5(1, filesize) == 872 | "e02cfbe5502b64aa5ae9f2d0d69eaa8d" 873 | and 874 | hash.md5(0, filesize) == 875 | "ab56b4d92b40713acc5af89985d4b786" 876 | and 877 | hash.md5(1, filesize) == 878 | "e02cfbe5502b64aa5ae9f2d0d69eaa8d" 879 | } 880 | 881 | 882 | // Test integer functions 883 | rule test { condition: uint8(0) == 0xAA} 884 | 885 | rule test { condition: uint16(0) == 0xBBAA} 886 | 887 | rule test { condition: uint32(0) == 0xDDCCBBAA} 888 | 889 | rule test { condition: uint8be(0) == 0xAA} 890 | 891 | rule test { condition: uint16be(0) == 0xAABB} 892 | 893 | rule test { condition: uint32be(0) == 0xAABBCCDD} 894 | 895 | 896 | // Test include files 897 | include "tests/data/baz.yar" rule t { condition: baz } 898 | 899 | include "tests/data/foo.yar" rule t { condition: foo } 900 | 901 | 902 | // Test process scan 903 | rule test { 904 | strings: 905 | $a = { 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 } 906 | condition: 907 | all of them 908 | } 909 | 910 | 911 | // Test performance warnings 912 | rule test { 913 | strings: $a = { 01 } 914 | condition: $a } 915 | 916 | rule test { 917 | strings: $a = { 01 ?? } 918 | condition: $a } 919 | 920 | rule test { 921 | strings: $a = { 01 ?? ?? } 922 | condition: $a } 923 | 924 | rule test { 925 | strings: $a = { 01 ?? ?? 02 } 926 | condition: $a } 927 | 928 | rule test { 929 | strings: $a = { 01 ?? ?2 03 } 930 | condition: $a } 931 | 932 | rule test { 933 | strings: $a = { 01 ?? 02 1? } 934 | condition: $a } 935 | 936 | rule test { 937 | strings: $a = { 1? 2? 3? } 938 | condition: $a } 939 | 940 | rule test { 941 | strings: $a = { 1? 2? 3? 04 } 942 | condition: $a } 943 | 944 | rule test { 945 | strings: $a = { 1? ?? 03 } 946 | condition: $a } 947 | 948 | rule test { 949 | strings: $a = { 00 01 } 950 | condition: $a } 951 | 952 | rule test { 953 | strings: $a = { 01 00 } 954 | condition: $a } 955 | 956 | rule test { 957 | strings: $a = { 00 00 } 958 | condition: $a } 959 | 960 | rule test { 961 | strings: $a = { 00 00 00 } 962 | condition: $a } 963 | 964 | rule test { 965 | strings: $a = { 00 00 01 } 966 | condition: $a } 967 | 968 | rule test { 969 | strings: $a = { 00 00 00 00 } 970 | condition: $a } 971 | 972 | rule test { 973 | strings: $a = { 00 00 00 01 } 974 | condition: $a } 975 | 976 | rule test { 977 | strings: $a = { FF FF FF FF } 978 | condition: $a } 979 | 980 | rule test { 981 | strings: $a = { 00 00 01 02 } 982 | condition: $a } 983 | 984 | rule test { 985 | strings: $a = { 00 01 02 03 } 986 | condition: $a } 987 | 988 | rule test { 989 | strings: $a = { 01 02 03 04 } 990 | condition: $a } 991 | 992 | rule test { 993 | strings: $a = { 01 02 03 } 994 | condition: $a } 995 | 996 | rule test { 997 | strings: $a = { 20 01 02 } 998 | condition: $a } 999 | 1000 | rule test { 1001 | strings: $a = { 01 02 } 1002 | condition: $a } 1003 | 1004 | rule test { 1005 | strings: $a = "foo" wide 1006 | condition: $a } 1007 | 1008 | rule test { 1009 | strings: $a = "MZ" 1010 | condition: $a } 1011 | -------------------------------------------------------------------------------- /plyara/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2014 Christian Buia 3 | # Copyright 2020 plyara Maintainers 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """Parse YARA rules and operate over them more easily. 17 | 18 | Plyara is a script and library that lexes and parses a file consisting of one more YARA rules into a python 19 | dictionary representation. The goal of this tool is to make it easier to perform bulk operations or transformations of 20 | large sets of YARA rules, such as extracting indicators, updating attributes, and analyzing a corpus. Other applications 21 | include linters and dependency checkers. 22 | """ 23 | import enum 24 | import logging 25 | import string 26 | import tempfile 27 | import re 28 | 29 | import ply.lex as lex 30 | import ply.yacc as yacc 31 | 32 | from plyara.exceptions import ParseTypeError, ParseValueError 33 | 34 | # Initialize the logger 35 | logger = logging.getLogger(__name__) 36 | 37 | 38 | class ElementTypes(enum.Enum): 39 | """An enumeration of the element types emitted by the parser to the interpreter.""" 40 | 41 | RULE_NAME = 1 42 | METADATA_KEY_VALUE = 2 43 | STRINGS_KEY_VALUE = 3 44 | STRINGS_MODIFIER = 4 45 | IMPORT = 5 46 | TERM = 6 47 | SCOPE = 7 48 | TAG = 8 49 | INCLUDE = 9 50 | COMMENT = 10 51 | MCOMMENT = 11 52 | 53 | 54 | class StringTypes(enum.Enum): 55 | """String types found in a YARA rule.""" 56 | 57 | TEXT = 1 58 | BYTE = 2 59 | REGEX = 3 60 | 61 | 62 | class Parser: 63 | """Interpret the output of the parser and produce an alternative representation of YARA rules.""" 64 | 65 | EXCLUSIVE_TEXT_MODIFIERS = {'nocase', 'xor', 'base64'} 66 | 67 | COMPARISON_OPERATORS = {'==', '!=', '>', '<', '>=', '<='} 68 | 69 | IMPORT_OPTIONS = {'pe', 70 | 'elf', 71 | 'cuckoo', 72 | 'magic', 73 | 'hash', 74 | 'math', 75 | 'dotnet', 76 | 'androguard', 77 | 'time'} 78 | 79 | KEYWORDS = {'all', 'and', 'any', 'ascii', 'at', 'condition', 80 | 'contains', 'entrypoint', 'false', 'filesize', 81 | 'fullword', 'for', 'global', 'in', 'import', 82 | 'include', 'int8', 'int16', 'int32', 'int8be', 83 | 'int16be', 'int32be', 'matches', 'meta', 'nocase', 84 | 'not', 'or', 'of', 'private', 'rule', 'strings', 85 | 'them', 'true', 'uint8', 'uint16', 'uint32', 'uint8be', 86 | 'uint16be', 'uint32be', 'wide', 'xor', 'base64', 'base64wide'} 87 | 88 | FUNCTION_KEYWORDS = {'uint8', 'uint16', 'uint32', 'uint8be', 'uint16be', 'uint32be'} 89 | 90 | def __init__(self, console_logging=False, store_raw_sections=True, meta_as_kv=False): 91 | """Initialize the parser object. 92 | 93 | Args: 94 | console_logging: Enable a stream handler if no handlers exist. (default False) 95 | store_raw_sections: Enable attribute storage of raw section input. (default True) 96 | meta_as_kv: Enable alternate structure for meta section as dictionary. (default False) 97 | """ 98 | self.rules = list() 99 | 100 | self.current_rule = dict() 101 | 102 | self.string_modifiers = list() 103 | self.imports = set() 104 | self.includes = list() 105 | self.terms = list() 106 | self.scopes = list() 107 | self.tags = list() 108 | self.comments = list() 109 | 110 | if console_logging: 111 | self._set_logging() 112 | 113 | # adds functionality to track attributes containing raw section data 114 | # in case needed (ie modifying metadata and re-constructing a complete rule 115 | # while maintaining original comments and padding) 116 | self.store_raw_sections = store_raw_sections 117 | self._raw_input = None 118 | self._meta_start = None 119 | self._meta_end = None 120 | self._strings_start = None 121 | self._strings_end = None 122 | self._condition_start = None 123 | self._condition_end = None 124 | self._rule_comments = list() 125 | self._stringnames = set() 126 | 127 | # Adds a dictionary representation of the meta section of a rule 128 | self.meta_as_kv = meta_as_kv 129 | 130 | self.lexer = lex.lex(module=self, debug=False) 131 | self.parser = yacc.yacc(module=self, debug=False, outputdir=tempfile.gettempdir()) 132 | 133 | def clear(self): 134 | """Clear all information about previously parsed rules.""" 135 | self.rules.clear() 136 | 137 | self.current_rule.clear() 138 | 139 | self.string_modifiers.clear() 140 | self.imports.clear() 141 | self.includes.clear() 142 | self.terms.clear() 143 | self.scopes.clear() 144 | self.tags.clear() 145 | self.comments.clear() 146 | 147 | self._raw_input = None 148 | self._meta_start = None 149 | self._meta_end = None 150 | self._strings_start = None 151 | self._strings_end = None 152 | self._condition_start = None 153 | self._condition_end = None 154 | self._rule_comments.clear() 155 | self._stringnames.clear() 156 | 157 | if self.lexer.lineno > 1: 158 | # Per https://ply.readthedocs.io/en/latest/ply.html#panic-mode-recovery 159 | # This discards the entire parsing stack and resets the parser to its 160 | # initial state. 161 | self.parser.restart() 162 | # Per https://ply.readthedocs.io/en/latest/ply.html#eof-handling 163 | # Be aware that setting more input with the self.lexer.input() method 164 | # does NOT reset the lexer state or the lineno attribute used for 165 | # position tracking. 166 | self.lexer.lineno = 1 167 | 168 | @staticmethod 169 | def _set_logging(): 170 | """Set the console logger only if handler(s) aren't already set.""" 171 | if not len(logger.handlers): 172 | logger.setLevel(logging.DEBUG) 173 | ch = logging.StreamHandler() 174 | ch.setLevel(logging.DEBUG) 175 | logger.addHandler(ch) 176 | 177 | def _add_element(self, element_type, element_value): 178 | """Accept elements from the parser and uses them to construct a representation of the YARA rule. 179 | 180 | Args: 181 | element_type: The element type determined by the parser. Input is one of ElementTypes. 182 | element_value: This is the contents of the element as parsed from the rule. 183 | """ 184 | if element_type == ElementTypes.RULE_NAME: 185 | rule_name, start_line, stop_line = element_value 186 | self.current_rule['rule_name'] = rule_name 187 | self.current_rule['start_line'] = start_line 188 | self.current_rule['stop_line'] = stop_line 189 | 190 | if self.store_raw_sections: 191 | if self._meta_start: 192 | self.current_rule['raw_meta'] = self._raw_input[self._meta_start:self._meta_end] 193 | 194 | if self._strings_start: 195 | self.current_rule['raw_strings'] = self._raw_input[self._strings_start:self._strings_end] 196 | 197 | if self._condition_start: 198 | self.current_rule['raw_condition'] = self._raw_input[self._condition_start:self._condition_end] 199 | 200 | self._flush_accumulators() 201 | 202 | self.rules.append(self.current_rule) 203 | logger.debug('Adding Rule: {}'.format(self.current_rule['rule_name'])) 204 | self.current_rule = dict() 205 | self._stringnames.clear() 206 | 207 | elif element_type == ElementTypes.METADATA_KEY_VALUE: 208 | key, value = element_value 209 | 210 | if 'metadata' not in self.current_rule: 211 | self.current_rule['metadata'] = [{key: value}] 212 | if self.meta_as_kv: 213 | self.current_rule['metadata_kv'] = {key: value} 214 | else: 215 | self.current_rule['metadata'].append({key: value}) 216 | if self.meta_as_kv: 217 | self.current_rule['metadata_kv'][key] = value 218 | 219 | elif element_type == ElementTypes.STRINGS_KEY_VALUE: 220 | key, value, string_type = element_value 221 | 222 | string_dict = {'name': key, 'value': value, 'type': string_type.name.lower()} 223 | 224 | if any(self.string_modifiers): 225 | string_dict['modifiers'] = self.string_modifiers 226 | self.string_modifiers = list() 227 | 228 | if 'strings' not in self.current_rule: 229 | self.current_rule['strings'] = [string_dict] 230 | else: 231 | self.current_rule['strings'].append(string_dict) 232 | 233 | elif element_type == ElementTypes.STRINGS_MODIFIER: 234 | self.string_modifiers.append(element_value) 235 | 236 | elif element_type == ElementTypes.IMPORT: 237 | self.imports.add(element_value) 238 | 239 | elif element_type == ElementTypes.INCLUDE: 240 | self.includes.append(element_value) 241 | 242 | elif element_type == ElementTypes.TERM: 243 | self.terms.append(element_value) 244 | 245 | elif element_type == ElementTypes.SCOPE: 246 | self.scopes.append(element_value) 247 | 248 | elif element_type == ElementTypes.TAG: 249 | self.tags.append(element_value) 250 | 251 | elif element_type == ElementTypes.COMMENT: 252 | self.comments.append(element_value) 253 | 254 | elif element_type == ElementTypes.MCOMMENT: 255 | self.comments.append(element_value) 256 | 257 | def _flush_accumulators(self): 258 | """Add accumulated elements to the current rule and resets the accumulators.""" 259 | if any(self.terms): 260 | self.current_rule['condition_terms'] = self.terms 261 | self.terms = list() 262 | 263 | if any(self.scopes): 264 | self.current_rule['scopes'] = self.scopes 265 | self.scopes = list() 266 | 267 | if any(self.tags): 268 | self.current_rule['tags'] = self.tags 269 | self.tags = list() 270 | 271 | if any(self.comments): 272 | self.current_rule['comments'] = self.comments 273 | self.comments = list() 274 | 275 | self._meta_start = None 276 | self._meta_end = None 277 | self._strings_start = None 278 | self._strings_end = None 279 | self._condition_start = None 280 | self._condition_end = None 281 | 282 | def parse_string(self, input_string): 283 | """Take a string input expected to consist of YARA rules, and return list of dictionaries representing them. 284 | 285 | Args: 286 | input_string: String input expected to consist of YARA rules. 287 | 288 | Returns: 289 | dict: All the parsed components of a YARA rule. 290 | """ 291 | self._raw_input = input_string 292 | self.parser.parse(input_string, lexer=self.lexer) 293 | 294 | for rule in self.rules: 295 | if any(self.imports): 296 | rule['imports'] = list(self.imports) 297 | if any(self.includes): 298 | rule['includes'] = self.includes 299 | 300 | return self.rules 301 | 302 | 303 | class Plyara(Parser): 304 | """Define the lexer and the parser rules.""" 305 | 306 | STRING_ESCAPE_CHARS = {'"', '\\', 't', 'n', 'x'} 307 | 308 | tokens = [ 309 | 'BYTESTRING', 310 | 'STRING', 311 | 'REXSTRING', 312 | 'EQUALS', 313 | 'STRINGNAME', 314 | 'STRINGNAME_ARRAY', 315 | 'STRINGNAME_COUNT', 316 | 'STRINGNAME_LENGTH', 317 | 'LPAREN', 318 | 'RPAREN', 319 | 'LBRACK', 320 | 'RBRACK', 321 | 'LBRACE', 322 | 'RBRACE', 323 | 'ID', 324 | 'BACKSLASH', 325 | 'FORWARDSLASH', 326 | 'PIPE', 327 | 'PLUS', 328 | 'SECTIONMETA', 329 | 'SECTIONSTRINGS', 330 | 'SECTIONCONDITION', 331 | 'COMMA', 332 | 'GREATERTHAN', 333 | 'LESSTHAN', 334 | 'GREATEREQUAL', 335 | 'LESSEQUAL', 336 | 'RIGHTBITSHIFT', 337 | 'LEFTBITSHIFT', 338 | 'MODULO', 339 | 'TILDE', 340 | 'XOR_OP', # XOR operator token (from conditions section) 341 | 'PERIOD', 342 | 'COLON', 343 | 'STAR', 344 | 'HYPHEN', 345 | 'AMPERSAND', 346 | 'NEQUALS', 347 | 'EQUIVALENT', 348 | 'DOTDOT', 349 | 'HEXNUM', 350 | 'FILESIZE_SIZE', 351 | 'NUM', 352 | 'COMMENT', 353 | 'MCOMMENT' 354 | ] 355 | 356 | reserved = { 357 | 'all': 'ALL', 358 | 'and': 'AND', 359 | 'any': 'ANY', 360 | 'ascii': 'ASCII', 361 | 'at': 'AT', 362 | 'contains': 'CONTAINS', 363 | 'entrypoint': 'ENTRYPOINT', 364 | 'false': 'FALSE', 365 | 'filesize': 'FILESIZE', 366 | 'for': 'FOR', 367 | 'fullword': 'FULLWORD', 368 | 'global': 'GLOBAL', 369 | 'import': 'IMPORT', 370 | 'in': 'IN', 371 | 'include': 'INCLUDE', 372 | 'int8': 'INT8', 373 | 'int16': 'INT16', 374 | 'int32': 'INT32', 375 | 'int8be': 'INT8BE', 376 | 'int16be': 'INT16BE', 377 | 'int32be': 'INT32BE', 378 | 'matches': 'MATCHES', 379 | 'nocase': 'NOCASE', 380 | 'not': 'NOT', 381 | 'of': 'OF', 382 | 'or': 'OR', 383 | 'private': 'PRIVATE', 384 | 'rule': 'RULE', 385 | 'them': 'THEM', 386 | 'true': 'TRUE', 387 | 'wide': 'WIDE', 388 | 'uint8': 'UINT8', 389 | 'uint16': 'UINT16', 390 | 'uint32': 'UINT32', 391 | 'uint8be': 'UINT8BE', 392 | 'uint16be': 'UINT16BE', 393 | 'uint32be': 'UINT32BE', 394 | 'xor': 'XOR_MOD', # XOR string modifier token (from strings section) 395 | 'base64': 'BASE64', 396 | 'base64wide': 'BASE64WIDE' 397 | } 398 | 399 | tokens = tokens + list(reserved.values()) 400 | 401 | # Regular expression rules for simple tokens 402 | t_LPAREN = r'\(' 403 | t_RPAREN = r'\)' 404 | t_EQUIVALENT = r'==' 405 | t_NEQUALS = r'!=' 406 | t_EQUALS = r'=' 407 | t_LBRACE = r'{' 408 | t_PLUS = r'\+' 409 | t_PIPE = r'\|' 410 | t_BACKSLASH = r'\\' 411 | t_FORWARDSLASH = r'/' 412 | t_COMMA = r',' 413 | t_GREATERTHAN = r'>' 414 | t_LESSTHAN = r'<' 415 | t_GREATEREQUAL = r'>=' 416 | t_LESSEQUAL = r'<=' 417 | t_RIGHTBITSHIFT = r'>>' 418 | t_LEFTBITSHIFT = r'<<' 419 | t_MODULO = r'%' 420 | t_TILDE = r'~' 421 | t_XOR_OP = r'\^' 422 | t_PERIOD = r'\.' 423 | t_COLON = r':' 424 | t_STAR = r'\*' 425 | t_LBRACK = r'\[' 426 | t_RBRACK = r'\]' 427 | t_HYPHEN = r'\-' 428 | t_AMPERSAND = r'&' 429 | t_DOTDOT = r'\.\.' 430 | 431 | states = ( 432 | ('STRING', 'exclusive', ), 433 | ('BYTESTRING', 'exclusive', ), 434 | ('REXSTRING', 'exclusive', ), 435 | ) 436 | 437 | # Complex token handling 438 | def t_RBRACE(self, t): 439 | r'}' 440 | t.value = t.value 441 | self._condition_end = t.lexpos 442 | 443 | return t 444 | 445 | @staticmethod 446 | def t_NEWLINE(t): 447 | r'(\n|\r\n)+' 448 | t.lexer.lineno += len(t.value) 449 | t.value = t.value 450 | 451 | @staticmethod 452 | def t_COMMENT(t): 453 | r'(//[^\n]*)' 454 | return t 455 | 456 | @staticmethod 457 | def t_MCOMMENT(t): 458 | r'/\*(.|\n|\r\n)*?\*/' 459 | if '\r\n' in t.value: 460 | t.lexer.lineno += t.value.count('\r\n') 461 | else: 462 | t.lexer.lineno += t.value.count('\n') 463 | 464 | return t 465 | 466 | @staticmethod 467 | def t_HEXNUM(t): 468 | r'0x[A-Fa-f0-9]+' 469 | t.value = t.value 470 | 471 | return t 472 | 473 | def t_SECTIONMETA(self, t): 474 | r'meta\s*:' 475 | t.value = t.value 476 | self._meta_start = t.lexpos 477 | t.lexer.section = 'meta' 478 | 479 | return t 480 | 481 | def t_SECTIONSTRINGS(self, t): 482 | r'strings\s*:' 483 | t.value = t.value 484 | self._strings_start = t.lexpos 485 | if self._meta_end is None: 486 | self._meta_end = t.lexpos 487 | t.lexer.section = 'strings' 488 | 489 | return t 490 | 491 | def t_SECTIONCONDITION(self, t): 492 | r'condition\s*:' 493 | t.value = t.value 494 | self._condition_start = t.lexpos 495 | if self._meta_end is None: 496 | self._meta_end = t.lexpos 497 | if self._strings_end is None: 498 | self._strings_end = t.lexpos 499 | t.lexer.section = 'condition' 500 | 501 | return t 502 | 503 | # Text string handling 504 | @staticmethod 505 | def t_begin_STRING(t): 506 | r'"' 507 | t.lexer.escape = 0 508 | t.lexer.string_start = t.lexer.lexpos - 1 509 | t.lexer.begin('STRING') 510 | t.lexer.hex_escape = 0 511 | 512 | # @staticmethod 513 | def t_STRING_value(self, t): 514 | r'.' 515 | if t.lexer.escape == 0 and t.value == '"': 516 | t.type = 'STRING' 517 | t.value = t.lexer.lexdata[t.lexer.string_start:t.lexer.lexpos] 518 | t.lexer.begin('INITIAL') 519 | 520 | return t 521 | 522 | else: 523 | self._process_string_with_escapes(t, escape_chars=self.STRING_ESCAPE_CHARS) 524 | 525 | t_STRING_ignore = '' 526 | 527 | @staticmethod 528 | def t_STRING_error(t): 529 | """Raise parsing error for illegal string character. 530 | 531 | Args: 532 | t: Token input from lexer. 533 | 534 | Raises: 535 | ParseTypeError 536 | """ 537 | raise ParseTypeError('Illegal string character: {!r}, at line: {}'.format(t.value[0], t.lexer.lineno), 538 | t.lexer.lineno, t.lexer.lexpos) 539 | 540 | # Byte string handling 541 | @staticmethod 542 | def t_begin_BYTESTRING(t): 543 | r'\{' 544 | if hasattr(t.lexer, 'section') and t.lexer.section == 'strings': 545 | t.lexer.bytestring_start = t.lexer.lexpos - 1 546 | t.lexer.begin('BYTESTRING') 547 | t.lexer.bytestring_group = 0 548 | else: 549 | t.type = 'LBRACE' 550 | 551 | return t 552 | 553 | @staticmethod 554 | def t_BYTESTRING_pair(t): 555 | r'\s*[a-fA-F0-9?]{2}\s*' 556 | 557 | @staticmethod 558 | def t_BYTESTRING_comment(t): 559 | r'\/\/[^\r\n]*' 560 | 561 | @staticmethod 562 | def t_BYTESTRING_mcomment(t): 563 | r'/\*(.|\n|\r\n)*?\*/' 564 | 565 | @staticmethod 566 | def t_BYTESTRING_jump(t): 567 | r'\[\s*(\d*)\s*-?\s*(\d*)\s*\]' 568 | groups = t.lexer.lexmatch.groups() 569 | index = groups.index(t.value) 570 | 571 | lower_bound = groups[index + 1] 572 | upper_bound = groups[index + 2] 573 | 574 | if lower_bound and upper_bound: 575 | if not 0 <= int(lower_bound) <= int(upper_bound): 576 | raise ParseValueError('Illegal bytestring jump bounds: {}, at line: {}'.format(t.value, t.lexer.lineno), 577 | t.lexer.lineno, t.lexer.lexpos) 578 | 579 | @staticmethod 580 | def t_BYTESTRING_group_start(t): 581 | r'\(' 582 | t.lexer.bytestring_group += 1 583 | 584 | @staticmethod 585 | def t_BYTESTRING_group_end(t): 586 | r'\)' 587 | t.lexer.bytestring_group -= 1 588 | 589 | @staticmethod 590 | def t_BYTESTRING_group_logical_or(t): 591 | r'\|' 592 | 593 | @staticmethod 594 | def t_BYTESTRING_end(t): 595 | r'\}' 596 | t.type = 'BYTESTRING' 597 | t.value = t.lexer.lexdata[t.lexer.bytestring_start:t.lexer.lexpos] 598 | 599 | if t.lexer.bytestring_group != 0: 600 | raise ParseValueError('Unbalanced group in bytestring: {}, at line: {}'.format(t.value, t.lexer.lineno), 601 | t.lexer.lineno, t.lexer.lexpos) 602 | 603 | t.lexer.begin('INITIAL') 604 | 605 | # Account for newlines in bytestring. 606 | if '\r\n' in t.value: 607 | t.lexer.lineno += t.value.count('\r\n') 608 | else: 609 | t.lexer.lineno += t.value.count('\n') 610 | 611 | return t 612 | 613 | t_BYTESTRING_ignore = ' \r\n\t' 614 | 615 | @staticmethod 616 | def t_BYTESTRING_error(t): 617 | """Raise parsing error for illegal bytestring character. 618 | 619 | Args: 620 | t: Token input from lexer. 621 | 622 | Raises: 623 | ParseTypeError 624 | """ 625 | raise ParseTypeError('Illegal bytestring character : {}, at line: {}'.format(t.value[0], t.lexer.lineno), 626 | t.lexer.lineno, t.lexer.lexpos) 627 | 628 | # Rexstring Handling 629 | @staticmethod 630 | def t_begin_REXSTRING(t): 631 | r'/' 632 | if hasattr(t.lexer, 'section') and t.lexer.section in ('strings', 'condition'): 633 | t.lexer.rexstring_start = t.lexer.lexpos - 1 634 | t.lexer.begin('REXSTRING') 635 | t.lexer.escape = 0 636 | t.lexer.hex_escape = 0 637 | else: 638 | t.type = 'FORWARDSLASH' 639 | 640 | return t 641 | 642 | @staticmethod 643 | def t_REXSTRING_end(t): 644 | r'/(?:i?s?)' 645 | if t.lexer.escape == 0: 646 | t.type = 'REXSTRING' 647 | t.value = t.lexer.lexdata[t.lexer.rexstring_start:t.lexer.lexpos] 648 | t.lexer.begin('INITIAL') 649 | 650 | return t 651 | else: 652 | t.lexer.escape ^= 1 653 | 654 | def t_REXSTRING_value(self, t): 655 | r'.' 656 | self._process_string_with_escapes(t) 657 | 658 | t_REXSTRING_ignore = '' 659 | 660 | @staticmethod 661 | def t_REXSTRING_error(t): 662 | """Raise parsing error for illegal rexstring character. 663 | 664 | Args: 665 | t: Token input from lexer. 666 | 667 | Raises: 668 | ParseTypeError 669 | """ 670 | raise ParseTypeError('Illegal rexstring character : {!r}, at line: {}'.format(t.value[0], t.lexer.lineno), 671 | t.lexer.lineno, t.lexer.lexpos) 672 | 673 | @staticmethod 674 | def t_STRINGNAME(t): 675 | r'\$[0-9a-zA-Z\-_]*[*]?' 676 | t.value = t.value 677 | 678 | return t 679 | 680 | @staticmethod 681 | def t_STRINGNAME_ARRAY(t): 682 | r'@[0-9a-zA-Z\-_]*[*]?' 683 | t.value = t.value 684 | 685 | return t 686 | 687 | @staticmethod 688 | def t_STRINGNAME_LENGTH(t): 689 | r'![0-9a-zA-Z\-_]*[*]?(?!=)' 690 | t.value = t.value 691 | 692 | return t 693 | 694 | @staticmethod 695 | def t_FILESIZE_SIZE(t): 696 | r"\d+[KM]B" 697 | t.value = t.value 698 | 699 | return t 700 | 701 | @staticmethod 702 | def t_NUM(t): 703 | r'\d+(\.\d+)?|0x\d+' 704 | t.value = t.value 705 | 706 | return t 707 | 708 | def t_ID(self, t): 709 | r'[a-zA-Z_][a-zA-Z_0-9.]*' 710 | t.type = self.reserved.get(t.value, 'ID') # Check for reserved words 711 | 712 | return t 713 | 714 | @staticmethod 715 | def t_STRINGNAME_COUNT(t): 716 | r'\#([a-zA-Z][0-9a-zA-Z\-_]*[*]?)?' 717 | t.value = t.value 718 | 719 | return t 720 | 721 | # A string containing ignored characters (spaces and tabs) 722 | t_ignore = ' \t' 723 | 724 | # Error handling rule 725 | @staticmethod 726 | def t_error(t): 727 | """Raise parsing error. 728 | 729 | Args: 730 | t: Token input from lexer. 731 | 732 | Raises: 733 | ParseTypeError 734 | """ 735 | raise ParseTypeError('Illegal character {!r} at line {}'.format(t.value[0], t.lexer.lineno), 736 | t.lexer.lineno, t.lexer.lexpos) 737 | 738 | # Parsing rules 739 | precedence = ( 740 | ('right', 'NUM', ), 741 | ('right', 'ID', ), 742 | ('right', 'HEXNUM', ) 743 | ) 744 | 745 | @staticmethod 746 | def p_ruleset(p): 747 | '''ruleset : rules 748 | | imports 749 | | includes 750 | | ruleset ruleset''' 751 | 752 | @staticmethod 753 | def p_rules(p): 754 | '''rules : rules rule 755 | | rule''' 756 | 757 | def p_rule(self, p): 758 | '''rule : scopes RULE ID tag_section LBRACE rule_body RBRACE''' 759 | logger.debug('Matched rule: {}'.format(p[3])) 760 | if '.' in p[3]: 761 | message = 'Invalid rule name {}, on line {}'.format(p[3], p.lineno(1)) 762 | raise ParseTypeError(message, p.lineno, p.lexpos) 763 | logger.debug('Rule start: {}, Rule stop: {}'.format(p.lineno(2), p.lineno(7))) 764 | 765 | while self._rule_comments: 766 | comment = self._rule_comments.pop() 767 | 768 | if p.lexpos(5) < comment.lexpos < p.lexpos(7): 769 | self._add_element(getattr(ElementTypes, comment.type), comment.value) 770 | 771 | element_value = (p[3], int(p.lineno(2)), int(p.lineno(7)), ) 772 | self._add_element(ElementTypes.RULE_NAME, element_value) 773 | 774 | @staticmethod 775 | def p_imports(p): 776 | '''imports : imports import 777 | | import''' 778 | 779 | @staticmethod 780 | def p_includes(p): 781 | '''includes : includes include 782 | | include''' 783 | 784 | @staticmethod 785 | def p_scopes(p): 786 | '''scopes : scopes scope 787 | | scope 788 | | ''' 789 | 790 | def p_import(self, p): 791 | '''import : IMPORT STRING''' 792 | import_value = p[2].replace('"', '') 793 | logger.debug('Matched import: {}'.format(import_value)) 794 | self._add_element(ElementTypes.IMPORT, import_value) 795 | 796 | def p_include(self, p): 797 | '''include : INCLUDE STRING''' 798 | include_value = p[2].replace('"', '') 799 | logger.debug('Matched include: {}'.format(include_value)) 800 | self._add_element(ElementTypes.INCLUDE, include_value) 801 | 802 | def p_scope(self, p): 803 | '''scope : PRIVATE 804 | | GLOBAL''' 805 | logger.debug('Matched scope identifier: {}'.format(p[1])) 806 | self._add_element(ElementTypes.SCOPE, p[1]) 807 | 808 | @staticmethod 809 | def p_tag_section(p): 810 | '''tag_section : COLON tags 811 | | ''' 812 | 813 | @staticmethod 814 | def p_tags(p): 815 | '''tags : tags tag 816 | | tag''' 817 | 818 | def p_tag(self, p): 819 | '''tag : ID''' 820 | logger.debug('Matched tag: {}'.format(p[1])) 821 | self._add_element(ElementTypes.TAG, p[1]) 822 | 823 | @staticmethod 824 | def p_rule_body(p): 825 | '''rule_body : sections''' 826 | logger.debug('Matched rule body') 827 | 828 | @staticmethod 829 | def p_rule_sections(p): 830 | '''sections : sections section 831 | | section''' 832 | 833 | @staticmethod 834 | def p_rule_section(p): 835 | '''section : meta_section 836 | | strings_section 837 | | condition_section''' 838 | 839 | @staticmethod 840 | def p_meta_section(p): 841 | '''meta_section : SECTIONMETA meta_kvs''' 842 | logger.debug('Matched meta section') 843 | 844 | @staticmethod 845 | def p_strings_section(p): 846 | '''strings_section : SECTIONSTRINGS strings_kvs''' 847 | 848 | @staticmethod 849 | def p_condition_section(p): 850 | '''condition_section : SECTIONCONDITION expression''' 851 | 852 | # Meta elements 853 | @staticmethod 854 | def p_meta_kvs(p): 855 | '''meta_kvs : meta_kvs meta_kv 856 | | meta_kv''' 857 | logger.debug('Matched meta kvs') 858 | 859 | def p_meta_kv(self, p): 860 | '''meta_kv : ID EQUALS STRING 861 | | ID EQUALS ID 862 | | ID EQUALS TRUE 863 | | ID EQUALS FALSE 864 | | ID EQUALS NUM''' 865 | key = p[1] 866 | value = p[3] 867 | if re.match(r'".*"', value): 868 | match = re.match('"(.*)"', value) 869 | if match: 870 | value = match.group(1) 871 | elif value in ('true', 'false'): 872 | value = True if value == 'true' else False 873 | else: 874 | value = int(value) 875 | logger.debug('Matched meta kv: {} equals {}'.format(key, value)) 876 | self._add_element(ElementTypes.METADATA_KEY_VALUE, (key, value, )) 877 | 878 | # Strings elements 879 | @staticmethod 880 | def p_strings_kvs(p): 881 | '''strings_kvs : strings_kvs strings_kv 882 | | strings_kv''' 883 | logger.debug('Matched strings kvs') 884 | 885 | def _parse_string_kv(self, p, string_type): 886 | """Perform parsing for all string types. 887 | 888 | Args: 889 | p: Parser object. 890 | string_type: StringTypes enum. 891 | """ 892 | key = p[1] 893 | value = p[3] 894 | match = re.match('"(.+)"', value) 895 | if match: 896 | value = match.group(1) 897 | if key != '$' and key in self._stringnames: 898 | message = 'Duplicate string name key {} on line {}'.format(key, p.lineno(1)) 899 | raise ParseTypeError(message, p.lineno, p.lexpos) 900 | self._stringnames.add(key) 901 | logger.debug('Matched strings kv: {} equals {}'.format(key, value)) 902 | self._add_element(ElementTypes.STRINGS_KEY_VALUE, (key, value, string_type, )) 903 | 904 | def p_byte_strings_kv(self, p): 905 | '''strings_kv : STRINGNAME EQUALS BYTESTRING 906 | | STRINGNAME EQUALS BYTESTRING comments 907 | | STRINGNAME EQUALS BYTESTRING byte_string_modifiers 908 | | STRINGNAME EQUALS BYTESTRING byte_string_modifiers comments''' 909 | self._parse_string_kv(p, StringTypes.BYTE) 910 | 911 | def p_text_strings_kv(self, p): 912 | '''strings_kv : STRINGNAME EQUALS STRING 913 | | STRINGNAME EQUALS STRING comments 914 | | STRINGNAME EQUALS STRING text_string_modifiers 915 | | STRINGNAME EQUALS STRING text_string_modifiers comments''' 916 | self._parse_string_kv(p, StringTypes.TEXT) 917 | 918 | def p_regex_strings_kv(self, p): 919 | '''strings_kv : STRINGNAME EQUALS REXSTRING 920 | | STRINGNAME EQUALS REXSTRING comments 921 | | STRINGNAME EQUALS REXSTRING regex_string_modifiers 922 | | STRINGNAME EQUALS REXSTRING regex_string_modifiers comments''' 923 | self._parse_string_kv(p, StringTypes.REGEX) 924 | 925 | @staticmethod 926 | def p_text_string_modifiers(p): 927 | '''text_string_modifiers : text_string_modifiers text_string_modifier 928 | | text_string_modifier''' 929 | 930 | def p_text_string_modifier(self, p): 931 | '''text_string_modifier : NOCASE 932 | | ASCII 933 | | WIDE 934 | | FULLWORD 935 | | XOR_MOD 936 | | XOR_MOD xor_mod_args 937 | | BASE64 938 | | BASE64WIDE 939 | | BASE64 base64_with_args 940 | | BASE64WIDE base64_with_args 941 | | PRIVATE''' 942 | self._add_string_modifier(p) 943 | 944 | @staticmethod 945 | def p_regex_text_string_modifiers(p): 946 | '''regex_string_modifiers : regex_string_modifiers regex_string_modifer 947 | | regex_string_modifer''' 948 | 949 | def p_regex_string_modifer(self, p): 950 | '''regex_string_modifer : NOCASE 951 | | ASCII 952 | | WIDE 953 | | FULLWORD 954 | | PRIVATE''' 955 | self._add_string_modifier(p) 956 | 957 | @staticmethod 958 | def p_byte_string_modifiers(p): 959 | '''byte_string_modifiers : byte_string_modifiers byte_string_modifer 960 | | byte_string_modifer''' 961 | 962 | def p_byte_string_modifer(self, p): 963 | '''byte_string_modifer : PRIVATE''' 964 | self._add_string_modifier(p) 965 | 966 | def p_xor_mod_args(self, p): 967 | '''xor_mod_args : LPAREN NUM RPAREN 968 | | LPAREN NUM HYPHEN NUM RPAREN 969 | | LPAREN HEXNUM RPAREN 970 | | LPAREN HEXNUM HYPHEN HEXNUM RPAREN 971 | | LPAREN NUM HYPHEN HEXNUM RPAREN 972 | | LPAREN HEXNUM HYPHEN NUM RPAREN''' 973 | logger.debug('Matched an xor arg: {}'.format(''.join(p[1:]))) 974 | mods = [x for x in p if x not in (None, '(', '-', ')')] 975 | mod_int_list = [] 976 | mod_lineidx = set() 977 | for i, x in enumerate(mods): 978 | mod_int = int(x, 16) if x.startswith('0x') else int(x) 979 | if 0 <= mod_int <= 255: 980 | mod_int_list.append(mod_int) 981 | mod_lineidx.add(i) 982 | else: 983 | message = 'String modification value {} not between 0-255 on line {}'.format(x, p.lineno(1 + i)) 984 | raise ParseTypeError(message, p.lineno, p.lexpos) 985 | if mod_int_list[0] > mod_int_list[-1]: 986 | mod_lineno = list({p.lineno(1 + i) for i in mod_lineidx}) 987 | mod_lineno.sort() 988 | line_no = ' and '.join(str(lno) for lno in mod_lineno) 989 | message = 'String modification lower bound exceeds upper bound on line {}'.format(line_no) 990 | raise ParseTypeError(message, p.lineno, p.lexpos) 991 | else: 992 | mod_str_mod = YaraXor(mod_int_list) 993 | logger.debug('Matched string modifier(s): {}'.format(mod_str_mod)) 994 | self._add_element(ElementTypes.STRINGS_MODIFIER, mod_str_mod) 995 | 996 | def p_base64_with_args(self, p): 997 | '''base64_with_args : LPAREN STRING RPAREN''' 998 | # Remove parens and leading/trailing quotes 999 | b64_mod = [x for x in p if x not in (None, '(', ')')][0].strip('"') 1000 | b64_data = b64_mod.encode('ascii').decode('unicode-escape') 1001 | if len(b64_data) != 64: 1002 | raise Exception("Base64 dictionary length {}, must be 64 characters".format(len(b64_data))) 1003 | if re.search(r'(.).*\1', b64_data): 1004 | raise Exception("Duplicate character in Base64 dictionary") 1005 | mod_str_mod = YaraBase64(b64_mod) 1006 | logger.debug('Matched string modifier(s): {}'.format(b64_mod)) 1007 | self._add_element(ElementTypes.STRINGS_MODIFIER, mod_str_mod) 1008 | 1009 | @staticmethod 1010 | def p_comments(p): 1011 | '''comments : COMMENT 1012 | | MCOMMENT''' 1013 | logger.debug('Matched a comment: {}'.format(p[1])) 1014 | 1015 | # Condition elements 1016 | @staticmethod 1017 | def p_expression(p): 1018 | '''expression : expression term 1019 | | term''' 1020 | 1021 | def p_condition(self, p): 1022 | '''term : FILESIZE_SIZE 1023 | | ID 1024 | | STRING 1025 | | NUM 1026 | | HEXNUM 1027 | | LPAREN 1028 | | RPAREN 1029 | | LBRACK 1030 | | RBRACK 1031 | | DOTDOT 1032 | | EQUIVALENT 1033 | | EQUALS 1034 | | NEQUALS 1035 | | PLUS 1036 | | PIPE 1037 | | BACKSLASH 1038 | | FORWARDSLASH 1039 | | COMMA 1040 | | GREATERTHAN 1041 | | LESSTHAN 1042 | | GREATEREQUAL 1043 | | LESSEQUAL 1044 | | RIGHTBITSHIFT 1045 | | LEFTBITSHIFT 1046 | | MODULO 1047 | | TILDE 1048 | | XOR_OP 1049 | | PERIOD 1050 | | COLON 1051 | | STAR 1052 | | HYPHEN 1053 | | AMPERSAND 1054 | | ALL 1055 | | AND 1056 | | ANY 1057 | | AT 1058 | | CONTAINS 1059 | | ENTRYPOINT 1060 | | FALSE 1061 | | FILESIZE 1062 | | FOR 1063 | | IN 1064 | | INT8 1065 | | INT16 1066 | | INT32 1067 | | INT8BE 1068 | | INT16BE 1069 | | INT32BE 1070 | | MATCHES 1071 | | NOT 1072 | | OR 1073 | | OF 1074 | | THEM 1075 | | TRUE 1076 | | UINT8 1077 | | UINT16 1078 | | UINT32 1079 | | UINT8BE 1080 | | UINT16BE 1081 | | UINT32BE 1082 | | STRINGNAME 1083 | | STRINGNAME_ARRAY 1084 | | STRINGNAME_LENGTH 1085 | | STRINGNAME_COUNT 1086 | | REXSTRING''' 1087 | logger.debug('Matched a condition term: {}'.format(p[1])) 1088 | if p[1] == '$': 1089 | message = 'Potential wrong use of anonymous string on line {}'.format(p.lineno(1)) 1090 | logger.info(message) 1091 | 1092 | self._add_element(ElementTypes.TERM, p[1]) 1093 | 1094 | # Error rule for syntax errors 1095 | def p_error(self, p): 1096 | """Raise syntax errors. 1097 | 1098 | Args: 1099 | p: Data from the parser. 1100 | 1101 | Raises: 1102 | ParseTypeError 1103 | """ 1104 | if not p: 1105 | # This happens when we try to parse an empty string or file, or one with no actual rules. 1106 | pass 1107 | elif p.type in ('COMMENT', 'MCOMMENT'): 1108 | self.parser.errok() # This is a method from PLY to reset the error state from parsing a comment 1109 | self._rule_comments.append(p) 1110 | else: 1111 | message = 'Unknown text {} for token of type {} on line {}'.format(p.value, p.type, p.lineno) 1112 | raise ParseTypeError(message, p.lineno, p.lexpos) 1113 | 1114 | @staticmethod 1115 | def _process_string_with_escapes(t, escape_chars=None): 1116 | if escape_chars is None: 1117 | escape_chars = [t.value] 1118 | if t.lexer.escape == 1 and t.value in escape_chars or t.value == '\\': 1119 | t.lexer.escape ^= 1 1120 | if t.value == 'x': 1121 | t.lexer.hex_escape = 2 1122 | elif t.lexer.hex_escape > 0: 1123 | if t.value.lower() in string.hexdigits: 1124 | t.lexer.hex_escape -= 1 1125 | else: 1126 | raise ParseTypeError('Invalid hex character: {!r}, at line: {}'.format(t.value, t.lexer.lineno), 1127 | t.lexer.lineno, t.lexer.lexpos) 1128 | elif t.lexer.escape == 1: 1129 | raise ParseTypeError('Invalid escape sequence: \\{}, at line: {}'.format(t.value, t.lexer.lineno), 1130 | t.lexer.lineno, t.lexer.lexpos) 1131 | 1132 | def _add_string_modifier(self, p): 1133 | mod_str = p[1] 1134 | prev_mod_with_args = False 1135 | if mod_str in self.string_modifiers: 1136 | message = 'Duplicate string modifier {} on line {}'.format(mod_str, p.lineno(1)) 1137 | raise ParseTypeError(message, p.lineno, p.lexpos) 1138 | if mod_str in self.EXCLUSIVE_TEXT_MODIFIERS: 1139 | prev_mods = {x for x in self.string_modifiers if isinstance(x, str)} 1140 | excluded_modifiers = prev_mods & ({mod_str} ^ self.EXCLUSIVE_TEXT_MODIFIERS) 1141 | if excluded_modifiers: 1142 | prev_mod_str = excluded_modifiers.pop() 1143 | message = ('Mutually exclusive string modifier use of {} on line {} after {} usage' 1144 | .format(mod_str, p.lineno(1), prev_mod_str)) 1145 | raise ParseTypeError(message, p.lineno, p.lexpos) 1146 | if self.string_modifiers: 1147 | # Convert previously created modifiers with args to strings 1148 | if mod_str.startswith('base64') and isinstance(self.string_modifiers[-1], YaraBase64): 1149 | if mod_str == 'base64wide': 1150 | self.string_modifiers[-1].modifier_name = 'base64wide' 1151 | logger.debug('Corrected base64 string modifier to base64wide') 1152 | self.string_modifiers[-1] = str(self.string_modifiers[-1]) 1153 | prev_mod_with_args = True 1154 | elif mod_str == 'xor' and isinstance(self.string_modifiers[-1], YaraXor): 1155 | self.string_modifiers[-1] = str(self.string_modifiers[-1]) 1156 | logger.debug('Modified xor string was already added') 1157 | prev_mod_with_args = True 1158 | if not prev_mod_with_args: 1159 | self._add_element(ElementTypes.STRINGS_MODIFIER, mod_str) 1160 | logger.debug('Matched a string modifier: {}'.format(mod_str)) 1161 | 1162 | 1163 | class YaraXor(str): 1164 | """YARA xor string modifier.""" 1165 | 1166 | def __init__(self, xor_range=None): 1167 | """Initialize XOR string modifier.""" 1168 | str.__init__(self) 1169 | self.modifier_name = 'xor' 1170 | self.modifier_list = xor_range if xor_range is not None else [] 1171 | 1172 | def __str__(self): 1173 | """Return the string representation.""" 1174 | if len(self.modifier_list) == 0: 1175 | return self.modifier_name 1176 | return '{}({})'.format( 1177 | self.modifier_name, 1178 | '-'.join(['{0:#0{1}x}'.format(x, 4) for x in self.modifier_list]) 1179 | ) 1180 | 1181 | def __repr__(self): 1182 | """Return the object representation.""" 1183 | if len(self.modifier_list) == 0: 1184 | return '{}()'.format(self.__class__.__name__) 1185 | else: 1186 | return '{}({})'.format(self.__class__.__name__, self.modifier_list) 1187 | 1188 | 1189 | class YaraBase64(str): 1190 | """YARA base64 string modifier for easier printing.""" 1191 | 1192 | def __init__(self, modifier_alphabet=None, modifier_name='base64'): 1193 | """Initialize base64 string modifier.""" 1194 | str.__init__(self) 1195 | self.modifier_name = 'base64' if modifier_name != 'base64wide' else 'base64wide' 1196 | self.modifier_alphabet = modifier_alphabet 1197 | 1198 | def __str__(self): 1199 | """Return the string representation.""" 1200 | if self.modifier_alphabet is None: 1201 | return '{}'.format(self.modifier_name) 1202 | else: 1203 | return '{}("{}")'.format(self.modifier_name, self.modifier_alphabet) 1204 | 1205 | def __repr__(self): 1206 | """Return the object representation.""" 1207 | if self.modifier_alphabet is None: 1208 | return '{}()'.format(self.__class__.__name__) 1209 | else: 1210 | return '{}({})'.format(self.__class__.__name__, repr(self.modifier_alphabet)) 1211 | --------------------------------------------------------------------------------