├── .github └── workflows │ └── publish-to-pypi.yml ├── .gitmodules ├── LICENSE ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── appveyor ├── install.ps1 ├── run_with_env.cmd ├── setup_x64.bat └── x64 │ ├── 600dd186-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 600dd187-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 600dd188-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 600dd189-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 656d875f-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 656d8760-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 656d8763-2429-11d7-8bf6-00b0d03daa06.reg │ ├── 656d8766-2429-11d7-8bf6-00b0d03daa06.reg │ └── VC_OBJECTS_PLATFORM_INFO.reg ├── setup.cfg ├── setup.py ├── tests.py └── yara-python.c /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python distribution to PyPI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | name: Build wheels on ${{ matrix.os }} (${{ matrix.arch }}) 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | include: 21 | - os: ubuntu-24.04 22 | arch: x86_64 23 | build-sdist: true 24 | - os: ubuntu-24.04 25 | arch: i686 26 | - os: ubuntu-24.04 27 | arch: aarch64 28 | - os: macos-15 29 | arch: x86_64 arm64 30 | env: MACOSX_DEPLOYMENT_TARGET=15.0 31 | - os: macos-14 32 | arch: x86_64 arm64 33 | env: MACOSX_DEPLOYMENT_TARGET=14.0 34 | - os: windows-2022 35 | arch: x86 36 | before: vcpkg install openssl:x86-windows-static 37 | env: LIB="C:\\vcpkg\\packages\\openssl_x86-windows-static\\lib" INCLUDE="C:\\vcpkg\\packages\\openssl_x86-windows-static\\include" 38 | - os: windows-2022 39 | arch: AMD64 40 | before: vcpkg install openssl:x64-windows-static 41 | env: LIB="C:\\vcpkg\\packages\\openssl_x64-windows-static\\lib" INCLUDE="C:\\vcpkg\\packages\\openssl_x64-windows-static\\include" 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | with: 46 | submodules: true 47 | 48 | - name: Set up QEMU 49 | if: runner.os == 'Linux' 50 | uses: docker/setup-qemu-action@v3 51 | with: 52 | platforms: all 53 | 54 | - name: Build wheels 55 | uses: pypa/cibuildwheel@v2.23.3 56 | with: 57 | output-dir: dist 58 | env: 59 | CIBW_ARCHS: ${{ matrix.arch }} 60 | CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*" 61 | CIBW_BEFORE_ALL_LINUX: | 62 | if [[ ! -z "$(which yum)" ]]; then 63 | yum install -y make gcc perl-core pcre-devel wget zlib-devel git automake 64 | wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1w/openssl-1.1.1w.tar.gz 65 | tar xf openssl*.gz 66 | cd openssl* 67 | ./config --prefix=/usr --openssldir=/etc/ssl zlib-dynamic 68 | make -j$(nproc) 69 | make install 70 | elif [[ ! -z "$(which apk)" ]]; then 71 | apk add openssl-dev 72 | fi 73 | CIBW_BEFORE_ALL_WINDOWS: ${{ matrix.before }} 74 | CIBW_BUILD_FRONTEND: "build; args: --config-setting=--enable-openssl" 75 | CIBW_ENVIRONMENT: ${{ matrix.env }} 76 | CIBW_TEST_SKIP: "*-macosx_arm64 *-macosx_x86_64" 77 | CIBW_TEST_COMMAND: python {package}/tests.py 78 | 79 | - name: Store the distribution packages 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: python-package-distributions-${{ matrix.os }}-${{ matrix.arch }} 83 | path: dist/*.whl 84 | 85 | - name: Build Sdist 86 | if: ${{ matrix.build-sdist }} 87 | run: pipx run build --sdist 88 | 89 | - name: Store the source distribution package 90 | if: ${{ matrix.build-sdist }} 91 | uses: actions/upload-artifact@v4 92 | with: 93 | name: python-package-distributions-source 94 | path: dist/*.tar.gz 95 | 96 | publish-to-pypi: 97 | needs: [build] 98 | runs-on: ubuntu-latest 99 | 100 | # Only publish to PyPI on tag pushes 101 | if: startsWith(github.ref, 'refs/tags/') 102 | 103 | environment: 104 | name: pypi 105 | url: https://pypi.org/p/yara-python 106 | permissions: 107 | id-token: write # IMPORTANT: mandatory for trusted publishing 108 | 109 | steps: 110 | - name: Download all the dists 111 | uses: actions/download-artifact@v4 112 | with: 113 | pattern: python-package-distributions-* 114 | merge-multiple: true 115 | path: dist/ 116 | 117 | - name: Publish distribution to PyPI 118 | uses: pypa/gh-action-pypi-publish@release/v1 119 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "yara"] 2 | path = yara 3 | url=https://github.com/VirusTotal/yara.git 4 | -------------------------------------------------------------------------------- /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 {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.c 2 | include yara/libyara/modules/module_list 3 | recursive-include yara *.c *.h 4 | include LICENSE 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://ci.appveyor.com/api/projects/status/gidnb9ulj3rje5s2?svg=true 2 | :target: https://ci.appveyor.com/project/plusvic/yara-python 3 | 4 | yara-python 5 | =========== 6 | 7 | With this library you can use `YARA `_ from 8 | your Python programs. It covers all YARA's features, from compiling, saving 9 | and loading rules to scanning files, strings and processes. 10 | 11 | Here it goes a little example: 12 | 13 | .. code-block:: python 14 | 15 | >>> import yara 16 | >>> rule = yara.compile(source='rule foo: bar {strings: $a = "lmn" condition: $a}') 17 | >>> matches = rule.match(data='abcdefgjiklmnoprstuvwxyz') 18 | >>> print(matches) 19 | [foo] 20 | >>> print(matches[0].rule) 21 | foo 22 | >>> print(matches[0].tags) 23 | ['bar'] 24 | >>> print(matches[0].strings) 25 | [$a] 26 | >>> print(matches[0].strings[0].identifier) 27 | $a 28 | >>> print(matches[0].strings[0].instances) 29 | [lmn] 30 | >>> print(matches[0].strings[0].instances[0].offset) 31 | 10 32 | >>> print(matches[0].strings[0].instances[0].matched_length) 33 | 3 34 | 35 | 36 | Installation 37 | ------------ 38 | 39 | The easiest way to install YARA is by using ``pip``: 40 | 41 | .. code-block:: bash 42 | 43 | $ pip install yara-python 44 | 45 | But you can also get the source from GitHub and compile it yourself: 46 | 47 | .. code-block:: bash 48 | 49 | $ git clone --recursive https://github.com/VirusTotal/yara-python 50 | $ cd yara-python 51 | $ python setup.py build 52 | $ sudo python setup.py install 53 | 54 | Notice the ``--recursive`` option used with ``git``. This is important because 55 | we need to download the ``yara`` subproject containing the source code for 56 | ``libyara`` (the core YARA library). It's also important to note that the two 57 | methods above link ``libyara`` statically into yara-python. If you want to link 58 | dynamically against a shared ``libyara`` library, use: 59 | 60 | .. code-block:: bash 61 | 62 | $ python setup.py build --dynamic-linking 63 | 64 | For this option to work you must build and install 65 | `YARA `_ separately before installing 66 | ``yara-python``. 67 | 68 | 69 | Documentation 70 | ------------- 71 | 72 | Find more information about how to use yara-python at 73 | https://yara.readthedocs.org/en/latest/yarapython.html. 74 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the 4 | # /E:ON and /V:ON options are not enabled in the batch script intepreter 5 | # See: http://stackoverflow.com/a/13751649/163740 6 | CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd" 7 | JANSSON_VERSION: "2.13" 8 | 9 | matrix: 10 | - PYTHON: "C:\\Python38" 11 | PYTHON_VERSION: "3.8.x" 12 | PYTHON_ARCH: "32" 13 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/fakubeldw67e9pmg/artifacts/YARA.OpenSSL.x86.1.1.1.nupkg" 14 | VS: "Visual Studio 14 2015" 15 | 16 | - PYTHON: "C:\\Python38-x64" 17 | PYTHON_VERSION: "3.8.x" 18 | PYTHON_ARCH: "64" 19 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/q63539qt9yqaqspo/artifacts/YARA.OpenSSL.x64.1.1.1.nupkg" 20 | VS: "Visual Studio 14 2015 Win64" 21 | 22 | - PYTHON: "C:\\Python39" 23 | PYTHON_VERSION: "3.9.0" 24 | PYTHON_ARCH: "32" 25 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/fakubeldw67e9pmg/artifacts/YARA.OpenSSL.x86.1.1.1.nupkg" 26 | VS: "Visual Studio 14 2015" 27 | 28 | - PYTHON: "C:\\Python39-x64" 29 | PYTHON_VERSION: "3.9.0" 30 | PYTHON_ARCH: "64" 31 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/q63539qt9yqaqspo/artifacts/YARA.OpenSSL.x64.1.1.1.nupkg" 32 | VS: "Visual Studio 14 2015 Win64" 33 | 34 | - PYTHON: "C:\\Python310" 35 | PYTHON_VERSION: "3.10.0" 36 | PYTHON_ARCH: "32" 37 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/fakubeldw67e9pmg/artifacts/YARA.OpenSSL.x86.1.1.1.nupkg" 38 | VS: "Visual Studio 14 2015" 39 | 40 | - PYTHON: "C:\\Python310-x64" 41 | PYTHON_VERSION: "3.10.0" 42 | PYTHON_ARCH: "64" 43 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/q63539qt9yqaqspo/artifacts/YARA.OpenSSL.x64.1.1.1.nupkg" 44 | VS: "Visual Studio 14 2015 Win64" 45 | 46 | - PYTHON: "C:\\Python311" 47 | PYTHON_VERSION: "3.11.2" 48 | PYTHON_ARCH: "32" 49 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/fakubeldw67e9pmg/artifacts/YARA.OpenSSL.x86.1.1.1.nupkg" 50 | VS: "Visual Studio 14 2015" 51 | 52 | - PYTHON: "C:\\Python311-x64" 53 | PYTHON_VERSION: "3.11.2" 54 | PYTHON_ARCH: "64" 55 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/q63539qt9yqaqspo/artifacts/YARA.OpenSSL.x64.1.1.1.nupkg" 56 | VS: "Visual Studio 14 2015 Win64" 57 | 58 | - PYTHON: "C:\\Python312" 59 | PYTHON_VERSION: "3.12.0" 60 | PYTHON_ARCH: "32" 61 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/fakubeldw67e9pmg/artifacts/YARA.OpenSSL.x86.1.1.1.nupkg" 62 | VS: "Visual Studio 14 2015" 63 | 64 | - PYTHON: "C:\\Python312-x64" 65 | PYTHON_VERSION: "3.12.0" 66 | PYTHON_ARCH: "64" 67 | OPENSSL_LIB: "https://ci.appveyor.com/api/buildjobs/q63539qt9yqaqspo/artifacts/YARA.OpenSSL.x64.1.1.1.nupkg" 68 | VS: "Visual Studio 14 2015 Win64" 69 | 70 | install: 71 | # If there is a newer build queued for the same PR, cancel this one. 72 | # The AppVeyor 'rollout builds' option is supposed to serve the same 73 | # purpose but it is problematic because it tends to cancel builds pushed 74 | # directly to master instead of just PR builds (or the converse). 75 | # credits: JuliaLang developers. 76 | - ps: 77 | if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` 78 | https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` 79 | Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` 80 | throw "There are newer queued builds for this pull request, failing early." } 81 | 82 | - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" 83 | 84 | # Install Python (from the official .msi of http://python.org) and pip when 85 | # not already installed. 86 | - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 } 87 | 88 | - "echo %APPVEYOR_BUILD_WORKER_IMAGE%" 89 | 90 | # Prepend newly installed Python to the PATH of this build (this cannot be 91 | # done from inside the powershell script as it would require to restart 92 | # the parent CMD process). 93 | - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 94 | 95 | # Check that we have the expected version and architecture for Python 96 | - "python --version" 97 | - 'python -c "import struct; print(struct.calcsize(''P'') * 8)"' 98 | 99 | # Upgrade to the latest version of pip to avoid it displaying warnings 100 | # about it being out of date. 101 | - "python -m pip install --disable-pip-version-check --user --upgrade pip" 102 | 103 | # Install the build dependencies of the project. If some dependencies contain 104 | # compiled extensions and are not provided as pre-built wheel packages, 105 | # pip will build them from source using the MSVC compiler matching the 106 | # target Python version and architecture. 107 | 108 | # Install wheel. 109 | - "%CMD_IN_ENV% pip install wheel" 110 | 111 | # Install setuptools. Setup tools doesn't comes by default in Python 3.12. 112 | - "%CMD_IN_ENV% pip install setuptools" 113 | 114 | # We are in projects/yara-python, lets go out to projects. 115 | - cd .. 116 | 117 | # Download precompiled OpenSSL library. 118 | - ps: Invoke-WebRequest "$env:OPENSSL_LIB" -OutFile "openssl.zip" 119 | - ps: Expand-Archive openssl.zip -DestinationPath openssl 120 | 121 | # Download and build jansson library. 122 | - ps: Invoke-WebRequest "https://github.com/akheron/jansson/archive/v$env:JANSSON_VERSION.zip" -OutFile "jansson.zip" 123 | - ps: Expand-Archive jansson.zip -DestinationPath . 124 | - cd jansson-%JANSSON_VERSION% 125 | - md build 126 | - cd build 127 | - cmake -DJANSSON_BUILD_DOCS=OFF -DJANSSON_WITHOUT_TESTS=ON -G "%VS%" .. 128 | - cmake --build . --config Release 129 | 130 | # We are in projects/jansson-%JANSSON_VERSION%/build, lets move to 131 | # projects/yara-python 132 | - cd ../../yara-python 133 | 134 | clone_script: 135 | - cmd: git clone -q --recursive --branch=%APPVEYOR_REPO_BRANCH% https://github.com/%APPVEYOR_REPO_NAME%.git %APPVEYOR_BUILD_FOLDER% 136 | - cmd: git fetch 137 | - cmd: git checkout -qf %APPVEYOR_REPO_BRANCH% 138 | 139 | build_script: 140 | # Build the compiled extension 141 | - "%CMD_IN_ENV% python setup.py build_ext --enable-cuckoo --enable-openssl 142 | -L../jansson-%JANSSON_VERSION%/build/lib/Release;../openssl/lib 143 | -I../jansson-%JANSSON_VERSION%/build/include;../openssl/include 144 | -llibcrypto" 145 | 146 | after_build: 147 | - "%CMD_IN_ENV% python setup.py install" 148 | 149 | test_script: 150 | - "%CMD_IN_ENV% python tests.py" 151 | 152 | after_test: 153 | # If tests are successful, create binary packages for the project. 154 | - "%CMD_IN_ENV% python setup.py bdist_wheel" 155 | 156 | artifacts: 157 | # Archive the generated packages in the ci.appveyor.com build report. 158 | - path: dist\* 159 | 160 | deploy: 161 | tag: $(APPVEYOR_REPO_TAG_NAME) 162 | provider: GitHub 163 | auth_token: 164 | secure: d3qqX7bmrBiKJI38yFPc5vHrGGfS3LxLC7FaG6ewI2ghPPE22Pk6QtyrEFFb73PL 165 | artifact: /.*\.exe/ 166 | draft: true 167 | on: 168 | APPVEYOR_REPO_TAG: true # deploy on tag push only 169 | 170 | #on_success: 171 | # - TODO: upload the content of dist/*.whl to a public wheelhouse 172 | # 173 | 174 | # Uncomment these lines for enabling Remote Desktop for debugging purposes. 175 | #on_finish: 176 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 177 | -------------------------------------------------------------------------------- /appveyor/install.ps1: -------------------------------------------------------------------------------- 1 | # Sample script to install Python and pip under Windows 2 | # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer 3 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | $BASE_URL = "https://www.python.org/ftp/python/" 6 | $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" 7 | $GET_PIP_PATH = "C:\get-pip.py" 8 | 9 | $PYTHON_PRERELEASE_REGEX = @" 10 | (?x) 11 | (?\d+) 12 | \. 13 | (?\d+) 14 | \. 15 | (?\d+) 16 | (?[a-z]{1,2}\d+) 17 | "@ 18 | 19 | 20 | function Download ($filename, $url) { 21 | $webclient = New-Object System.Net.WebClient 22 | 23 | $basedir = $pwd.Path + "\" 24 | $filepath = $basedir + $filename 25 | if (Test-Path $filename) { 26 | Write-Host "Reusing" $filepath 27 | return $filepath 28 | } 29 | 30 | # Download and retry up to 3 times in case of network transient errors. 31 | Write-Host "Downloading" $filename "from" $url 32 | $retry_attempts = 2 33 | for ($i = 0; $i -lt $retry_attempts; $i++) { 34 | try { 35 | $webclient.DownloadFile($url, $filepath) 36 | break 37 | } 38 | Catch [Exception]{ 39 | Start-Sleep 1 40 | } 41 | } 42 | if (Test-Path $filepath) { 43 | Write-Host "File saved at" $filepath 44 | } else { 45 | # Retry once to get the error message if any at the last try 46 | $webclient.DownloadFile($url, $filepath) 47 | } 48 | return $filepath 49 | } 50 | 51 | 52 | function ParsePythonVersion ($python_version) { 53 | if ($python_version -match $PYTHON_PRERELEASE_REGEX) { 54 | return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, 55 | $matches.prerelease) 56 | } 57 | $version_obj = [version]$python_version 58 | return ($version_obj.major, $version_obj.minor, $version_obj.build, "") 59 | } 60 | 61 | 62 | function DownloadPython ($python_version, $platform_suffix) { 63 | $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version 64 | 65 | if (($major -le 2 -and $micro -eq 0) ` 66 | -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` 67 | ) { 68 | $dir = "$major.$minor" 69 | $python_version = "$major.$minor$prerelease" 70 | } else { 71 | $dir = "$major.$minor.$micro" 72 | } 73 | 74 | if ($prerelease) { 75 | if (($major -le 2) ` 76 | -or ($major -eq 3 -and $minor -eq 1) ` 77 | -or ($major -eq 3 -and $minor -eq 2) ` 78 | -or ($major -eq 3 -and $minor -eq 3) ` 79 | ) { 80 | $dir = "$dir/prev" 81 | } 82 | } 83 | 84 | if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { 85 | $ext = "msi" 86 | if ($platform_suffix) { 87 | $platform_suffix = ".$platform_suffix" 88 | } 89 | } else { 90 | $ext = "exe" 91 | if ($platform_suffix) { 92 | $platform_suffix = "-$platform_suffix" 93 | } 94 | } 95 | 96 | $filename = "python-$python_version$platform_suffix.$ext" 97 | $url = "$BASE_URL$dir/$filename" 98 | $filepath = Download $filename $url 99 | return $filepath 100 | } 101 | 102 | 103 | function InstallPython ($python_version, $architecture, $python_home) { 104 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home 105 | if (Test-Path $python_home) { 106 | Write-Host $python_home "already exists, skipping." 107 | return $false 108 | } 109 | if ($architecture -eq "32") { 110 | $platform_suffix = "" 111 | } else { 112 | $platform_suffix = "amd64" 113 | } 114 | $installer_path = DownloadPython $python_version $platform_suffix 115 | $installer_ext = [System.IO.Path]::GetExtension($installer_path) 116 | Write-Host "Installing $installer_path to $python_home" 117 | $install_log = $python_home + ".log" 118 | if ($installer_ext -eq '.msi') { 119 | InstallPythonMSI $installer_path $python_home $install_log 120 | } else { 121 | InstallPythonEXE $installer_path $python_home $install_log 122 | } 123 | if (Test-Path $python_home) { 124 | Write-Host "Python $python_version ($architecture) installation complete" 125 | } else { 126 | Write-Host "Failed to install Python in $python_home" 127 | Get-Content -Path $install_log 128 | Exit 1 129 | } 130 | } 131 | 132 | 133 | function InstallPythonEXE ($exepath, $python_home, $install_log) { 134 | $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" 135 | RunCommand $exepath $install_args 136 | } 137 | 138 | 139 | function InstallPythonMSI ($msipath, $python_home, $install_log) { 140 | $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" 141 | $uninstall_args = "/qn /x $msipath" 142 | RunCommand "msiexec.exe" $install_args 143 | if (-not(Test-Path $python_home)) { 144 | Write-Host "Python seems to be installed else-where, reinstalling." 145 | RunCommand "msiexec.exe" $uninstall_args 146 | RunCommand "msiexec.exe" $install_args 147 | } 148 | } 149 | 150 | function RunCommand ($command, $command_args) { 151 | Write-Host $command $command_args 152 | Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru 153 | } 154 | 155 | 156 | function InstallPip ($python_home) { 157 | $pip_path = $python_home + "\Scripts\pip.exe" 158 | $python_path = $python_home + "\python.exe" 159 | if (-not(Test-Path $pip_path)) { 160 | Write-Host "Installing pip..." 161 | $webclient = New-Object System.Net.WebClient 162 | $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) 163 | Write-Host "Executing:" $python_path $GET_PIP_PATH 164 | & $python_path $GET_PIP_PATH 165 | } else { 166 | Write-Host "pip already installed." 167 | } 168 | } 169 | 170 | 171 | function main () { 172 | InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON 173 | InstallPip $env:PYTHON 174 | } 175 | 176 | main 177 | -------------------------------------------------------------------------------- /appveyor/run_with_env.cmd: -------------------------------------------------------------------------------- 1 | :: To build extensions for 64 bit Python 3, we need to configure environment 2 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 3 | :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) 4 | :: 5 | :: To build extensions for 64 bit Python 2, we need to configure environment 6 | :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: 7 | :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) 8 | :: 9 | :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific 10 | :: environment configurations. 11 | :: 12 | :: Note: this script needs to be run with the /E:ON and /V:ON flags for the 13 | :: cmd interpreter, at least for (SDK v7.0) 14 | :: 15 | :: More details at: 16 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows 17 | :: http://stackoverflow.com/a/13751649/163740 18 | :: 19 | :: Author: Olivier Grisel 20 | :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 21 | :: 22 | :: Notes about batch files for Python people: 23 | :: 24 | :: Quotes in values are literally part of the values: 25 | :: SET FOO="bar" 26 | :: FOO is now five characters long: " b a r " 27 | :: If you don't want quotes, don't include them on the right-hand side. 28 | :: 29 | :: The CALL lines at the end of this file look redundant, but if you move them 30 | :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y 31 | :: case, I don't know why. 32 | @ECHO OFF 33 | 34 | SET COMMAND_TO_RUN=%* 35 | SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows 36 | SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf 37 | 38 | :: Extract the major and minor versions, and allow for the minor version to be 39 | :: more than 9. This requires the version number to have two dots in it. 40 | SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% 41 | IF "%PYTHON_VERSION:~3,1%" == "." ( 42 | SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% 43 | ) ELSE ( 44 | SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% 45 | ) 46 | 47 | :: Based on the Python version, determine what SDK version to use, and whether 48 | :: to set the SDK for 64-bit. 49 | IF %MAJOR_PYTHON_VERSION% == 2 ( 50 | SET WINDOWS_SDK_VERSION="v7.0" 51 | SET SET_SDK_64=Y 52 | ) ELSE ( 53 | IF %MAJOR_PYTHON_VERSION% == 3 ( 54 | SET WINDOWS_SDK_VERSION="v7.1" 55 | IF %MINOR_PYTHON_VERSION% LEQ 4 ( 56 | SET SET_SDK_64=Y 57 | ) ELSE ( 58 | SET SET_SDK_64=N 59 | IF EXIST "%WIN_WDK%" ( 60 | :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ 61 | REN "%WIN_WDK%" 0wdf 62 | ) 63 | ) 64 | ) ELSE ( 65 | ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" 66 | EXIT 1 67 | ) 68 | ) 69 | 70 | IF %PYTHON_ARCH% == 64 ( 71 | IF %SET_SDK_64% == Y ( 72 | ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture 73 | SET DISTUTILS_USE_SDK=1 74 | SET MSSdk=1 75 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% 76 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release 77 | ECHO Executing: %COMMAND_TO_RUN% 78 | call %COMMAND_TO_RUN% || EXIT 1 79 | ) ELSE ( 80 | ECHO Using default MSVC build environment for 64 bit architecture 81 | ECHO Executing: %COMMAND_TO_RUN% 82 | call %COMMAND_TO_RUN% || EXIT 1 83 | ) 84 | ) ELSE ( 85 | ECHO Using default MSVC build environment for 32 bit architecture 86 | ECHO Executing: %COMMAND_TO_RUN% 87 | call %COMMAND_TO_RUN% || EXIT 1 88 | ) 89 | -------------------------------------------------------------------------------- /appveyor/setup_x64.bat: -------------------------------------------------------------------------------- 1 | regedit /s x64\VC_OBJECTS_PLATFORM_INFO.reg 2 | 3 | regedit /s x64\600dd186-2429-11d7-8bf6-00b0d03daa06.reg 4 | regedit /s x64\600dd187-2429-11d7-8bf6-00b0d03daa06.reg 5 | regedit /s x64\600dd188-2429-11d7-8bf6-00b0d03daa06.reg 6 | regedit /s x64\600dd189-2429-11d7-8bf6-00b0d03daa06.reg 7 | regedit /s x64\656d875f-2429-11d7-8bf6-00b0d03daa06.reg 8 | regedit /s x64\656d8760-2429-11d7-8bf6-00b0d03daa06.reg 9 | regedit /s x64\656d8763-2429-11d7-8bf6-00b0d03daa06.reg 10 | regedit /s x64\656d8766-2429-11d7-8bf6-00b0d03daa06.reg 11 | 12 | copy "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcpackages\AMD64.VCPlatform.config" "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcpackages\AMD64.VCPlatform.Express.config" 13 | copy "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcpackages\Itanium.VCPlatform.config" "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcpackages\Itanium.VCPlatform.Express.config" 14 | copy "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat" "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\vcvarsamd64.bat" 15 | -------------------------------------------------------------------------------- /appveyor/x64/600dd186-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/600dd186-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/600dd187-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/600dd187-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/600dd188-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/600dd188-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/600dd189-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/600dd189-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/656d875f-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/656d875f-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/656d8760-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/656d8760-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/656d8763-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/656d8763-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/656d8766-2429-11d7-8bf6-00b0d03daa06.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/656d8766-2429-11d7-8bf6-00b0d03daa06.reg -------------------------------------------------------------------------------- /appveyor/x64/VC_OBJECTS_PLATFORM_INFO.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VirusTotal/yara-python/74920b6da9a70a162b3bdc41b30e4af02e5c7dff/appveyor/x64/VC_OBJECTS_PLATFORM_INFO.reg -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.rst 3 | license_file = LICENSE 4 | 5 | [test] 6 | test_suite=tests 7 | 8 | [build_ext] 9 | # These modules are not stable or tested enough 10 | # enable_dex = true 11 | # enable_macho = true 12 | 13 | # need libjansson-dev 14 | # enable_cuckoo = true 15 | 16 | # need libmagic-dev 17 | # enable_magic = true 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2007-2022. The YARA Authors. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from setuptools import setup, Command, Extension 18 | from distutils.command.build import build 19 | from distutils.command.build_ext import build_ext 20 | from codecs import open 21 | 22 | import distutils.errors 23 | import distutils.ccompiler 24 | import distutils.sysconfig 25 | 26 | import contextlib 27 | import os 28 | import sys 29 | import tempfile 30 | import shutil 31 | import subprocess 32 | 33 | OPTIONS = [ 34 | ('dynamic-linking', None, 'link dynamically against libyara'), 35 | ('enable-cuckoo', None, 'enable "cuckoo" module'), 36 | ('enable-magic', None, 'enable "magic" module'), 37 | ('enable-dex', None, 'enable "dex" module'), 38 | ('enable-macho', None, 'enable "macho" module'), 39 | ('enable-profiling', None, 'enable profiling features'), 40 | ('enable-openssl', None, 'enable features that depend on OpenSSL'), 41 | ] 42 | 43 | 44 | BOOLEAN_OPTIONS = [ 45 | 'dynamic-linking', 46 | 'enable-cuckoo', 47 | 'enable-magic', 48 | 'enable-dex', 49 | 'enable-macho', 50 | 'enable-profiling', 51 | 'enable-openssl', 52 | ] 53 | 54 | 55 | @contextlib.contextmanager 56 | def muted(*streams): 57 | """A context manager to redirect stdout and/or stderr to /dev/null. 58 | 59 | Examples: 60 | with muted(sys.stdout): 61 | ... 62 | 63 | with muted(sys.stderr): 64 | ... 65 | 66 | with muted(sys.stdout, sys.stderr): 67 | ... 68 | """ 69 | devnull = open(os.devnull, 'w') 70 | try: 71 | old_streams = [os.dup(s.fileno()) for s in streams] 72 | for s in streams: 73 | os.dup2(devnull.fileno(), s.fileno()) 74 | yield 75 | finally: 76 | for o,n in zip(old_streams, streams): 77 | os.dup2(o, n.fileno()) 78 | devnull.close() 79 | 80 | 81 | def has_function(function_name, includes=None, include_dirs=None, libraries=None, library_dirs=None): 82 | """Checks if a given functions exists in the current platform.""" 83 | compiler = distutils.ccompiler.new_compiler() 84 | with muted(sys.stdout, sys.stderr): 85 | result = compiler.has_function( 86 | function_name, 87 | includes=includes, 88 | include_dirs=include_dirs, 89 | libraries=libraries, 90 | library_dirs=library_dirs) 91 | if os.path.exists('a.out'): 92 | os.remove('a.out') 93 | return result 94 | 95 | 96 | def has_header(header_name): 97 | compiler = distutils.ccompiler.new_compiler() 98 | with muted(sys.stdout, sys.stderr): 99 | with tempfile.NamedTemporaryFile(mode='w', prefix=header_name, delete=False, suffix='.c') as f: 100 | f.write(""" 101 | #include <{}> 102 | 103 | int main() {{ return 0; }} 104 | """.format(header_name)) 105 | f.close() 106 | try: 107 | compiler.compile([f.name]) 108 | except distutils.errors.CompileError: 109 | return False 110 | return True 111 | 112 | 113 | class BuildCommand(build): 114 | 115 | user_options = build.user_options + OPTIONS 116 | boolean_options = build.boolean_options + BOOLEAN_OPTIONS 117 | 118 | def initialize_options(self): 119 | 120 | build.initialize_options(self) 121 | self.dynamic_linking = None 122 | self.enable_magic = None 123 | self.enable_cuckoo = None 124 | self.enable_dex = None 125 | self.enable_macho = None 126 | self.enable_profiling = None 127 | self.enable_openssl = None 128 | 129 | def finalize_options(self): 130 | 131 | build.finalize_options(self) 132 | 133 | 134 | 135 | class BuildExtCommand(build_ext): 136 | 137 | user_options = build_ext.user_options + OPTIONS 138 | boolean_options = build_ext.boolean_options + BOOLEAN_OPTIONS 139 | 140 | def initialize_options(self): 141 | 142 | build_ext.initialize_options(self) 143 | self.dynamic_linking = None 144 | self.enable_magic = None 145 | self.enable_cuckoo = None 146 | self.enable_dex = None 147 | self.enable_macho = None 148 | self.enable_profiling = None 149 | self.enable_openssl = None 150 | 151 | def finalize_options(self): 152 | 153 | build_ext.finalize_options(self) 154 | 155 | # If the build_ext command was invoked by the build command, take the 156 | # values for these options from the build command. 157 | 158 | self.set_undefined_options('build', 159 | ('dynamic_linking', 'dynamic_linking'), 160 | ('enable_magic', 'enable_magic'), 161 | ('enable_cuckoo', 'enable_cuckoo'), 162 | ('enable_dex', 'enable_dex'), 163 | ('enable_macho', 'enable_macho'), 164 | ('enable_profiling', 'enable_profiling'), 165 | ('enable_openssl', 'enable_openssl')) 166 | 167 | if self.enable_magic and self.dynamic_linking: 168 | raise distutils.errors.DistutilsOptionError( 169 | '--enable-magic can''t be used with --dynamic-linking') 170 | if self.enable_cuckoo and self.dynamic_linking: 171 | raise distutils.errors.DistutilsOptionError( 172 | '--enable-cuckoo can''t be used with --dynamic-linking') 173 | if self.enable_dex and self.dynamic_linking: 174 | raise distutils.errors.DistutilsOptionError( 175 | '--enable-dex can''t be used with --dynamic-linking') 176 | if self.enable_macho and self.dynamic_linking: 177 | raise distutils.errors.DistutilsOptionError( 178 | '--enable-macho can''t be used with --dynamic-linking') 179 | if self.enable_openssl and self.dynamic_linking: 180 | raise distutils.errors.DistutilsOptionError( 181 | '--enable-openssl can''t be used with --dynamic-linking') 182 | 183 | 184 | def run(self): 185 | """Execute the build command.""" 186 | 187 | module = self.distribution.ext_modules[0] 188 | base_dir = os.path.dirname(__file__) 189 | 190 | if base_dir: 191 | os.chdir(base_dir) 192 | 193 | exclusions = [] 194 | 195 | for define in self.define or []: 196 | module.define_macros.append(define) 197 | 198 | for library in self.libraries or []: 199 | module.libraries.append(library) 200 | 201 | building_for_windows = self.plat_name in ('win32','win-amd64') 202 | building_for_osx = 'macosx' in self.plat_name 203 | building_for_linux = 'linux' in self.plat_name 204 | building_for_freebsd = 'freebsd' in self.plat_name 205 | building_for_openbsd = 'openbsd' in self.plat_name # need testing 206 | 207 | if building_for_windows: 208 | arch = 'x86' if self.plat_name == 'win32' else 'x64' 209 | openssl_include_dirs = [ 210 | os.path.join(base_dir, 'yara\\windows\\vs2015\\packages\\YARA.OpenSSL.{}.1.1.1\\include'.format(arch)), 211 | os.path.join(base_dir, 'yara\\windows\\vs2017\\packages\\YARA.OpenSSL.{}.1.1.1\\include'.format(arch)) 212 | ] 213 | openssl_library_dirs = [ 214 | os.path.join(base_dir, 'yara\\windows\\vs2015\\packages\\YARA.OpenSSL.{}.1.1.1\\lib'.format(arch)), 215 | os.path.join(base_dir, 'yara\\windows\\vs2017\\packages\\YARA.OpenSSL.{}.1.1.1\\lib'.format(arch)) 216 | ] 217 | openssl_libraries = ['libcrypto'] 218 | else: 219 | openssl_include_dirs = [] 220 | openssl_library_dirs = [] 221 | openssl_libraries = ['crypto'] 222 | 223 | if building_for_linux: 224 | module.define_macros.append(('_GNU_SOURCE', '1')) 225 | module.define_macros.append(('USE_LINUX_PROC', '1')) 226 | module.extra_compile_args.append('-std=c99') 227 | elif building_for_windows: 228 | module.define_macros.append(('USE_WINDOWS_PROC', '1')) 229 | module.define_macros.append(('_CRT_SECURE_NO_WARNINGS', '1')) 230 | module.libraries.append('kernel32') 231 | module.libraries.append('advapi32') 232 | module.libraries.append('user32') 233 | module.libraries.append('crypt32') 234 | module.libraries.append('ws2_32') 235 | elif building_for_osx: 236 | module.define_macros.append(('_GNU_SOURCE', '1')) 237 | module.define_macros.append(('USE_MACH_PROC', '1')) 238 | module.extra_compile_args.append('-std=c99') 239 | module.include_dirs.append('/usr/local/opt/openssl/include') 240 | module.include_dirs.append('/opt/local/include') 241 | module.library_dirs.append('/opt/local/lib') 242 | module.include_dirs.append('/usr/local/include') 243 | module.library_dirs.append('/usr/local/lib') 244 | module.library_dirs.append('/usr/local/opt/openssl/lib') 245 | module.include_dirs.append('/opt/homebrew/include') 246 | module.library_dirs.append('/opt/homebrew/opt/openssl/lib') 247 | elif building_for_freebsd: 248 | module.define_macros.append(('_GNU_SOURCE', '1')) 249 | module.define_macros.append(('USE_FREEBSD_PROC', '1')) 250 | module.include_dirs.append('/opt/local/include') 251 | module.library_dirs.append('/opt/local/lib') 252 | module.include_dirs.append('/usr/local/include') 253 | module.library_dirs.append('/usr/local/lib') 254 | elif building_for_openbsd: 255 | module.define_macros.append(('_GNU_SOURCE', '1')) 256 | module.define_macros.append(('USE_OPENBSD_PROC', '1')) 257 | module.extra_compile_args.append('-std=c99') 258 | module.include_dirs.append('/opt/local/include') 259 | module.library_dirs.append('/opt/local/lib') 260 | module.include_dirs.append('/usr/local/include') 261 | module.library_dirs.append('/usr/local/lib') 262 | else: 263 | module.define_macros.append(('_GNU_SOURCE', '1')) 264 | module.define_macros.append(('USE_NO_PROC', '1')) 265 | module.extra_compile_args.append('-std=c99') 266 | 267 | if has_header('stdbool.h'): 268 | module.define_macros.append(('HAVE_STDBOOL_H', '1')) 269 | 270 | if has_function('memmem'): 271 | module.define_macros.append(('HAVE_MEMMEM', '1')) 272 | if has_function('strlcpy'): 273 | module.define_macros.append(('HAVE_STRLCPY', '1')) 274 | if has_function('strlcat'): 275 | module.define_macros.append(('HAVE_STRLCAT', '1')) 276 | 277 | if self.enable_profiling: 278 | module.define_macros.append(('YR_PROFILING_ENABLED', '1')) 279 | 280 | if self.dynamic_linking: 281 | module.libraries.append('yara') 282 | else: 283 | # Is OpenSSL available? 284 | if (has_function('OpenSSL_add_all_algorithms', 285 | includes=['openssl/evp.h'], 286 | include_dirs=module.include_dirs + openssl_include_dirs, 287 | libraries=module.libraries + openssl_libraries, 288 | library_dirs=module.library_dirs + openssl_library_dirs) 289 | # In case OpenSSL is being linked statically 290 | or has_function('OpenSSL_add_all_algorithms', 291 | includes=['openssl/evp.h'], 292 | include_dirs=module.include_dirs + openssl_include_dirs, 293 | libraries=module.libraries + openssl_libraries + ['dl', 'pthread', 'z'], 294 | library_dirs=module.library_dirs + openssl_library_dirs) 295 | or self.enable_openssl): 296 | module.define_macros.append(('HASH_MODULE', '1')) 297 | module.define_macros.append(('HAVE_LIBCRYPTO', '1')) 298 | module.libraries.extend(openssl_libraries) 299 | module.include_dirs.extend(openssl_include_dirs) 300 | module.library_dirs.extend(openssl_library_dirs) 301 | elif building_for_windows: 302 | # OpenSSL is not available, but in Windows we can rely on Wincrypt for 303 | # hashing functions. 304 | module.define_macros.append(('HASH_MODULE', '1')) 305 | module.define_macros.append(('HAVE_WINCRYPT_H', '1')) 306 | # The authenticode parser depends on OpenSSL and must be excluded. 307 | exclusions.append('yara/libyara/modules/pe/authenticode-parser') 308 | else: 309 | # Without OpenSSL there's no hash module nor authenticode parser. 310 | exclusions.append('yara/libyara/modules/hash/hash.c') 311 | exclusions.append('yara/libyara/modules/pe/authenticode-parser') 312 | 313 | if self.enable_magic: 314 | module.define_macros.append(('MAGIC_MODULE', '1')) 315 | module.libraries.append('magic') 316 | else: 317 | exclusions.append('yara/libyara/modules/magic/magic.c') 318 | 319 | if self.enable_cuckoo: 320 | module.define_macros.append(('CUCKOO_MODULE', '1')) 321 | module.libraries.append('jansson') 322 | else: 323 | exclusions.append('yara/libyara/modules/cuckoo/cuckoo.c') 324 | 325 | if self.enable_dex: 326 | module.define_macros.append(('DEX_MODULE', '1')) 327 | else: 328 | exclusions.append('yara/libyara/modules/dex/dex.c') 329 | 330 | if self.enable_macho: 331 | module.define_macros.append(('MACHO_MODULE', '1')) 332 | else: 333 | exclusions.append('yara/libyara/modules/macho/macho.c') 334 | 335 | # exclude pb_tests module 336 | exclusions.append('yara/libyara/modules/pb_tests/pb_tests.c') 337 | exclusions.append('yara/libyara/modules/pb_tests/pb_tests.pb-c.c') 338 | 339 | # Always turn on the DOTNET module. 340 | module.define_macros.append(('DOTNET_MODULE', '1')) 341 | 342 | exclusions = [os.path.normpath(x) for x in exclusions] 343 | 344 | for directory, _, files in os.walk('yara/libyara/'): 345 | for f in files: 346 | f = os.path.normpath(os.path.join(directory, f)) 347 | # Ignore any file that is not a .c file 348 | if not f.endswith('.c'): 349 | continue 350 | # Ignore files that are listed in the exclusion list. 351 | if any(map(lambda e: f.startswith(e), exclusions)): 352 | continue 353 | module.sources.append(f) 354 | 355 | build_ext.run(self) 356 | 357 | 358 | class UpdateCommand(Command): 359 | """Update libyara source. 360 | 361 | This is normally only run by packagers to make a new release. 362 | """ 363 | user_options = [] 364 | 365 | def initialize_options(self): 366 | pass 367 | 368 | def finalize_options(self): 369 | pass 370 | 371 | def run(self): 372 | subprocess.check_call(['git', 'stash'], cwd='yara') 373 | 374 | subprocess.check_call(['git', 'submodule', 'init']) 375 | subprocess.check_call(['git', 'submodule', 'update']) 376 | 377 | subprocess.check_call(['git', 'reset', '--hard'], cwd='yara') 378 | subprocess.check_call(['git', 'clean', '-x', '-f', '-d'], cwd='yara') 379 | 380 | subprocess.check_call(['git', 'checkout', 'master'], cwd='yara') 381 | subprocess.check_call(['git', 'pull'], cwd='yara') 382 | subprocess.check_call(['git', 'fetch', '--tags'], cwd='yara') 383 | 384 | tag_name = 'tags/v%s' % self.distribution.metadata.version 385 | subprocess.check_call(['git', 'checkout', tag_name], cwd='yara') 386 | 387 | subprocess.check_call(['./bootstrap.sh'], cwd='yara') 388 | subprocess.check_call(['./configure'], cwd='yara') 389 | 390 | 391 | with open('README.rst', 'r', 'utf-8') as f: 392 | readme = f.read() 393 | 394 | setup( 395 | name='yara-python', 396 | version='4.5.4', 397 | description='Python interface for YARA', 398 | long_description=readme, 399 | long_description_content_type='text/markdown', 400 | license='Apache 2.0', 401 | author='Victor M. Alvarez', 402 | author_email='plusvic@gmail.com, vmalvarez@virustotal.com', 403 | url='https://github.com/VirusTotal/yara-python', 404 | classifiers=[ 405 | 'Programming Language :: Python', 406 | 'License :: OSI Approved :: Apache Software License', 407 | 'Operating System :: OS Independent', 408 | 'Development Status :: 5 - Production/Stable', 409 | ], 410 | zip_safe=False, 411 | cmdclass={ 412 | 'build': BuildCommand, 413 | 'build_ext': BuildExtCommand, 414 | 'update': UpdateCommand}, 415 | ext_modules=[Extension( 416 | name='yara', 417 | include_dirs=['yara/libyara/include', 'yara/libyara/', '.'], 418 | define_macros=[('BUCKETS_128', 1), ('CHECKSUM_1B', 1)], 419 | sources=['yara-python.c'])]) 420 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) 2007-2022. The YARA Authors. All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | import tempfile 20 | import binascii 21 | import os 22 | import sys 23 | import unittest 24 | import yara 25 | # Python 2/3 26 | try: 27 | import StringIO 28 | except: 29 | import io 30 | 31 | PE32_FILE = binascii.unhexlify('\ 32 | 4d5a000000000000000000000000000000000000000000000000000000000000\ 33 | 0000000000000000000000000000000000000000000000000000000040000000\ 34 | 504500004c0101005dbe45450000000000000000e00003010b01080004000000\ 35 | 0000000000000000600100006001000064010000000040000100000001000000\ 36 | 0400000000000000040000000000000064010000600100000000000002000004\ 37 | 0000100000100000000010000010000000000000100000000000000000000000\ 38 | 0000000000000000000000000000000000000000000000000000000000000000\ 39 | 0000000000000000000000000000000000000000000000000000000000000000\ 40 | 0000000000000000000000000000000000000000000000000000000000000000\ 41 | 0000000000000000000000000000000000000000000000002e74657874000000\ 42 | 0400000060010000040000006001000000000000000000000000000020000060\ 43 | 6a2a58c3') 44 | 45 | ELF32_FILE = binascii.unhexlify('\ 46 | 7f454c4601010100000000000000000002000300010000006080040834000000\ 47 | a800000000000000340020000100280004000300010000000000000000800408\ 48 | 008004086c0000006c0000000500000000100000000000000000000000000000\ 49 | b801000000bb2a000000cd8000546865204e65747769646520417373656d626c\ 50 | 657220322e30352e303100002e7368737472746162002e74657874002e636f6d\ 51 | 6d656e7400000000000000000000000000000000000000000000000000000000\ 52 | 000000000000000000000000000000000b000000010000000600000060800408\ 53 | 600000000c000000000000000000000010000000000000001100000001000000\ 54 | 00000000000000006c0000001f00000000000000000000000100000000000000\ 55 | 010000000300000000000000000000008b0000001a0000000000000000000000\ 56 | 0100000000000000') 57 | 58 | ELF64_FILE = binascii.unhexlify('\ 59 | 7f454c4602010100000000000000000002003e00010000008000400000000000\ 60 | 4000000000000000c80000000000000000000000400038000100400004000300\ 61 | 0100000005000000000000000000000000004000000000000000400000000000\ 62 | 8c000000000000008c0000000000000000002000000000000000000000000000\ 63 | b801000000bb2a000000cd8000546865204e65747769646520417373656d626c\ 64 | 657220322e30352e303100002e7368737472746162002e74657874002e636f6d\ 65 | 6d656e7400000000000000000000000000000000000000000000000000000000\ 66 | 0000000000000000000000000000000000000000000000000000000000000000\ 67 | 00000000000000000b0000000100000006000000000000008000400000000000\ 68 | 80000000000000000c0000000000000000000000000000001000000000000000\ 69 | 0000000000000000110000000100000000000000000000000000000000000000\ 70 | 8c000000000000001f0000000000000000000000000000000100000000000000\ 71 | 0000000000000000010000000300000000000000000000000000000000000000\ 72 | ab000000000000001a0000000000000000000000000000000100000000000000\ 73 | 0000000000000000') 74 | 75 | # The 3 possible outcomes for each pattern 76 | [SUCCEED, FAIL, SYNTAX_ERROR] = range(3) 77 | 78 | RE_TESTS = [ 79 | 80 | # RE, string, expected result, expected matching 81 | 82 | (')', '', SYNTAX_ERROR), 83 | ('abc', 'abc', SUCCEED, 'abc'), 84 | ('abc', 'xbc', FAIL), 85 | ('abc', 'axc', FAIL), 86 | ('abc', 'abx', FAIL), 87 | ('abc', 'xabcx', SUCCEED, 'abc'), 88 | ('abc', 'ababc', SUCCEED, 'abc'), 89 | ('a.c', 'abc', SUCCEED, 'abc'), 90 | ('a.b', 'a\nb', FAIL), 91 | ('a.*b', 'acc\nccb', FAIL), 92 | ('a.{4,5}b', 'acc\nccb', FAIL), 93 | ('a.b', 'a\rb', SUCCEED, 'a\rb'), 94 | ('ab*c', 'abc', SUCCEED, 'abc'), 95 | ('ab*c', 'ac', SUCCEED, 'ac'), 96 | ('ab*bc', 'abc', SUCCEED, 'abc'), 97 | ('ab*bc', 'abbc', SUCCEED, 'abbc'), 98 | ('a.*bb', 'abbbb', SUCCEED, 'abbbb'), 99 | ('a.*?bbb', 'abbbbbb', SUCCEED, 'abbb'), 100 | ('a.*c', 'ac', SUCCEED, 'ac'), 101 | ('a.*c', 'axyzc', SUCCEED, 'axyzc'), 102 | ('ab+c', 'abbc', SUCCEED, 'abbc'), 103 | ('ab+c', 'ac', FAIL), 104 | ('ab+', 'abbbb', SUCCEED, 'abbbb'), 105 | ('ab+?', 'abbbb', SUCCEED, 'ab'), 106 | ('ab+bc', 'abc', FAIL), 107 | ('ab+bc', 'abq', FAIL), 108 | ('a+b+c', 'aabbabc', SUCCEED, 'abc'), 109 | ('ab?bc', 'abbbbc', FAIL), 110 | ('ab?c', 'abc', SUCCEED, 'abc'), 111 | ('ab*?', 'abbb', SUCCEED, 'a'), 112 | ('ab?c', 'abc', SUCCEED, 'abc'), 113 | ('ab??', 'ab', SUCCEED, 'a'), 114 | ('a(b|x)c', 'abc', SUCCEED, 'abc'), 115 | ('a(b|x)c', 'axc', SUCCEED, 'axc'), 116 | ('a(b|.)c', 'axc', SUCCEED, 'axc'), 117 | ('a(b|x|y)c', 'ayc', SUCCEED, 'ayc'), 118 | ('(a+|b)*', 'ab', SUCCEED, 'ab'), 119 | ('a|b|c|d|e', 'e', SUCCEED, 'e'), 120 | ('(a|b|c|d|e)f', 'ef', SUCCEED, 'ef'), 121 | ('.b{2}', 'abb', SUCCEED, 'abb'), 122 | ('ab{1}c', 'abc', SUCCEED, 'abc'), 123 | ('ab{1,2}c', 'abbc', SUCCEED, 'abbc'), 124 | ('ab{1,}c', 'abbbc', SUCCEED, 'abbbc'), 125 | ('ab{1,}b', 'ab', FAIL), 126 | ('ab{1}c', 'abbc', FAIL), 127 | ('ab{0,}c', 'ac', SUCCEED, 'ac'), 128 | ('ab{0,}c', 'abbbc', SUCCEED, 'abbbc'), 129 | ('ab{,3}c', 'abbbc', SUCCEED, 'abbbc'), 130 | ('ab{,2}c', 'abbbc', FAIL), 131 | ('ab{4,5}bc', 'abbbbc', FAIL), 132 | ('ab{2,3}?', 'abbbbb', SUCCEED, 'abb'), 133 | ('ab{.*}', 'ab{c}', SUCCEED, 'ab{c}'), 134 | ('.(aa){1,2}', 'aaaaaaaaaa', SUCCEED, 'aaaaa'), 135 | ('a.(bc.){2}', 'aabcabca', SUCCEED, 'aabcabca'), 136 | ('(ab{1,2}c){1,3}', 'abbcabc', SUCCEED, 'abbcabc'), 137 | ('ab(c|cc){1,3}d', 'abccccccd', SUCCEED, 'abccccccd'), 138 | ('a[bx]c', 'abc', SUCCEED, 'abc'), 139 | ('a[bx]c', 'axc', SUCCEED, 'axc'), 140 | ('a[0-9]*b', 'ab', SUCCEED, 'ab'), 141 | ('a[0-9]*b', 'a0123456789b', SUCCEED, 'a0123456789b'), 142 | ('[0-9a-f]+', '0123456789abcdef', SUCCEED, '0123456789abcdef'), 143 | ('[0-9a-f]+', 'xyz0123456789xyz', SUCCEED, '0123456789'), 144 | (r'a[\s\S]b', 'a b', SUCCEED, 'a b'), 145 | (r'a[\d\D]b', 'a1b', SUCCEED, 'a1b'), 146 | ('[x-z]+', 'abc', FAIL), 147 | ('a[-]?c', 'ac', SUCCEED, 'ac'), 148 | ('a[-b]', 'a-', SUCCEED, 'a-'), 149 | ('a[-b]', 'ab', SUCCEED, 'ab'), 150 | ('a[b-]', 'a-', SUCCEED, 'a-'), 151 | ('a[b-]', 'ab', SUCCEED, 'ab'), 152 | ('[a-c-e]', 'b', SUCCEED, 'b'), 153 | ('[a-c-e]', '-', SUCCEED, '-'), 154 | ('[a-c-e]', 'd', FAIL), 155 | ('[b-a]', '', SYNTAX_ERROR), 156 | ('(abc', '', SYNTAX_ERROR), 157 | ('abc)', '', SYNTAX_ERROR), 158 | ('a[]b', '', SYNTAX_ERROR), 159 | ('a\\', '', SYNTAX_ERROR), 160 | ('a[\\-b]', 'a-', SUCCEED, 'a-'), 161 | ('a[\\-b]', 'ab', SUCCEED, 'ab'), 162 | ('a[\\', '', SYNTAX_ERROR), 163 | ('a]', 'a]', SUCCEED, 'a]'), 164 | ('a[]]b', 'a]b', SUCCEED, 'a]b'), 165 | (r'a[\]]b', 'a]b', SUCCEED, 'a]b'), 166 | ('a[^bc]d', 'aed', SUCCEED, 'aed'), 167 | ('a[^bc]d', 'abd', FAIL), 168 | ('a[^-b]c', 'adc', SUCCEED, 'adc'), 169 | ('a[^-b]c', 'a-c', FAIL), 170 | ('a[^]b]c', 'a]c', FAIL), 171 | ('a[^]b]c', 'adc', SUCCEED, 'adc'), 172 | ('[^ab]*', 'cde', SUCCEED, 'cde'), 173 | (')(', '', SYNTAX_ERROR), 174 | (r'a\sb', 'a b', SUCCEED, 'a b'), 175 | (r'a\sb', 'a\tb', SUCCEED, 'a\tb'), 176 | (r'a\sb', 'a\rb', SUCCEED, 'a\rb'), 177 | (r'a\sb', 'a\nb', SUCCEED, 'a\nb'), 178 | (r'a\sb', 'a\vb', SUCCEED, 'a\vb'), 179 | (r'a\sb', 'a\fb', SUCCEED, 'a\fb'), 180 | (r'a\Sb', 'a b', FAIL), 181 | (r'a\Sb', 'a\tb', FAIL), 182 | (r'a\Sb', 'a\rb', FAIL), 183 | (r'a\Sb', 'a\nb', FAIL), 184 | (r'a\Sb', 'a\vb', FAIL), 185 | (r'a\Sb', 'a\fb', FAIL), 186 | (r'\n\r\t\f\a', '\n\r\t\f\a', SUCCEED, '\n\r\t\f\a'), 187 | (r'[\n][\r][\t][\f][\a]', '\n\r\t\f\a', SUCCEED, '\n\r\t\f\a'), 188 | (r'\x00\x01\x02', '\x00\x01\x02', SUCCEED, '\x00\x01\x02'), 189 | (r'[\x00-\x02]+', '\x00\x01\x02', SUCCEED, '\x00\x01\x02'), 190 | (r'[\x00-\x02]+', '\x03\x04\x05', FAIL), 191 | (r'[\x5D]', ']', SUCCEED, ']'), 192 | (r'[\0x5A-\x5D]', '\x5B', SUCCEED, '\x5B'), 193 | (r'[\x5D-\x5F]', '\x5E', SUCCEED, '\x5E'), 194 | (r'[\x5C-\x5F]', '\x5E', SUCCEED, '\x5E'), 195 | (r'[\x5D-\x5F]', '\x5E', SUCCEED, '\x5E'), 196 | (r'a\wc', 'abc', SUCCEED, 'abc'), 197 | (r'a\wc', 'a_c', SUCCEED, 'a_c'), 198 | (r'a\wc', 'a0c', SUCCEED, 'a0c'), 199 | (r'a\wc', 'a*c', FAIL), 200 | (r'\w+', '--ab_cd0123--', SUCCEED, 'ab_cd0123'), 201 | (r'[\w]+', '--ab_cd0123--', SUCCEED, 'ab_cd0123'), 202 | (r'\D+', '1234abc5678', SUCCEED, 'abc'), 203 | (r'[\d]+', '0123456789', SUCCEED, '0123456789'), 204 | (r'[\D]+', '1234abc5678', SUCCEED, 'abc'), 205 | (r'[\da-fA-F]+', '123abc', SUCCEED, '123abc'), 206 | ('^(ab|cd)e', 'abcde', FAIL), 207 | ('(abc|)ef', 'abcdef', SUCCEED, 'ef'), 208 | ('(abc|)ef', 'abcef', SUCCEED, 'abcef'), 209 | (r'\babc', 'abc', SUCCEED, 'abc'), 210 | (r'abc\b', 'abc', SUCCEED, 'abc'), 211 | (r'\babc', '1abc', FAIL), 212 | (r'abc\b', 'abc1', FAIL), 213 | (r'abc\s\b', 'abc x', SUCCEED, 'abc '), 214 | (r'abc\s\b', 'abc ', FAIL), 215 | (r'\babc\b', ' abc ', SUCCEED, 'abc'), 216 | (r'\b\w\w\w\b', ' abc ', SUCCEED, 'abc'), 217 | (r'\w\w\w\b', 'abcd', SUCCEED, 'bcd'), 218 | (r'\b\w\w\w', 'abcd', SUCCEED, 'abc'), 219 | (r'\b\w\w\w\b', 'abcd', FAIL), 220 | (r'\Babc', 'abc', FAIL), 221 | (r'abc\B', 'abc', FAIL), 222 | (r'\Babc', '1abc', SUCCEED, 'abc'), 223 | (r'abc\B', 'abc1', SUCCEED, 'abc'), 224 | (r'abc\s\B', 'abc x', FAIL), 225 | (r'abc\s\B', 'abc ', SUCCEED, 'abc '), 226 | (r'\w\w\w\B', 'abcd', SUCCEED, 'abc'), 227 | (r'\B\w\w\w', 'abcd', SUCCEED, 'bcd'), 228 | (r'\B\w\w\w\B', 'abcd', FAIL), 229 | 230 | # This is allowed in most regexp engines but in order to keep the 231 | # grammar free of shift/reduce conflicts I've decided not supporting 232 | # it. Users can use the (abc|) form instead. 233 | 234 | ('(|abc)ef', '', SYNTAX_ERROR), 235 | 236 | ('((a)(b)c)(d)', 'abcd', SUCCEED, 'abcd'), 237 | ('(a|b)c*d', 'abcd', SUCCEED, 'bcd'), 238 | ('(ab|ab*)bc', 'abc', SUCCEED, 'abc'), 239 | ('a([bc]*)c*', 'abc', SUCCEED, 'abc'), 240 | ('a([bc]*)c*', 'ac', SUCCEED, 'ac'), 241 | ('a([bc]*)c*', 'a', SUCCEED, 'a'), 242 | ('a([bc]*)(c*d)', 'abcd', SUCCEED, 'abcd'), 243 | ('a([bc]+)(c*d)', 'abcd', SUCCEED, 'abcd'), 244 | ('a([bc]*)(c+d)', 'abcd', SUCCEED, 'abcd'), 245 | ('a[bcd]*dcdcde', 'adcdcde', SUCCEED, 'adcdcde'), 246 | ('a[bcd]+dcdcde', 'adcdcde', FAIL), 247 | (r'\((.*), (.*)\)', '(a, b)', SUCCEED, '(a, b)'), 248 | ('abc|123$', 'abcx', SUCCEED, 'abc'), 249 | ('abc|123$', '123x', FAIL), 250 | ('abc|^123', '123', SUCCEED, '123'), 251 | ('abc|^123', 'x123', FAIL), 252 | ('^abc$', 'abc', SUCCEED, 'abc'), 253 | ('^abc$', 'abcc', FAIL), 254 | ('^abc', 'abcc', SUCCEED, 'abc'), 255 | ('^abc$', 'aabc', FAIL), 256 | ('abc$', 'aabc', SUCCEED, 'abc'), 257 | ('^a(bc+|b[eh])g|.h$', 'abhg', SUCCEED, 'abhg'), 258 | ('(bc+d$|ef*g.|h?i(j|k))', 'effgz', SUCCEED, 'effgz'), 259 | ('(bc+d$|ef*g.|h?i(j|k))', 'ij', SUCCEED, 'ij'), 260 | ('(bc+d$|ef*g.|h?i(j|k))', 'effg', FAIL), 261 | ('(bc+d$|ef*g.|h?i(j|k))', 'bcdd', FAIL), 262 | ('(bc+d$|ef*g.|h?i(j|k))', 'reffgz', SUCCEED, 'effgz'), 263 | 264 | # Test case for issue #324 265 | ('whatever| x. x', ' xy x', SUCCEED, ' xy x'), 266 | ] 267 | 268 | 269 | def warnings_callback(warning_type, message): 270 | global warnings_callback_called, warnings_callback_message 271 | warnings_callback_called = warning_type 272 | warnings_callback_message = message 273 | 274 | 275 | class TestYara(unittest.TestCase): 276 | 277 | def assertTrueRules(self, rules, data='dummy'): 278 | 279 | for r in rules: 280 | r = yara.compile(source=r) 281 | self.assertTrue(r.match(data=data)) 282 | 283 | def assertFalseRules(self, rules, data='dummy'): 284 | 285 | for r in rules: 286 | r = yara.compile(source=r) 287 | self.assertFalse(r.match(data=data)) 288 | 289 | def assertSyntaxError(self, rules): 290 | 291 | for r in rules: 292 | self.assertRaises(yara.SyntaxError, yara.compile, source=r) 293 | 294 | def runReTest(self, test): 295 | 296 | regexp = test[0] 297 | string = test[1] 298 | expected_result = test[2] 299 | 300 | source = 'rule test { strings: $a = /%s/ condition: $a }' % regexp 301 | 302 | if expected_result == SYNTAX_ERROR: 303 | self.assertRaises(yara.SyntaxError, yara.compile, source=source) 304 | else: 305 | rule = yara.compile(source=source) 306 | matches = rule.match(data=string) 307 | if expected_result == SUCCEED: 308 | self.assertTrue(matches) 309 | matching_string = matches[0].strings[0] 310 | instance = matching_string.instances[0] 311 | if sys.version_info[0] >= 3: 312 | self.assertTrue(instance.matched_data == bytes(test[3], 'utf-8')) 313 | else: 314 | self.assertTrue(instance.matched_data == test[3]) 315 | else: 316 | self.assertFalse(matches) 317 | 318 | def testBooleanOperators(self): 319 | 320 | self.assertTrueRules([ 321 | 'rule test { condition: true }', 322 | 'rule test { condition: true or false }', 323 | 'rule test { condition: true and true }', 324 | 'rule test { condition: 0x1 and 0x2}', 325 | ]) 326 | 327 | self.assertFalseRules([ 328 | 'rule test { condition: false }', 329 | 'rule test { condition: true and false }', 330 | 'rule test { condition: false or false }' 331 | ]) 332 | 333 | def testComparisonOperators(self): 334 | 335 | self.assertTrueRules([ 336 | 'rule test { condition: 2 > 1 }', 337 | 'rule test { condition: 1 < 2 }', 338 | 'rule test { condition: 2 >= 1 }', 339 | 'rule test { condition: 1 <= 1 }', 340 | 'rule test { condition: 1 == 1 }', 341 | 'rule test { condition: 1.5 == 1.5}', 342 | 'rule test { condition: 1.0 == 1}', 343 | 'rule test { condition: 1.5 >= 1.0}', 344 | 'rule test { condition: 1.5 >= 1}', 345 | 'rule test { condition: 1.0 >= 1}', 346 | 'rule test { condition: 0.5 < 1}', 347 | 'rule test { condition: 0.5 <= 1}', 348 | 'rule rest { condition: 1.0 <= 1}', 349 | 'rule rest { condition: "abc" == "abc"}', 350 | 'rule rest { condition: "abc" <= "abc"}', 351 | 'rule rest { condition: "abc" >= "abc"}', 352 | 'rule rest { condition: "ab" < "abc"}', 353 | 'rule rest { condition: "abc" > "ab"}', 354 | 'rule rest { condition: "abc" < "abd"}', 355 | 'rule rest { condition: "abd" > "abc"}', 356 | ]) 357 | 358 | self.assertFalseRules([ 359 | 'rule test { condition: 1 != 1}', 360 | 'rule test { condition: 1 != 1.0}', 361 | 'rule test { condition: 2 > 3}', 362 | 'rule test { condition: 2.1 < 2}', 363 | 'rule test { condition: "abc" != "abc"}', 364 | 'rule test { condition: "abc" > "abc"}', 365 | 'rule test { condition: "abc" < "abc"}', 366 | ]) 367 | 368 | def testArithmeticOperators(self): 369 | 370 | self.assertTrueRules([ 371 | r'rule test { condition: (1 + 1) * 2 == (9 - 1) \ 2 }', 372 | 'rule test { condition: 5 % 2 == 1 }', 373 | 'rule test { condition: 1.5 + 1.5 == 3}', 374 | r'rule test { condition: 3 \ 2 == 1}', 375 | r'rule test { condition: 3.0 \ 2 == 1.5}', 376 | 'rule test { condition: 1 + -1 == 0}', 377 | 'rule test { condition: -1 + -1 == -2}', 378 | 'rule test { condition: 4 --2 * 2 == 8}', 379 | 'rule test { condition: -1.0 * 1 == -1.0}', 380 | 'rule test { condition: 1-1 == 0}', 381 | 'rule test { condition: -2.0-3.0 == -5}', 382 | 'rule test { condition: --1 == 1}', 383 | 'rule test { condition: 1--1 == 2}', 384 | 'rule test { condition: -0x01 == -1}', 385 | ]) 386 | 387 | def testBitwiseOperators(self): 388 | 389 | self.assertTrueRules([ 390 | 'rule test { condition: 0x55 | 0xAA == 0xFF }', 391 | 'rule test { condition: ~0xAA ^ 0x5A & 0xFF == (~0xAA) ^ (0x5A & 0xFF) }', 392 | 'rule test { condition: ~0x55 & 0xFF == 0xAA }', 393 | 'rule test { condition: 8 >> 2 == 2 }', 394 | 'rule test { condition: 1 << 3 == 8 }', 395 | 'rule test { condition: 1 | 3 ^ 3 == 1 | (3 ^ 3) }' 396 | ]) 397 | 398 | self.assertFalseRules([ 399 | 'rule test { condition: ~0xAA ^ 0x5A & 0xFF == 0x0F }', 400 | 'rule test { condition: 1 | 3 ^ 3 == (1 | 3) ^ 3}' 401 | ]) 402 | 403 | def testSyntax(self): 404 | 405 | self.assertSyntaxError([ 406 | 'rule test { strings: $a = "a" $a = "a" condition: all of them }' 407 | ]) 408 | 409 | def testAnonymousStrings(self): 410 | 411 | self.assertTrueRules([ 412 | 'rule test { strings: $ = "a" $ = "b" condition: all of them }', 413 | ], "ab") 414 | 415 | def testStrings(self): 416 | 417 | self.assertTrueRules([ 418 | 'rule test { strings: $a = "a" condition: $a }', 419 | 'rule test { strings: $a = "ab" condition: $a }', 420 | 'rule test { strings: $a = "abc" condition: $a }', 421 | 'rule test { strings: $a = "xyz" condition: $a }', 422 | 'rule test { strings: $a = "abc" nocase fullword condition: $a }', 423 | 'rule test { strings: $a = "aBc" nocase condition: $a }', 424 | 'rule test { strings: $a = "abc" fullword condition: $a }', 425 | ], "---- abc ---- xyz") 426 | 427 | self.assertFalseRules([ 428 | 'rule test { strings: $a = "a" fullword condition: $a }', 429 | 'rule test { strings: $a = "ab" fullword condition: $a }', 430 | 'rule test { strings: $a = "abc" wide fullword condition: $a }', 431 | ], "---- abc ---- xyz") 432 | 433 | self.assertTrueRules([ 434 | 'rule test { strings: $a = "a" wide condition: $a }', 435 | 'rule test { strings: $a = "a" wide ascii condition: $a }', 436 | 'rule test { strings: $a = "ab" wide condition: $a }', 437 | 'rule test { strings: $a = "ab" wide ascii condition: $a }', 438 | 'rule test { strings: $a = "abc" wide condition: $a }', 439 | 'rule test { strings: $a = "abc" wide nocase fullword condition: $a }', 440 | 'rule test { strings: $a = "aBc" wide nocase condition: $a }', 441 | 'rule test { strings: $a = "aBc" wide ascii nocase condition: $a }', 442 | 'rule test { strings: $a = "---xyz" wide nocase condition: $a }' 443 | ], "---- a\x00b\x00c\x00 -\x00-\x00-\x00-\x00x\x00y\x00z\x00") 444 | 445 | self.assertTrueRules([ 446 | 'rule test { strings: $a = "abc" fullword condition: $a }', 447 | ], "abc") 448 | 449 | self.assertFalseRules([ 450 | 'rule test { strings: $a = "abc" fullword condition: $a }', 451 | ], "xabcx") 452 | 453 | self.assertFalseRules([ 454 | 'rule test { strings: $a = "abc" fullword condition: $a }', 455 | ], "xabc") 456 | 457 | self.assertFalseRules([ 458 | 'rule test { strings: $a = "abc" fullword condition: $a }', 459 | ], "abcx") 460 | 461 | self.assertFalseRules([ 462 | 'rule test { strings: $a = "abc" ascii wide fullword condition: $a }', 463 | ], "abcx") 464 | 465 | self.assertTrueRules([ 466 | 'rule test { strings: $a = "abc" ascii wide fullword condition: $a }', 467 | ], "a\x00abc") 468 | 469 | self.assertTrueRules([ 470 | 'rule test { strings: $a = "abc" wide fullword condition: $a }', 471 | ], "a\x00b\x00c\x00") 472 | 473 | self.assertFalseRules([ 474 | 'rule test { strings: $a = "abc" wide fullword condition: $a }', 475 | ], "x\x00a\x00b\x00c\x00x\x00") 476 | 477 | self.assertFalseRules([ 478 | 'rule test { strings: $a = "ab" wide fullword condition: $a }', 479 | ], "x\x00a\x00b\x00") 480 | 481 | self.assertFalseRules([ 482 | 'rule test { strings: $a = "abc" wide fullword condition: $a }', 483 | ], "x\x00a\x00b\x00c\x00") 484 | 485 | self.assertTrueRules([ 486 | 'rule test { strings: $a = "abc" wide fullword condition: $a }', 487 | ], "x\x01a\x00b\x00c\x00") 488 | 489 | self.assertTrueRules([ 490 | 'rule test {\ 491 | strings:\ 492 | $a = "abcdef"\ 493 | $b = "cdef"\ 494 | $c = "ef"\ 495 | condition:\ 496 | all of them\ 497 | }' 498 | ], 'abcdef') 499 | 500 | def testWildcardStrings(self): 501 | 502 | self.assertTrueRules([ 503 | 'rule test {\ 504 | strings:\ 505 | $s1 = "abc"\ 506 | $s2 = "xyz"\ 507 | condition:\ 508 | for all of ($*) : ($)\ 509 | }' 510 | ], "---- abc ---- A\x00B\x00C\x00 ---- xyz") 511 | 512 | def testHexStrings(self): 513 | 514 | self.assertTrueRules([ 515 | 'rule test { strings: $a = { 64 01 00 00 60 01 } condition: $a }', 516 | 'rule test { strings: $a = { 64 0? 00 00 ?0 01 } condition: $a }', 517 | 'rule test { strings: $a = { 6? 01 00 00 60 0? } condition: $a }', 518 | 'rule test { strings: $a = { 64 01 [1-3] 60 01 } condition: $a }', 519 | 'rule test { strings: $a = { 64 01 [1-3] (60|61) 01 } condition: $a }', 520 | 'rule test { strings: $a = { 4D 5A [-] 6A 2A [-] 58 C3} condition: $a }', 521 | 'rule test { strings: $a = { 4D 5A [300-] 6A 2A [-] 58 C3} condition: $a }', 522 | 'rule test { strings: $a = { 2e 7? (65 | ??) 78 } condition: $a }' 523 | ], PE32_FILE) 524 | 525 | self.assertFalseRules([ 526 | 'rule test { strings: $a = { 4D 5A [0-300] 6A 2A } condition: $a }', 527 | 'rule test { strings: $a = { 4D 5A [0-128] 45 [0-128] 01 [0-128] C3 } condition: $a }', 528 | ], PE32_FILE) 529 | 530 | self.assertTrueRules([ 531 | 'rule test { strings: $a = { 31 32 [-] 38 39 } condition: $a }', 532 | 'rule test { strings: $a = { 31 32 [-] 33 34 [-] 38 39 } condition: $a }', 533 | 'rule test { strings: $a = { 31 32 [1] 34 35 [2] 38 39 } condition: $a }', 534 | 'rule test { strings: $a = { 31 32 [1-] 34 35 [1-] 38 39 } condition: $a }', 535 | 'rule test { strings: $a = { 31 32 [0-3] 34 35 [1-] 38 39 } condition: $a }', 536 | 'rule test { strings: $a = { 31 32 [0-2] 35 [1-] 37 38 39 } condition: $a }', 537 | ], '123456789') 538 | 539 | self.assertTrueRules([ 540 | 'rule test { strings: $a = { 31 32 [-] 38 39 } condition: all of them }', 541 | ], '123456789') 542 | 543 | self.assertFalseRules([ 544 | 'rule test { strings: $a = { 31 32 [-] 32 33 } condition: $a }', 545 | 'rule test { strings: $a = { 35 36 [-] 31 32 } condition: $a }', 546 | 'rule test { strings: $a = { 31 32 [2-] 34 35 } condition: $a }', 547 | 'rule test { strings: $a = { 31 32 [0-3] 37 38 } condition: $a }', 548 | ], '123456789') 549 | 550 | self.assertSyntaxError([ 551 | 'rule test { strings: $a = { 01 [0] 02 } condition: $a }', 552 | 'rule test { strings: $a = { [-] 01 02 } condition: $a }', 553 | 'rule test { strings: $a = { 01 02 [-] } condition: $a }', 554 | 'rule test { strings: $a = { 01 02 ([-] 03 | 04) } condition: $a }', 555 | 'rule test { strings: $a = { 01 02 (03 [-] | 04) } condition: $a }', 556 | 'rule test { strings: $a = { 01 02 (03 | 04 [-]) } condition: $a }' 557 | ]) 558 | 559 | rules = yara.compile(source='rule test { strings: $a = { 61 [0-3] (62|63) } condition: $a }') 560 | matches = rules.match(data='abbb') 561 | 562 | if sys.version_info[0] >= 3: 563 | self.assertTrue(matches[0].strings[0].identifier == '$a') 564 | self.assertTrue(matches[0].strings[0].instances[0].offset == 0) 565 | self.assertTrue(matches[0].strings[0].instances[0].matched_data == bytes('ab', 'utf-8')) 566 | else: 567 | self.assertTrue(matches[0].strings[0].identifier == '$a') 568 | self.assertTrue(matches[0].strings[0].instances[0].offset == 0) 569 | self.assertTrue(matches[0].strings[0].instances[0].matched_data == 'ab') 570 | 571 | def testCount(self): 572 | 573 | self.assertTrueRules([ 574 | 'rule test { strings: $a = "ssi" condition: #a == 2 }', 575 | ], 'mississippi') 576 | 577 | def testAt(self): 578 | 579 | self.assertTrueRules([ 580 | 'rule test { strings: $a = "ssi" condition: $a at 2 and $a at 5 }', 581 | 'rule test { strings: $a = "mis" condition: $a at ~0xFF & 0xFF }' 582 | ], 'mississippi') 583 | 584 | self.assertTrueRules([ 585 | 'rule test { strings: $a = { 00 00 00 00 ?? 74 65 78 74 } condition: $a at 308}', 586 | ], PE32_FILE) 587 | 588 | def testIn(self): 589 | 590 | self.assertTrueRules([ 591 | 'import "pe" rule test { strings: $a = { 6a 2a 58 c3 } condition: $a in (pe.entry_point .. pe.entry_point + 1) }', 592 | ], PE32_FILE) 593 | 594 | def testOffset(self): 595 | 596 | self.assertTrueRules([ 597 | 'rule test { strings: $a = "ssi" condition: @a == 2 }', 598 | 'rule test { strings: $a = "ssi" condition: @a == @a[1] }', 599 | 'rule test { strings: $a = "ssi" condition: @a[2] == 5 }' 600 | ], 'mississippi') 601 | 602 | def testLength(self): 603 | 604 | self.assertTrueRules([ 605 | 'rule test { strings: $a = /m.*?ssi/ condition: !a == 5 }', 606 | 'rule test { strings: $a = /m.*?ssi/ condition: !a[1] == 5 }', 607 | 'rule test { strings: $a = /m.*ssi/ condition: !a == 8 }', 608 | 'rule test { strings: $a = /m.*ssi/ condition: !a[1] == 8 }', 609 | 'rule test { strings: $a = /ssi.*ppi/ condition: !a[1] == 9 }', 610 | 'rule test { strings: $a = /ssi.*ppi/ condition: !a[2] == 6 }', 611 | 'rule test { strings: $a = { 6D [1-3] 73 73 69 } condition: !a == 5}', 612 | 'rule test { strings: $a = { 6D [-] 73 73 69 } condition: !a == 5}', 613 | 'rule test { strings: $a = { 6D [-] 70 70 69 } condition: !a == 11}', 614 | 'rule test { strings: $a = { 6D 69 73 73 [-] 70 69 } condition: !a == 11}', 615 | ], 'mississippi') 616 | 617 | def testOf(self): 618 | 619 | self.assertTrueRules([ 620 | 'rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: any of them }', 621 | 'rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: 1 of them }', 622 | 'rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: 2 of them }', 623 | 'rule test { strings: $a1 = "dummy1" $b1 = "dummy1" $b2 = "ssi" condition: any of ($a*, $b*) }', 624 | ], 'mississipi') 625 | 626 | self.assertTrueRules([""" 627 | rule test 628 | { 629 | strings: 630 | $ = /abc/ 631 | $ = /def/ 632 | $ = /ghi/ 633 | condition: 634 | for any of ($*) : ( for any i in (1..#): (uint8(@[i] - 1) == 0x00) ) 635 | }""" 636 | ], 'abc\x00def\x00ghi') 637 | 638 | self.assertFalseRules([ 639 | 'rule test { strings: $a = "ssi" $b = "mis" $c = "oops" condition: all of them }' 640 | ], 'mississipi') 641 | 642 | self.assertSyntaxError([ 643 | 'rule test { condition: all of ($a*) }', 644 | 'rule test { condition: all of them }' 645 | ]) 646 | 647 | def testFor(self): 648 | 649 | self.assertTrueRules([ 650 | 'rule test { strings: $a = "ssi" condition: for all i in (1..#a) : (@a[i] >= 2 and @a[i] <= 5) }', 651 | 'rule test { strings: $a = "ssi" $b = "mi" condition: for all i in (1..#a) : ( for all j in (1..#b) : (@a[i] >= @b[j])) }' 652 | ], 'mississipi') 653 | 654 | self.assertFalseRules([ 655 | 'rule test { strings: $a = "ssi" condition: for all i in (1..#a) : (@a[i] == 5) }', 656 | ], 'mississipi') 657 | 658 | def testXorKey(self): 659 | 660 | global rule_data 661 | rule_data = None 662 | 663 | def callback(data): 664 | global rule_data 665 | rule_data = data 666 | return yara.CALLBACK_CONTINUE 667 | 668 | r = yara.compile(source='rule test { strings: $a = "dummy" xor(1-2) condition: $a }') 669 | r.match(data='etllxfwoo{', callback=callback) 670 | 671 | self.assertTrue(rule_data['matches']) 672 | self.assertEqual(rule_data['rule'], 'test') 673 | self.assertEqual(len(rule_data['strings']), 1) 674 | string = rule_data['strings'][0] 675 | self.assertEqual(len(string.instances), 2) 676 | self.assertEqual(string.instances[0].xor_key, 1) 677 | self.assertEqual(string.instances[1].xor_key, 2) 678 | 679 | # Make sure plaintext() works. 680 | self.assertTrue(string.instances[0].plaintext() == b'dummy') 681 | 682 | # Test that the xor_key for matched strings is 0 if the string is not an xor 683 | # string. We always want to make sure this is set! 684 | def testXorKeyNoXorString(self): 685 | 686 | global rule_data 687 | rule_data = None 688 | 689 | def callback(data): 690 | global rule_data 691 | rule_data = data 692 | return yara.CALLBACK_CONTINUE 693 | 694 | r = yara.compile(source='rule test { strings: $a = "dummy" condition: $a }') 695 | r.match(data='dummy', callback=callback) 696 | 697 | self.assertTrue(rule_data['matches']) 698 | self.assertEqual(rule_data['rule'],'test') 699 | self.assertEqual(len(rule_data['strings']), 1) 700 | self.assertEqual(rule_data['strings'][0].instances[0].xor_key, 0) 701 | 702 | def testMatchedLength(self): 703 | yara.set_config(max_match_data=2) 704 | r = yara.compile(source='rule test { strings: $a = "dummy" condition: $a }') 705 | matches = r.match(data='dummy') 706 | self.assertEqual(matches[0].strings[0].instances[0].matched_length, 5) 707 | self.assertEqual(matches[0].strings[0].instances[0].matched_data, b'du') 708 | yara.set_config(max_match_data=512) 709 | 710 | def testRE(self): 711 | 712 | self.assertTrueRules([ 713 | 'rule test { strings: $a = /ssi/ condition: $a }', 714 | 'rule test { strings: $a = /ssi(s|p)/ condition: $a }', 715 | 'rule test { strings: $a = /ssim*/ condition: $a }', 716 | 'rule test { strings: $a = /ssa?/ condition: $a }', 717 | 'rule test { strings: $a = /Miss/ nocase condition: $a }', 718 | 'rule test { strings: $a = /(M|N)iss/ nocase condition: $a }', 719 | 'rule test { strings: $a = /[M-N]iss/ nocase condition: $a }', 720 | 'rule test { strings: $a = /(Mi|ssi)ssippi/ nocase condition: $a }', 721 | r'rule test { strings: $a = /ppi\tmi/ condition: $a }', 722 | r'rule test { strings: $a = /ppi\.mi/ condition: $a }', 723 | 'rule test { strings: $a = /^mississippi/ fullword condition: $a }', 724 | 'rule test { strings: $a = /mississippi.*mississippi$/s condition: $a }', 725 | ], 'mississippi\tmississippi.mississippi\nmississippi') 726 | 727 | self.assertFalseRules([ 728 | 'rule test { strings: $a = /^ssi/ condition: $a }', 729 | 'rule test { strings: $a = /ssi$/ condition: $a }', 730 | 'rule test { strings: $a = /ssissi/ fullword condition: $a }', 731 | 'rule test { strings: $a = /^[isp]+/ condition: $a }' 732 | ], 'mississippi') 733 | 734 | for test in RE_TESTS: 735 | try: 736 | self.runReTest(test) 737 | except Exception as e: 738 | print('\nFailed test: %s\n' % str(test)) 739 | raise e 740 | 741 | def testEntrypoint(self): 742 | 743 | self.assertTrueRules([ 744 | 'rule test { strings: $a = { 6a 2a 58 c3 } condition: $a at entrypoint }', 745 | ], PE32_FILE) 746 | 747 | self.assertTrueRules([ 748 | 'rule test { strings: $a = { b8 01 00 00 00 bb 2a } condition: $a at entrypoint }', 749 | ], ELF32_FILE) 750 | 751 | self.assertTrueRules([ 752 | 'rule test { strings: $a = { b8 01 00 00 00 bb 2a } condition: $a at entrypoint }', 753 | ], ELF64_FILE) 754 | 755 | self.assertFalseRules([ 756 | 'rule test { condition: entrypoint >= 0 }', 757 | ]) 758 | 759 | # This test ensures that anything after the NULL character is stripped. 760 | def testMetaNull(self): 761 | 762 | r = yara.compile(source=r'rule test { meta: a = "foo\x00bar\x80" condition: true }') 763 | self.assertTrue((list(r)[0].meta['a']) == 'foo') 764 | 765 | def testMeta(self): 766 | 767 | r = yara.compile(source=r""" 768 | rule test { 769 | meta: 770 | a = "foo\x80bar" 771 | b = "ñ" 772 | c = "\xc3\xb1" 773 | condition: 774 | true } 775 | """) 776 | 777 | meta = list(r)[0].meta 778 | 779 | if sys.version_info > (3, 0): 780 | self.assertTrue(meta['a'] == 'foobar') 781 | else: 782 | self.assertTrue(meta['a'] == 'foo\x80bar') 783 | 784 | self.assertTrue(meta['b'] == 'ñ') 785 | self.assertTrue(meta['c'] == 'ñ') 786 | 787 | # This test is similar to testMeta but it tests the meta data generated 788 | # when a Match object is created. 789 | def testScanMeta(self): 790 | 791 | r = yara.compile(source=r""" 792 | rule test { 793 | meta: 794 | a = "foo\x80bar" 795 | b = "ñ" 796 | c = "\xc3\xb1" 797 | condition: 798 | true } 799 | """) 800 | 801 | m = r.match(data='dummy') 802 | meta = list(m)[0].meta 803 | 804 | if sys.version_info > (3, 0): 805 | self.assertTrue(meta['a'] == 'foobar') 806 | else: 807 | self.assertTrue(meta['a'] == 'foo\x80bar') 808 | 809 | self.assertTrue(meta['b'] == 'ñ') 810 | self.assertTrue(meta['c'] == 'ñ') 811 | 812 | # This test is similar to testScanMeta but it tests for displaying multiple values in the meta data generated 813 | # when a Match object is created (upon request). 814 | def testDuplicateMeta(self): 815 | r = yara.compile(source=""" 816 | rule test { 817 | meta: 818 | a = 1 819 | a = 2 820 | b = 3 821 | condition: 822 | true 823 | } 824 | """) 825 | 826 | # Default behaviour should produce a simple KV map and should use the 'latest' metadata value per field 827 | meta = r.match(data="dummy")[0].meta 828 | self.assertTrue(meta['a'] == 2 and meta['b'] == 3) 829 | 830 | # `allow_duplicate_metadata` flag should reveal all metadata values per field as a list 831 | meta = r.match(data="dummy", allow_duplicate_metadata=True)[0].meta 832 | self.assertTrue(meta['a'] == [1, 2] and meta['b'] == [3]) 833 | 834 | def testFilesize(self): 835 | 836 | self.assertTrueRules([ 837 | 'rule test { condition: filesize == %d }' % len(PE32_FILE), 838 | ], PE32_FILE) 839 | 840 | def testTooManyArguments(self): 841 | 842 | self.assertRaises(TypeError, yara.compile, 'rules1.yar', 'rules2.yar') 843 | 844 | def testCompileFile(self): 845 | 846 | f = tempfile.TemporaryFile('wt') 847 | 848 | f.write('rule test { condition: true }') 849 | f.flush() 850 | f.seek(0) 851 | 852 | r = yara.compile(file=f) 853 | f.close() 854 | self.assertTrue(r.match(data=PE32_FILE)) 855 | 856 | def testCompileFiles(self): 857 | 858 | tmpdir = tempfile.gettempdir() 859 | 860 | p1 = os.path.join(tmpdir, 'test1') 861 | f1 = open(p1, 'wt') 862 | f1.write('rule test1 { condition: true }') 863 | f1.close() 864 | 865 | p2 = os.path.join(tmpdir, 'test2') 866 | t2 = open(p2, 'wt') 867 | t2.write('rule test2 { condition: true }') 868 | t2.close() 869 | 870 | r = yara.compile(filepaths={ 871 | 'test1': p1, 872 | 'test2': p2 873 | }) 874 | 875 | self.assertTrue(len(r.match(data='dummy')) == 2) 876 | 877 | for m in r.match(data='dummy'): 878 | self.assertTrue(m.rule in ('test1', 'test2')) 879 | self.assertTrue(m.namespace == m.rule) 880 | 881 | os.remove(p1) 882 | os.remove(p2) 883 | 884 | def testIncludeFiles(self): 885 | 886 | tmpdir = tempfile.gettempdir() 887 | 888 | p1 = os.path.join(tmpdir, 'test1') 889 | f1 = open(p1, 'wt') 890 | f1.write('rule test1 { condition: true }') 891 | f1.close() 892 | 893 | p2 = os.path.join(tmpdir, 'test2') 894 | f2 = open(p2, 'wt') 895 | f2.write('include "%s" rule test2 { condition: test1 }' % p1) 896 | f2.close() 897 | 898 | r = yara.compile(p2) 899 | self.assertTrue(len(r.match(data='dummy')) == 2) 900 | 901 | self.assertRaises(yara.SyntaxError, yara.compile, source='include "test2"', includes=False) 902 | 903 | def testExternals(self): 904 | 905 | r = yara.compile(source='rule test { condition: ext_int == 15 }', externals={'ext_int': 15}) 906 | self.assertTrue(r.match(data='dummy')) 907 | 908 | r = yara.compile(source='rule test { condition: ext_int == -15}', externals={'ext_int': -15}) 909 | self.assertTrue(r.match(data='dummy')) 910 | 911 | r = yara.compile(source='rule test { condition: ext_float == 3.14 }', externals={'ext_float': 3.14}) 912 | self.assertTrue(r.match(data='dummy')) 913 | 914 | r = yara.compile(source='rule test { condition: ext_float == -0.5 }', externals={'ext_float': -0.5}) 915 | self.assertTrue(r.match(data='dummy')) 916 | 917 | r = yara.compile(source='rule test { condition: ext_bool }', externals={'ext_bool': True}) 918 | self.assertTrue(r.match(data='dummy')) 919 | 920 | r = yara.compile(source='rule test { condition: ext_str }', externals={'ext_str': ''}) 921 | self.assertFalse(r.match(data='dummy')) 922 | 923 | r = yara.compile(source='rule test { condition: ext_str }', externals={'ext_str': 'foo'}) 924 | self.assertTrue(r.match(data='dummy')) 925 | 926 | r = yara.compile(source='rule test { condition: ext_bool }', externals={'ext_bool': False}) 927 | self.assertFalse(r.match(data='dummy')) 928 | 929 | r = yara.compile(source='rule test { condition: ext_str contains "ssi" }', externals={'ext_str': 'mississippi'}) 930 | self.assertTrue(r.match(data='dummy')) 931 | 932 | r = yara.compile(source='rule test { condition: ext_str matches /foo/ }', externals={'ext_str': ''}) 933 | self.assertFalse(r.match(data='dummy')) 934 | 935 | r = yara.compile(source='rule test { condition: ext_str matches /foo/ }', externals={'ext_str': 'FOO'}) 936 | self.assertFalse(r.match(data='dummy')) 937 | 938 | r = yara.compile(source='rule test { condition: ext_str matches /foo/i }', externals={'ext_str': 'FOO'}) 939 | self.assertTrue(r.match(data='dummy')) 940 | 941 | r = yara.compile(source='rule test { condition: ext_str matches /ssi(s|p)/ }', externals={'ext_str': 'mississippi'}) 942 | self.assertTrue(r.match(data='dummy')) 943 | 944 | r = yara.compile(source='rule test { condition: ext_str matches /ppi$/ }', externals={'ext_str': 'mississippi'}) 945 | self.assertTrue(r.match(data='dummy')) 946 | 947 | r = yara.compile(source='rule test { condition: ext_str matches /ssi$/ }', externals={'ext_str': 'mississippi'}) 948 | self.assertFalse(r.match(data='dummy')) 949 | 950 | r = yara.compile(source='rule test { condition: ext_str matches /^miss/ }', externals={'ext_str': 'mississippi'}) 951 | self.assertTrue(r.match(data='dummy')) 952 | 953 | r = yara.compile(source='rule test { condition: ext_str matches /^iss/ }', externals={'ext_str': 'mississippi'}) 954 | self.assertFalse(r.match(data='dummy')) 955 | 956 | r = yara.compile(source='rule test { condition: ext_str matches /ssi$/ }', externals={'ext_str': 'mississippi'}) 957 | self.assertFalse(r.match(data='dummy')) 958 | 959 | if sys.version_info[0] >= 3: 960 | self.assertTrue(yara.compile( 961 | source="rule test { condition: true}", 962 | externals={'foo': u'\u6765\u6613\u7f51\u7edc\u79d1'})) 963 | else: 964 | self.assertRaises(UnicodeEncodeError, yara.compile, 965 | source="rule test { condition: true}", 966 | externals={'foo': u'\u6765\u6613\u7f51\u7edc\u79d1'}) 967 | 968 | def testCallbackAll(self): 969 | global rule_data 970 | rule_data = [] 971 | 972 | def callback(data): 973 | global rule_data 974 | rule_data.append(data) 975 | return yara.CALLBACK_CONTINUE 976 | 977 | r = yara.compile(source='rule t { condition: true } rule f { condition: false }') 978 | r.match(data='dummy', callback=callback, which_callbacks=yara.CALLBACK_ALL) 979 | 980 | self.assertTrue(len(rule_data) == 2) 981 | 982 | def testCallback(self): 983 | 984 | global rule_data 985 | rule_data = None 986 | 987 | def callback(data): 988 | global rule_data 989 | rule_data = data 990 | return yara.CALLBACK_CONTINUE 991 | 992 | r = yara.compile(source='rule test { strings: $a = { 50 45 00 00 4c 01 } condition: $a }') 993 | r.match(data=PE32_FILE, callback=callback) 994 | 995 | self.assertTrue(rule_data['matches']) 996 | self.assertTrue(rule_data['rule'] == 'test') 997 | 998 | rule_data = None 999 | 1000 | r = yara.compile(source='rule test { condition: false }') 1001 | r.match(data='dummy', callback=callback, which_callbacks=yara.CALLBACK_NON_MATCHES) 1002 | 1003 | self.assertTrue(rule_data['rule'] == 'test') 1004 | 1005 | rule_data = None 1006 | 1007 | r = yara.compile(source='rule test { condition: true }') 1008 | r.match(data='dummy', callback=callback, which_callbacks=yara.CALLBACK_MATCHES) 1009 | 1010 | self.assertTrue(rule_data['rule'] == 'test') 1011 | 1012 | def testIncludeCallback(self): 1013 | 1014 | def callback(requested_filename, filename, namespace): 1015 | if requested_filename == 'foo': 1016 | return 'rule included {condition: true }' 1017 | return None 1018 | 1019 | r = yara.compile(source='include "foo" rule r { condition: included }', include_callback=callback) 1020 | self.assertTrue(r.match(data='dummy')) 1021 | 1022 | def testConsoleCallback(self): 1023 | global called 1024 | called = False 1025 | 1026 | def callback(message): 1027 | global called 1028 | called = True 1029 | return yara.CALLBACK_CONTINUE 1030 | 1031 | r = yara.compile(source='import "console" rule r { condition: console.log("AXSERS") }') 1032 | r.match(data='dummy', console_callback=callback) 1033 | self.assertTrue(called) 1034 | 1035 | def testCompare(self): 1036 | 1037 | r = yara.compile(sources={ 1038 | 'test1': 'rule test { condition: true}', 1039 | 'test2': 'rule test { condition: true}' 1040 | }) 1041 | 1042 | m = r.match(data="dummy") 1043 | 1044 | self.assertTrue(len(m) == 2) 1045 | 1046 | if sys.version_info[0] < 3: 1047 | self.assertTrue(m[0] < m[1]) 1048 | self.assertTrue(m[0] != m[1]) 1049 | self.assertFalse(m[0] > m[1]) 1050 | self.assertFalse(m[0] == m[1]) 1051 | 1052 | def testComments(self): 1053 | 1054 | self.assertTrueRules([ 1055 | """ 1056 | rule test { 1057 | condition: 1058 | // this is a comment 1059 | /*** this is a comment ***/ 1060 | /* /* /* 1061 | this is a comment 1062 | */ 1063 | true 1064 | } 1065 | """, 1066 | ]) 1067 | 1068 | def testModules(self): 1069 | 1070 | self.assertTrueRules([ 1071 | 'import "tests" rule test { condition: tests.constants.one + 1 == tests.constants.two }', 1072 | 'import "tests" rule test { condition: tests.constants.foo == "foo" }', 1073 | 'import "tests" rule test { condition: tests.constants.empty == "" }', 1074 | 'import "tests" rule test { condition: tests.empty() == "" }', 1075 | 'import "tests" rule test { condition: tests.struct_array[1].i == 1 }', 1076 | 'import "tests" rule test { condition: tests.struct_array[0].i == 1 or true}', 1077 | 'import "tests" rule test { condition: tests.integer_array[0] == 0}', 1078 | 'import "tests" rule test { condition: tests.integer_array[1] == 1}', 1079 | 'import "tests" rule test { condition: tests.string_array[0] == "foo"}', 1080 | 'import "tests" rule test { condition: tests.string_array[2] == "baz"}', 1081 | 'import "tests" rule test { condition: tests.string_dict["foo"] == "foo"}', 1082 | 'import "tests" rule test { condition: tests.string_dict["bar"] == "bar"}', 1083 | 'import "tests" rule test { condition: tests.isum(1,2) == 3}', 1084 | 'import "tests" rule test { condition: tests.isum(1,2,3) == 6}', 1085 | 'import "tests" rule test { condition: tests.fsum(1.0,2.0) == 3.0}', 1086 | 'import "tests" rule test { condition: tests.fsum(1.0,2.0,3.0) == 6.0}', 1087 | 'import "tests" rule test { condition: tests.length("dummy") == 5}', 1088 | ]) 1089 | 1090 | self.assertFalseRules([ 1091 | 'import "tests" rule test { condition: tests.struct_array[0].i == 1 }', 1092 | 'import "tests" rule test { condition: tests.isum(1,1) == 3}', 1093 | 'import "tests" rule test { condition: tests.fsum(1.0,1.0) == 3.0}', 1094 | ]) 1095 | 1096 | def testIntegerFunctions(self): 1097 | 1098 | self.assertTrueRules([ 1099 | 'rule test { condition: uint8(0) == 0xAA}', 1100 | 'rule test { condition: uint16(0) == 0xBBAA}', 1101 | 'rule test { condition: uint32(0) == 0xDDCCBBAA}', 1102 | 'rule test { condition: uint8be(0) == 0xAA}', 1103 | 'rule test { condition: uint16be(0) == 0xAABB}', 1104 | 'rule test { condition: uint32be(0) == 0xAABBCCDD}', 1105 | ], b'\xAA\xBB\xCC\xDD') 1106 | 1107 | def testStringIO(self): 1108 | 1109 | # Python 2/3 1110 | try: 1111 | stream = StringIO.StringIO() 1112 | except: 1113 | stream = io.BytesIO() 1114 | 1115 | r1 = yara.compile(source='rule test { condition: true }') 1116 | r1.save(file=stream) 1117 | 1118 | stream.seek(0) 1119 | 1120 | r2 = yara.load(file=stream) 1121 | m = r2.match(data="dummy") 1122 | 1123 | self.assertTrue(len(m) == 1) 1124 | 1125 | def testModuleData(self): 1126 | 1127 | data = {} 1128 | 1129 | def callback(module_data): 1130 | data['constants'] = module_data.get('constants') 1131 | 1132 | r1 = yara.compile( 1133 | source='import "tests" rule test { condition: false }') 1134 | 1135 | r1.match(data='', modules_callback=callback) 1136 | 1137 | if sys.version_info[0] >= 3: 1138 | self.assertTrue(data['constants']['foo'] == bytes('foo', 'utf-8')) 1139 | self.assertTrue(data['constants']['empty'] == bytes('', 'utf-8')) 1140 | else: 1141 | self.assertTrue(data['constants']['foo'] == 'foo') 1142 | self.assertTrue(data['constants']['empty'] == '') 1143 | 1144 | self.assertTrue(data['constants']['one'] == 1) 1145 | self.assertTrue(data['constants']['two'] == 2) 1146 | 1147 | def testRulesIterator(self): 1148 | 1149 | rules = yara.compile( 1150 | source=''' 1151 | rule test1 { condition: false } 1152 | rule test2 { condition: false } 1153 | rule test3 { condition: false } 1154 | ''') 1155 | 1156 | for i, r in enumerate(rules, start=1): 1157 | self.assertTrue(r.identifier == 'test%d' % i) 1158 | 1159 | it = iter(rules) 1160 | r = next(it) 1161 | self.assertTrue(r.identifier == 'test1') 1162 | r = next(it) 1163 | self.assertTrue(r.identifier == 'test2') 1164 | r = next(it) 1165 | self.assertTrue(r.identifier == 'test3') 1166 | 1167 | def testSetConfig(self): 1168 | 1169 | yara.set_config(max_strings_per_rule=1) 1170 | 1171 | self.assertSyntaxError([''' 1172 | rule test { strings: $a = "1" $b = "2" condition: all of them } 1173 | ''']) 1174 | 1175 | yara.set_config(max_strings_per_rule=10000) 1176 | 1177 | def testGlobalPrivate(self): 1178 | 1179 | rules = """ 1180 | global rule a { condition: true } 1181 | private rule b { condition: true } 1182 | """ 1183 | 1184 | # Have to convert to a list because Rules are not subscriptable, yet... 1185 | r = list(yara.compile(source=rules)) 1186 | self.assertTrue(r[0].is_global == True) 1187 | self.assertTrue(r[1].is_private == True) 1188 | 1189 | def testMatchMemoryview(self): 1190 | 1191 | r = yara.compile(source='rule test { strings: $s = "test" condition: $s }') 1192 | data = memoryview(b"test") 1193 | 1194 | self.assertTrue(r.match(data=data)) 1195 | 1196 | def testWarningCallback(self): 1197 | global warnings_callback_called, warnings_callback_message 1198 | 1199 | warnings_callback_called = False 1200 | warnings_callback_message = None 1201 | 1202 | r = yara.compile(sources={'ns1': 'rule x { strings: $x = "X" condition: $x }'}) 1203 | data = memoryview(b"X" * 1000099) 1204 | r.match(data=data, warnings_callback=warnings_callback) 1205 | 1206 | self.assertTrue(warnings_callback_called == yara.CALLBACK_TOO_MANY_MATCHES) 1207 | 1208 | self.assertTrue(warnings_callback_message == ("ns1", "x", "$x")) 1209 | 1210 | self.assertTrue(warnings_callback_message.namespace == "ns1") 1211 | self.assertTrue(warnings_callback_message.rule == "x") 1212 | self.assertTrue(warnings_callback_message.string == "$x") 1213 | 1214 | def testCompilerErrorOnWarning(self): 1215 | # Make sure we always throw on warnings if requested, and that warnings 1216 | # are accumulated. 1217 | 1218 | rules = """ 1219 | rule a { strings: $a = "A" condition: $a } 1220 | rule b { strings: $b = "B" condition: $b } 1221 | """ 1222 | 1223 | expected = [ 1224 | 'line 2: string "$a" may slow down scanning', 1225 | 'line 3: string "$b" may slow down scanning', 1226 | ] 1227 | 1228 | with self.assertRaises(yara.WarningError) as ctx: 1229 | yara.compile(source=rules, error_on_warning=True) 1230 | 1231 | e = ctx.exception 1232 | self.assertListEqual(e.warnings, expected) 1233 | 1234 | # Now make sure the warnings member is set if error_on_warning is not 1235 | # set. 1236 | rules = yara.compile(source=rules) 1237 | self.assertListEqual(rules.warnings, expected) 1238 | 1239 | 1240 | if __name__ == "__main__": 1241 | unittest.main() 1242 | -------------------------------------------------------------------------------- /yara-python.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2007-2022. The YARA Authors. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | /* headers */ 17 | 18 | #define PY_SSIZE_T_CLEAN 19 | 20 | #include 21 | #include "structmember.h" 22 | 23 | #if PY_VERSION_HEX >= 0x02060000 24 | #include "bytesobject.h" 25 | #include "structseq.h" 26 | #elif PY_VERSION_HEX < 0x02060000 27 | #define PyBytes_AsString PyString_AsString 28 | #define PyBytes_Check PyString_Check 29 | #define PyBytes_FromStringAndSize PyString_FromStringAndSize 30 | #endif 31 | 32 | #include 33 | #include 34 | 35 | #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) 36 | typedef int Py_ssize_t; 37 | #define PY_SSIZE_T_MAX INT_MAX 38 | #define PY_SSIZE_T_MIN INT_MIN 39 | #endif 40 | 41 | #ifndef PyVarObject_HEAD_INIT 42 | #define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, 43 | #endif 44 | 45 | #if PY_VERSION_HEX < 0x03020000 46 | typedef long Py_hash_t; 47 | #endif 48 | 49 | #if PY_MAJOR_VERSION >= 3 50 | #define PY_STRING(x) PyUnicode_DecodeUTF8(x, strlen(x), "ignore" ) 51 | #define PY_STRING_FORMAT(...) PyUnicode_FromFormat(__VA_ARGS__) 52 | #define PY_STRING_TO_C(x) PyUnicode_AsUTF8(x) 53 | #define PY_STRING_CHECK(x) PyUnicode_Check(x) 54 | #else 55 | #define PY_STRING(x) PyString_FromString(x) 56 | #define PY_STRING_FORMAT(...) PyString_FromFormat(__VA_ARGS__) 57 | #define PY_STRING_TO_C(x) PyString_AsString(x) 58 | #define PY_STRING_CHECK(x) (PyString_Check(x) || PyUnicode_Check(x)) 59 | #endif 60 | 61 | #if PY_VERSION_HEX < 0x03020000 62 | #define PyDescr_NAME(x) (((PyDescrObject*)x)->d_name) 63 | #endif 64 | 65 | /* Module globals */ 66 | 67 | static PyObject* YaraError = NULL; 68 | static PyObject* YaraSyntaxError = NULL; 69 | static PyObject* YaraTimeoutError = NULL; 70 | static PyObject* YaraWarningError = NULL; 71 | 72 | 73 | #define YARA_DOC "\ 74 | This module allows you to apply YARA rules to files or strings.\n\ 75 | \n\ 76 | For complete documentation please visit:\n\ 77 | https://yara.readthedocs.io/en/stable/yarapython.html\n" 78 | 79 | #if defined(_WIN32) || defined(__CYGWIN__) 80 | #include 81 | #define strdup _strdup 82 | #endif 83 | 84 | // Match object 85 | 86 | typedef struct 87 | { 88 | PyObject_HEAD 89 | PyObject* rule; 90 | PyObject* ns; 91 | PyObject* tags; 92 | PyObject* meta; 93 | PyObject* strings; 94 | 95 | } Match; 96 | 97 | static PyMemberDef Match_members[] = { 98 | { 99 | "rule", 100 | T_OBJECT_EX, 101 | offsetof(Match, rule), 102 | READONLY, 103 | "Name of the matching rule" 104 | }, 105 | { 106 | "namespace", 107 | T_OBJECT_EX, 108 | offsetof(Match, ns), 109 | READONLY, 110 | "Namespace of the matching rule" 111 | }, 112 | { 113 | "tags", 114 | T_OBJECT_EX, 115 | offsetof(Match, tags), 116 | READONLY, 117 | "List of tags associated to the rule" 118 | }, 119 | { 120 | "meta", 121 | T_OBJECT_EX, 122 | offsetof(Match, meta), 123 | READONLY, 124 | "Dictionary with metadata associated to the rule" 125 | }, 126 | { 127 | "strings", 128 | T_OBJECT_EX, 129 | offsetof(Match, strings), 130 | READONLY, 131 | "Tuple with offsets and strings that matched the file" 132 | }, 133 | { NULL } // End marker 134 | }; 135 | 136 | static PyObject* Match_NEW( 137 | const char* rule, 138 | const char* ns, 139 | PyObject* tags, 140 | PyObject* meta, 141 | PyObject* strings); 142 | 143 | static void Match_dealloc( 144 | PyObject* self); 145 | 146 | static PyObject* Match_repr( 147 | PyObject* self); 148 | 149 | static PyObject* Match_getattro( 150 | PyObject* self, 151 | PyObject* name); 152 | 153 | static PyObject* Match_richcompare( 154 | PyObject* self, 155 | PyObject* other, 156 | int op); 157 | 158 | static Py_hash_t Match_hash( 159 | PyObject* self); 160 | 161 | 162 | static PyMethodDef Match_methods[] = 163 | { 164 | { NULL }, 165 | }; 166 | 167 | static PyTypeObject Match_Type = { 168 | PyVarObject_HEAD_INIT(NULL, 0) 169 | "yara.Match", /*tp_name*/ 170 | sizeof(Match), /*tp_basicsize*/ 171 | 0, /*tp_itemsize*/ 172 | (destructor)Match_dealloc, /*tp_dealloc*/ 173 | 0, /*tp_print*/ 174 | 0, /*tp_getattr*/ 175 | 0, /*tp_setattr*/ 176 | 0, /*tp_compare*/ 177 | Match_repr, /*tp_repr*/ 178 | 0, /*tp_as_number*/ 179 | 0, /*tp_as_sequence*/ 180 | 0, /*tp_as_mapping*/ 181 | Match_hash, /*tp_hash */ 182 | 0, /*tp_call*/ 183 | 0, /*tp_str*/ 184 | Match_getattro, /*tp_getattro*/ 185 | 0, /*tp_setattro*/ 186 | 0, /*tp_as_buffer*/ 187 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 188 | "Match class", /* tp_doc */ 189 | 0, /* tp_traverse */ 190 | 0, /* tp_clear */ 191 | Match_richcompare, /* tp_richcompare */ 192 | 0, /* tp_weaklistoffset */ 193 | 0, /* tp_iter */ 194 | 0, /* tp_iternext */ 195 | Match_methods, /* tp_methods */ 196 | Match_members, /* tp_members */ 197 | 0, /* tp_getset */ 198 | 0, /* tp_base */ 199 | 0, /* tp_dict */ 200 | 0, /* tp_descr_get */ 201 | 0, /* tp_descr_set */ 202 | 0, /* tp_dictoffset */ 203 | 0, /* tp_init */ 204 | 0, /* tp_alloc */ 205 | 0, /* tp_new */ 206 | }; 207 | 208 | // StringMatch object 209 | 210 | typedef struct 211 | { 212 | PyObject_HEAD 213 | PyObject* identifier; 214 | PyObject* instances; 215 | // This is not exposed directly because it contains flags that are internal 216 | // to yara (eg: STRING_FLAGS_FITS_IN_ATOM) along with modifiers 217 | // (eg: STRING_FLAGS_XOR). 218 | uint64_t flags; 219 | } StringMatch; 220 | 221 | static PyMemberDef StringMatch_members[] = { 222 | { 223 | "identifier", 224 | T_OBJECT_EX, 225 | offsetof(StringMatch, identifier), 226 | READONLY, 227 | "Name of the matching string" 228 | }, 229 | { 230 | "instances", 231 | T_OBJECT_EX, 232 | offsetof(StringMatch, instances), 233 | READONLY, 234 | "StringMatchInstance objects of the matching string" 235 | }, 236 | { NULL } // End marker 237 | }; 238 | 239 | static PyObject* StringMatch_NEW( 240 | const char* identifier, 241 | uint64_t flags, 242 | PyObject* instance_list); 243 | 244 | static void StringMatch_dealloc( 245 | PyObject* self); 246 | 247 | static PyObject* StringMatch_repr( 248 | PyObject* self); 249 | 250 | static PyObject* StringMatch_getattro( 251 | PyObject* self, 252 | PyObject* name); 253 | 254 | static Py_hash_t StringMatch_hash( 255 | PyObject* self); 256 | 257 | static PyObject* StringMatch_is_xor( 258 | PyObject* self, 259 | PyObject* args); 260 | 261 | 262 | static PyMethodDef StringMatch_methods[] = 263 | { 264 | { 265 | "is_xor", 266 | (PyCFunction) StringMatch_is_xor, 267 | METH_NOARGS, 268 | "Return true if a string has the xor modifier" 269 | }, 270 | { NULL }, 271 | }; 272 | 273 | static PyTypeObject StringMatch_Type = { 274 | PyVarObject_HEAD_INIT(NULL, 0) 275 | "yara.StringMatch", /*tp_name*/ 276 | sizeof(StringMatch), /*tp_basicsize*/ 277 | 0, /*tp_itemsize*/ 278 | (destructor)StringMatch_dealloc, /*tp_dealloc*/ 279 | 0, /*tp_print*/ 280 | 0, /*tp_getattr*/ 281 | 0, /*tp_setattr*/ 282 | 0, /*tp_compare*/ 283 | StringMatch_repr, /*tp_repr*/ 284 | 0, /*tp_as_number*/ 285 | 0, /*tp_as_sequence*/ 286 | 0, /*tp_as_mapping*/ 287 | StringMatch_hash, /*tp_hash */ 288 | 0, /*tp_call*/ 289 | 0, /*tp_str*/ 290 | StringMatch_getattro, /*tp_getattro*/ 291 | 0, /*tp_setattro*/ 292 | 0, /*tp_as_buffer*/ 293 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 294 | "StringMatch class", /* tp_doc */ 295 | 0, /* tp_traverse */ 296 | 0, /* tp_clear */ 297 | 0, /* tp_richcompare */ // XXX: Implement richcompare? 298 | 0, /* tp_weaklistoffset */ 299 | 0, /* tp_iter */ 300 | 0, /* tp_iternext */ 301 | StringMatch_methods, /* tp_methods */ 302 | StringMatch_members, /* tp_members */ 303 | 0, /* tp_getset */ 304 | 0, /* tp_base */ 305 | 0, /* tp_dict */ 306 | 0, /* tp_descr_get */ 307 | 0, /* tp_descr_set */ 308 | 0, /* tp_dictoffset */ 309 | 0, /* tp_init */ 310 | 0, /* tp_alloc */ 311 | 0, /* tp_new */ 312 | }; 313 | 314 | // StringMatchInstance object 315 | 316 | typedef struct 317 | { 318 | PyObject_HEAD 319 | PyObject* offset; 320 | PyObject* matched_data; 321 | PyObject* matched_length; 322 | PyObject* xor_key; 323 | } StringMatchInstance; 324 | 325 | static PyMemberDef StringMatchInstance_members[] = { 326 | { 327 | "offset", 328 | T_OBJECT_EX, 329 | offsetof(StringMatchInstance, offset), 330 | READONLY, 331 | "Offset of the matched data" 332 | }, 333 | { 334 | "matched_data", 335 | T_OBJECT_EX, 336 | offsetof(StringMatchInstance, matched_data), 337 | READONLY, 338 | "Matched data" 339 | }, 340 | { 341 | "matched_length", 342 | T_OBJECT_EX, 343 | offsetof(StringMatchInstance, matched_length), 344 | READONLY, 345 | "Length of matched data" 346 | }, 347 | { 348 | "xor_key", 349 | T_OBJECT_EX, 350 | offsetof(StringMatchInstance, xor_key), 351 | READONLY, 352 | "XOR key found for xor strings" 353 | }, 354 | { NULL } // End marker 355 | }; 356 | 357 | static PyObject* StringMatchInstance_NEW( 358 | uint64_t offset, 359 | PyObject* matched_data, 360 | int32_t match_length, 361 | uint8_t xor_key); 362 | 363 | static void StringMatchInstance_dealloc( 364 | PyObject* self); 365 | 366 | static PyObject* StringMatchInstance_repr( 367 | PyObject* self); 368 | 369 | static PyObject* StringMatchInstance_getattro( 370 | PyObject* self, 371 | PyObject* name); 372 | 373 | static Py_hash_t StringMatchInstance_hash( 374 | PyObject* self); 375 | 376 | static PyObject* StringMatchInstance_plaintext( 377 | PyObject* self, 378 | PyObject* args); 379 | 380 | 381 | static PyMethodDef StringMatchInstance_methods[] = 382 | { 383 | { 384 | "plaintext", 385 | (PyCFunction) StringMatchInstance_plaintext, 386 | METH_NOARGS, 387 | "Return matched data after xor key applied." 388 | }, 389 | { NULL }, 390 | }; 391 | 392 | static PyTypeObject StringMatchInstance_Type = { 393 | PyVarObject_HEAD_INIT(NULL, 0) 394 | "yara.StringMatchInstance", /*tp_name*/ 395 | sizeof(StringMatchInstance), /*tp_basicsize*/ 396 | 0, /*tp_itemsize*/ 397 | (destructor)StringMatchInstance_dealloc, /*tp_dealloc*/ 398 | 0, /*tp_print*/ 399 | 0, /*tp_getattr*/ 400 | 0, /*tp_setattr*/ 401 | 0, /*tp_compare*/ 402 | StringMatchInstance_repr, /*tp_repr*/ 403 | 0, /*tp_as_number*/ 404 | 0, /*tp_as_sequence*/ 405 | 0, /*tp_as_mapping*/ 406 | StringMatchInstance_hash, /*tp_hash */ 407 | 0, /*tp_call*/ 408 | 0, /*tp_str*/ 409 | StringMatchInstance_getattro, /*tp_getattro*/ 410 | 0, /*tp_setattro*/ 411 | 0, /*tp_as_buffer*/ 412 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 413 | "StringMatchInstance class", /* tp_doc */ 414 | 0, /* tp_traverse */ 415 | 0, /* tp_clear */ 416 | 0, /* tp_richcompare */ // XXX: Implement richcompare? 417 | 0, /* tp_weaklistoffset */ 418 | 0, /* tp_iter */ 419 | 0, /* tp_iternext */ 420 | StringMatchInstance_methods, /* tp_methods */ 421 | StringMatchInstance_members, /* tp_members */ 422 | 0, /* tp_getset */ 423 | 0, /* tp_base */ 424 | 0, /* tp_dict */ 425 | 0, /* tp_descr_get */ 426 | 0, /* tp_descr_set */ 427 | 0, /* tp_dictoffset */ 428 | 0, /* tp_init */ 429 | 0, /* tp_alloc */ 430 | 0, /* tp_new */ 431 | }; 432 | 433 | // Rule object 434 | 435 | typedef struct 436 | { 437 | PyObject_HEAD 438 | PyObject* identifier; 439 | PyObject* tags; 440 | PyObject* meta; 441 | PyObject* global; 442 | PyObject* private; 443 | } Rule; 444 | 445 | static void Rule_dealloc( 446 | PyObject* self); 447 | 448 | static PyObject* Rule_getattro( 449 | PyObject* self, 450 | PyObject* name); 451 | 452 | static PyMemberDef Rule_members[] = { 453 | { 454 | "is_global", 455 | T_OBJECT_EX, 456 | offsetof(Rule, global), 457 | READONLY, 458 | "Rule is global" 459 | }, 460 | { 461 | "is_private", 462 | T_OBJECT_EX, 463 | offsetof(Rule, private), 464 | READONLY, 465 | "Rule is private" 466 | }, 467 | { 468 | "identifier", 469 | T_OBJECT_EX, 470 | offsetof(Rule, identifier), 471 | READONLY, 472 | "Name of the rule" 473 | }, 474 | { 475 | "tags", 476 | T_OBJECT_EX, 477 | offsetof(Rule, tags), 478 | READONLY, 479 | "Tags for the rule" 480 | }, 481 | { 482 | "meta", 483 | T_OBJECT_EX, 484 | offsetof(Rule, meta), 485 | READONLY, 486 | "Meta for the rule" 487 | }, 488 | { NULL } // End marker 489 | }; 490 | 491 | static PyMethodDef Rule_methods[] = 492 | { 493 | { NULL, NULL } 494 | }; 495 | 496 | static PyTypeObject Rule_Type = { 497 | PyVarObject_HEAD_INIT(NULL, 0) 498 | "yara.Rule", /*tp_name*/ 499 | sizeof(Rule), /*tp_basicsize*/ 500 | 0, /*tp_itemsize*/ 501 | (destructor) Rule_dealloc, /*tp_dealloc*/ 502 | 0, /*tp_print*/ 503 | 0, /*tp_getattr*/ 504 | 0, /*tp_setattr*/ 505 | 0, /*tp_compare*/ 506 | 0, /*tp_repr*/ 507 | 0, /*tp_as_number*/ 508 | 0, /*tp_as_sequence*/ 509 | 0, /*tp_as_mapping*/ 510 | 0, /*tp_hash */ 511 | 0, /*tp_call*/ 512 | 0, /*tp_str*/ 513 | Rule_getattro, /*tp_getattro*/ 514 | 0, /*tp_setattro*/ 515 | 0, /*tp_as_buffer*/ 516 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 517 | "Rule class", /* tp_doc */ 518 | 0, /* tp_traverse */ 519 | 0, /* tp_clear */ 520 | 0, /* tp_richcompare */ 521 | 0, /* tp_weaklistoffset */ 522 | 0, /* tp_iter */ 523 | 0, /* tp_iternext */ 524 | Rule_methods, /* tp_methods */ 525 | Rule_members, /* tp_members */ 526 | 0, /* tp_getset */ 527 | 0, /* tp_base */ 528 | 0, /* tp_dict */ 529 | 0, /* tp_descr_get */ 530 | 0, /* tp_descr_set */ 531 | 0, /* tp_dictoffset */ 532 | 0, /* tp_init */ 533 | 0, /* tp_alloc */ 534 | 0, /* tp_new */ 535 | }; 536 | 537 | 538 | // Rules object 539 | 540 | typedef struct 541 | { 542 | PyObject_HEAD 543 | PyObject* externals; 544 | PyObject* warnings; 545 | YR_RULES* rules; 546 | YR_RULE* iter_current_rule; 547 | } Rules; 548 | 549 | 550 | static Rules* Rules_NEW(void); 551 | 552 | static void Rules_dealloc( 553 | PyObject* self); 554 | 555 | static PyObject* Rules_match( 556 | PyObject* self, 557 | PyObject* args, 558 | PyObject* keywords); 559 | 560 | static PyObject* Rules_save( 561 | PyObject* self, 562 | PyObject* args, 563 | PyObject* keywords); 564 | 565 | static PyObject* Rules_profiling_info( 566 | PyObject* self, 567 | PyObject* args); 568 | 569 | static PyObject* Rules_getattro( 570 | PyObject* self, 571 | PyObject* name); 572 | 573 | static PyObject* Rules_next( 574 | PyObject* self); 575 | 576 | static PyMemberDef Rules_members[] = { 577 | { 578 | "warnings", 579 | T_OBJECT_EX, 580 | offsetof(Rules, warnings), 581 | READONLY, 582 | "List of compiler warnings" 583 | }, 584 | { NULL } // End marker 585 | }; 586 | 587 | static PyMethodDef Rules_methods[] = 588 | { 589 | { 590 | "match", 591 | (PyCFunction) Rules_match, 592 | METH_VARARGS | METH_KEYWORDS 593 | }, 594 | { 595 | "save", 596 | (PyCFunction) Rules_save, 597 | METH_VARARGS | METH_KEYWORDS 598 | }, 599 | { 600 | "profiling_info", 601 | (PyCFunction) Rules_profiling_info, 602 | METH_NOARGS 603 | }, 604 | { 605 | NULL, 606 | NULL 607 | } 608 | }; 609 | 610 | static PyTypeObject Rules_Type = { 611 | PyVarObject_HEAD_INIT(NULL, 0) 612 | "yara.Rules", /*tp_name*/ 613 | sizeof(Rules), /*tp_basicsize*/ 614 | 0, /*tp_itemsize*/ 615 | (destructor) Rules_dealloc, /*tp_dealloc*/ 616 | 0, /*tp_print*/ 617 | 0, /*tp_getattr*/ 618 | 0, /*tp_setattr*/ 619 | 0, /*tp_compare*/ 620 | 0, /*tp_repr*/ 621 | 0, /*tp_as_number*/ 622 | 0, /*tp_as_sequence*/ 623 | 0, /*tp_as_mapping*/ 624 | 0, /*tp_hash */ 625 | 0, /*tp_call*/ 626 | 0, /*tp_str*/ 627 | Rules_getattro, /*tp_getattro*/ 628 | 0, /*tp_setattro*/ 629 | 0, /*tp_as_buffer*/ 630 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 631 | "Rules class", /* tp_doc */ 632 | 0, /* tp_traverse */ 633 | 0, /* tp_clear */ 634 | 0, /* tp_richcompare */ 635 | 0, /* tp_weaklistoffset */ 636 | PyObject_SelfIter, /* tp_iter */ 637 | (iternextfunc) Rules_next, /* tp_iternext */ 638 | Rules_methods, /* tp_methods */ 639 | Rules_members, /* tp_members */ 640 | 0, /* tp_getset */ 641 | 0, /* tp_base */ 642 | 0, /* tp_dict */ 643 | 0, /* tp_descr_get */ 644 | 0, /* tp_descr_set */ 645 | 0, /* tp_dictoffset */ 646 | 0, /* tp_init */ 647 | 0, /* tp_alloc */ 648 | 0, /* tp_new */ 649 | }; 650 | 651 | typedef struct _CALLBACK_DATA 652 | { 653 | PyObject* matches; 654 | PyObject* callback; 655 | PyObject* modules_data; 656 | PyObject* modules_callback; 657 | PyObject* warnings_callback; 658 | PyObject* console_callback; 659 | int which; 660 | bool allow_duplicate_metadata; 661 | 662 | } CALLBACK_DATA; 663 | 664 | static PyStructSequence_Field RuleString_Fields[] = { 665 | {"namespace", "Namespace of the rule"}, 666 | {"rule", "Identifier of the rule"}, 667 | {"string", "Identifier of the string"}, 668 | {NULL} 669 | }; 670 | 671 | static PyStructSequence_Desc RuleString_Desc = { 672 | "RuleString", 673 | "Named tuple tying together rule identifier and string identifier", 674 | RuleString_Fields, 675 | (sizeof(RuleString_Fields) / sizeof(RuleString_Fields[0])) - 1 676 | }; 677 | 678 | static PyTypeObject RuleString_Type = {0}; 679 | 680 | // Forward declarations for handling module data. 681 | PyObject* convert_structure_to_python( 682 | YR_OBJECT_STRUCTURE* structure); 683 | 684 | 685 | PyObject* convert_array_to_python( 686 | YR_OBJECT_ARRAY* array); 687 | 688 | 689 | PyObject* convert_dictionary_to_python( 690 | YR_OBJECT_DICTIONARY* dictionary); 691 | 692 | 693 | PyObject* convert_object_to_python( 694 | YR_OBJECT* object) 695 | { 696 | PyObject* result = NULL; 697 | 698 | if (object == NULL) 699 | return NULL; 700 | 701 | switch(object->type) 702 | { 703 | case OBJECT_TYPE_INTEGER: 704 | if (object->value.i != YR_UNDEFINED) 705 | result = Py_BuildValue("l", object->value.i); 706 | break; 707 | 708 | case OBJECT_TYPE_STRING: 709 | if (object->value.ss != NULL) 710 | result = PyBytes_FromStringAndSize( 711 | object->value.ss->c_string, 712 | object->value.ss->length); 713 | break; 714 | 715 | case OBJECT_TYPE_STRUCTURE: 716 | result = convert_structure_to_python(object_as_structure(object)); 717 | break; 718 | 719 | case OBJECT_TYPE_ARRAY: 720 | result = convert_array_to_python(object_as_array(object)); 721 | break; 722 | 723 | case OBJECT_TYPE_FUNCTION: 724 | // Do nothing with functions... 725 | break; 726 | 727 | case OBJECT_TYPE_DICTIONARY: 728 | result = convert_dictionary_to_python(object_as_dictionary(object)); 729 | break; 730 | 731 | case OBJECT_TYPE_FLOAT: 732 | if (!isnan(object->value.d)) 733 | result = Py_BuildValue("d", object->value.d); 734 | break; 735 | 736 | default: 737 | break; 738 | } 739 | 740 | return result; 741 | } 742 | 743 | 744 | PyObject* convert_structure_to_python( 745 | YR_OBJECT_STRUCTURE* structure) 746 | { 747 | YR_STRUCTURE_MEMBER* member; 748 | 749 | PyObject* py_object; 750 | PyObject* py_dict = PyDict_New(); 751 | 752 | if (py_dict == NULL) 753 | return py_dict; 754 | 755 | member = structure->members; 756 | 757 | while (member != NULL) 758 | { 759 | py_object = convert_object_to_python(member->object); 760 | 761 | if (py_object != NULL) 762 | { 763 | PyDict_SetItemString(py_dict, member->object->identifier, py_object); 764 | Py_DECREF(py_object); 765 | } 766 | 767 | member =member->next; 768 | } 769 | 770 | return py_dict; 771 | } 772 | 773 | 774 | PyObject* convert_array_to_python( 775 | YR_OBJECT_ARRAY* array) 776 | { 777 | PyObject* py_object; 778 | PyObject* py_list = PyList_New(0); 779 | 780 | if (py_list == NULL) 781 | return py_list; 782 | 783 | // If there is nothing in the list, return an empty Python list 784 | if (array->items == NULL) 785 | return py_list; 786 | 787 | for (int i = 0; i < array->items->length; i++) 788 | { 789 | py_object = convert_object_to_python(array->items->objects[i]); 790 | 791 | if (py_object != NULL) 792 | { 793 | PyList_Append(py_list, py_object); 794 | Py_DECREF(py_object); 795 | } 796 | } 797 | 798 | return py_list; 799 | } 800 | 801 | 802 | PyObject* convert_dictionary_to_python( 803 | YR_OBJECT_DICTIONARY* dictionary) 804 | { 805 | PyObject* py_object; 806 | PyObject* py_dict = PyDict_New(); 807 | 808 | if (py_dict == NULL) 809 | return py_dict; 810 | 811 | // If there is nothing in the YARA dictionary, return an empty Python dict 812 | if (dictionary->items == NULL) 813 | return py_dict; 814 | 815 | for (int i = 0; i < dictionary->items->used; i++) 816 | { 817 | py_object = convert_object_to_python(dictionary->items->objects[i].obj); 818 | 819 | if (py_object != NULL) 820 | { 821 | PyDict_SetItemString( 822 | py_dict, 823 | dictionary->items->objects[i].key->c_string, 824 | py_object); 825 | 826 | Py_DECREF(py_object); 827 | } 828 | } 829 | 830 | return py_dict; 831 | } 832 | 833 | 834 | static int handle_import_module( 835 | YR_MODULE_IMPORT* module_import, 836 | CALLBACK_DATA* data) 837 | { 838 | if (data->modules_data == NULL) 839 | return CALLBACK_CONTINUE; 840 | 841 | PyGILState_STATE gil_state = PyGILState_Ensure(); 842 | 843 | PyObject* module_data = PyDict_GetItemString( 844 | data->modules_data, 845 | module_import->module_name); 846 | 847 | #if PY_MAJOR_VERSION >= 3 848 | if (module_data != NULL && PyBytes_Check(module_data)) 849 | #else 850 | if (module_data != NULL && PyString_Check(module_data)) 851 | #endif 852 | { 853 | Py_ssize_t data_size; 854 | 855 | #if PY_MAJOR_VERSION >= 3 856 | PyBytes_AsStringAndSize( 857 | module_data, 858 | (char**) &module_import->module_data, 859 | &data_size); 860 | #else 861 | PyString_AsStringAndSize( 862 | module_data, 863 | (char**) &module_import->module_data, 864 | &data_size); 865 | #endif 866 | 867 | module_import->module_data_size = data_size; 868 | } 869 | 870 | PyGILState_Release(gil_state); 871 | 872 | return CALLBACK_CONTINUE; 873 | } 874 | 875 | 876 | static int handle_module_imported( 877 | void* message_data, 878 | CALLBACK_DATA* data) 879 | { 880 | if (data->modules_callback == NULL) 881 | return CALLBACK_CONTINUE; 882 | 883 | PyGILState_STATE gil_state = PyGILState_Ensure(); 884 | 885 | PyObject* module_info_dict = convert_structure_to_python( 886 | object_as_structure(message_data)); 887 | 888 | if (module_info_dict == NULL) 889 | { 890 | PyGILState_Release(gil_state); 891 | return CALLBACK_CONTINUE; 892 | } 893 | 894 | PyObject* object = PY_STRING(object_as_structure(message_data)->identifier); 895 | PyDict_SetItemString(module_info_dict, "module", object); 896 | Py_DECREF(object); 897 | 898 | Py_INCREF(data->modules_callback); 899 | 900 | PyObject* callback_result = PyObject_CallFunctionObjArgs( 901 | data->modules_callback, 902 | module_info_dict, 903 | NULL); 904 | 905 | int result = CALLBACK_CONTINUE; 906 | 907 | if (callback_result != NULL) 908 | { 909 | #if PY_MAJOR_VERSION >= 3 910 | if (PyLong_Check(callback_result)) 911 | #else 912 | if (PyLong_Check(callback_result) || PyInt_Check(callback_result)) 913 | #endif 914 | { 915 | result = (int) PyLong_AsLong(callback_result); 916 | } 917 | } 918 | else 919 | { 920 | result = CALLBACK_ERROR; 921 | } 922 | 923 | Py_XDECREF(callback_result); 924 | Py_DECREF(module_info_dict); 925 | Py_DECREF(data->modules_callback); 926 | 927 | PyGILState_Release(gil_state); 928 | 929 | return result; 930 | } 931 | 932 | 933 | static int handle_console_log( 934 | void* message_data, 935 | CALLBACK_DATA* data) 936 | { 937 | PyGILState_STATE gil_state = PyGILState_Ensure(); 938 | int result = CALLBACK_CONTINUE; 939 | 940 | if (data->console_callback == NULL) 941 | { 942 | // If the user does not specify a console callback we dump to stdout. 943 | // If we want to support 3.2 and newer only we can use 944 | // https://docs.python.org/3/c-api/sys.html?highlight=stdout#c.PySys_FormatStdout 945 | // instead of this call with the limit. 946 | PySys_WriteStdout("%.1000s\n", (char*) message_data); 947 | } 948 | else 949 | { 950 | PyObject* log_string = PY_STRING((char*) message_data); 951 | Py_INCREF(data->console_callback); 952 | 953 | PyObject* callback_result = PyObject_CallFunctionObjArgs( 954 | data->console_callback, 955 | log_string, 956 | NULL); 957 | 958 | if (callback_result != NULL) 959 | { 960 | #if PY_MAJOR_VERSION >= 3 961 | if (PyLong_Check(callback_result)) 962 | #else 963 | if (PyLong_Check(callback_result) || PyInt_Check(callback_result)) 964 | #endif 965 | { 966 | result = (int) PyLong_AsLong(callback_result); 967 | } 968 | } 969 | else 970 | { 971 | result = CALLBACK_ERROR; 972 | } 973 | 974 | Py_DECREF(log_string); 975 | Py_XDECREF(callback_result); 976 | Py_DECREF(data->console_callback); 977 | } 978 | 979 | PyGILState_Release(gil_state); 980 | 981 | return result; 982 | } 983 | 984 | 985 | static int handle_too_many_matches( 986 | YR_SCAN_CONTEXT* context, 987 | YR_STRING* string, 988 | CALLBACK_DATA* data) 989 | { 990 | PyGILState_STATE gil_state = PyGILState_Ensure(); 991 | 992 | PyObject* warning_type = NULL; 993 | PyObject* string_identifier = NULL; 994 | PyObject* rule_identifier = NULL; 995 | PyObject* namespace_identifier = NULL; 996 | PyObject* rule_string = NULL; 997 | YR_RULE* rule = NULL; 998 | 999 | int result = CALLBACK_CONTINUE; 1000 | 1001 | if (data->warnings_callback == NULL) 1002 | { 1003 | char message[200]; 1004 | 1005 | snprintf( 1006 | message, 1007 | sizeof(message), 1008 | "too many matches for string %s in rule \"%s\"", 1009 | string->identifier, 1010 | context->rules->rules_table[string->rule_idx].identifier); 1011 | 1012 | if (PyErr_WarnEx(PyExc_RuntimeWarning, message, 1) == -1) 1013 | result = CALLBACK_ERROR; 1014 | } 1015 | else 1016 | { 1017 | Py_INCREF(data->warnings_callback); 1018 | 1019 | string_identifier = PY_STRING(string->identifier); 1020 | 1021 | if (string_identifier == NULL) 1022 | { 1023 | result = CALLBACK_ERROR; 1024 | goto _exit; 1025 | } 1026 | 1027 | rule = &context->rules->rules_table[string->rule_idx]; 1028 | rule_identifier = PY_STRING(rule->identifier); 1029 | 1030 | if (rule_identifier == NULL) 1031 | { 1032 | result = CALLBACK_ERROR; 1033 | goto _exit; 1034 | } 1035 | 1036 | namespace_identifier = PY_STRING(rule->ns->name); 1037 | 1038 | if (namespace_identifier == NULL) 1039 | { 1040 | result = CALLBACK_ERROR; 1041 | goto _exit; 1042 | } 1043 | 1044 | rule_string = PyStructSequence_New(&RuleString_Type); 1045 | 1046 | if (rule_string == NULL) 1047 | { 1048 | result = CALLBACK_ERROR; 1049 | goto _exit; 1050 | } 1051 | 1052 | PyStructSequence_SET_ITEM(rule_string, 0, namespace_identifier); 1053 | PyStructSequence_SET_ITEM(rule_string, 1, rule_identifier); 1054 | PyStructSequence_SET_ITEM(rule_string, 2, string_identifier); 1055 | 1056 | // PyStructSequenece steals the reference so we NULL these 1057 | // so that Py_XDECREF() can be used in _exit label 1058 | namespace_identifier = NULL; 1059 | rule_identifier = NULL; 1060 | string_identifier = NULL; 1061 | 1062 | warning_type = PyLong_FromLong(CALLBACK_MSG_TOO_MANY_MATCHES); 1063 | 1064 | if (warning_type == NULL) 1065 | { 1066 | result = CALLBACK_ERROR; 1067 | goto _exit; 1068 | } 1069 | 1070 | PyObject* callback_result = PyObject_CallFunctionObjArgs( 1071 | data->warnings_callback, 1072 | warning_type, 1073 | rule_string, 1074 | NULL); 1075 | 1076 | if (callback_result != NULL) 1077 | { 1078 | #if PY_MAJOR_VERSION >= 3 1079 | if (PyLong_Check(callback_result)) 1080 | #else 1081 | if (PyLong_Check(callback_result) || PyInt_Check(callback_result)) 1082 | #endif 1083 | { 1084 | result = (int) PyLong_AsLong(callback_result); 1085 | } 1086 | } 1087 | else 1088 | { 1089 | result = CALLBACK_ERROR; 1090 | } 1091 | 1092 | Py_XDECREF(callback_result); 1093 | } 1094 | 1095 | _exit: 1096 | 1097 | Py_XDECREF(namespace_identifier); 1098 | Py_XDECREF(rule_identifier); 1099 | Py_XDECREF(string_identifier); 1100 | Py_XDECREF(rule_string); 1101 | Py_XDECREF(warning_type); 1102 | Py_XDECREF(data->warnings_callback); 1103 | 1104 | PyGILState_Release(gil_state); 1105 | 1106 | return result; 1107 | } 1108 | 1109 | 1110 | #define CALLBACK_MATCHES 0x01 1111 | #define CALLBACK_NON_MATCHES 0x02 1112 | #define CALLBACK_ALL CALLBACK_MATCHES | CALLBACK_NON_MATCHES 1113 | 1114 | int yara_callback( 1115 | YR_SCAN_CONTEXT* context, 1116 | int message, 1117 | void* message_data, 1118 | void* user_data) 1119 | { 1120 | YR_STRING* string; 1121 | YR_MATCH* m; 1122 | YR_META* meta; 1123 | YR_RULE* rule; 1124 | 1125 | const char* tag; 1126 | 1127 | PyObject* tag_list = NULL; 1128 | PyObject* string_instance_list = NULL; 1129 | PyObject* string_list = NULL; 1130 | PyObject* meta_list = NULL; 1131 | PyObject* string_match_instance = NULL; 1132 | PyObject* match; 1133 | PyObject* callback_dict; 1134 | PyObject* object; 1135 | PyObject* matches = ((CALLBACK_DATA*) user_data)->matches; 1136 | PyObject* callback = ((CALLBACK_DATA*) user_data)->callback; 1137 | PyObject* callback_result; 1138 | 1139 | int which = ((CALLBACK_DATA*) user_data)->which; 1140 | 1141 | switch(message) 1142 | { 1143 | case CALLBACK_MSG_IMPORT_MODULE: 1144 | return handle_import_module(message_data, user_data); 1145 | 1146 | case CALLBACK_MSG_MODULE_IMPORTED: 1147 | return handle_module_imported(message_data, user_data); 1148 | 1149 | case CALLBACK_MSG_TOO_MANY_MATCHES: 1150 | return handle_too_many_matches(context, message_data, user_data); 1151 | 1152 | case CALLBACK_MSG_SCAN_FINISHED: 1153 | return CALLBACK_CONTINUE; 1154 | 1155 | case CALLBACK_MSG_RULE_NOT_MATCHING: 1156 | // In cases where the rule doesn't match and the user didn't provided a 1157 | // callback function or is not interested in getting notified about 1158 | // non-matches, there's nothing more do to here, keep executing the function 1159 | // if otherwise. 1160 | 1161 | if (callback == NULL || 1162 | (which & CALLBACK_NON_MATCHES) != CALLBACK_NON_MATCHES) 1163 | return CALLBACK_CONTINUE; 1164 | break; 1165 | 1166 | case CALLBACK_MSG_CONSOLE_LOG: 1167 | return handle_console_log(message_data, user_data); 1168 | } 1169 | 1170 | // At this point we have handled all the other cases of when this callback 1171 | // can be called. The only things left are: 1172 | // 1173 | // 1. A matching rule. 1174 | // 1175 | // 2 A non-matching rule and the user has requested to see non-matching rules. 1176 | // 1177 | // In both cases, we need to create the data that will be either passed back 1178 | // to the python callback or stored in the matches list. 1179 | 1180 | int result = CALLBACK_CONTINUE; 1181 | 1182 | rule = (YR_RULE*) message_data; 1183 | 1184 | PyGILState_STATE gil_state = PyGILState_Ensure(); 1185 | 1186 | tag_list = PyList_New(0); 1187 | string_list = PyList_New(0); 1188 | meta_list = PyDict_New(); 1189 | 1190 | if (tag_list == NULL || string_list == NULL || meta_list == NULL) 1191 | { 1192 | Py_XDECREF(tag_list); 1193 | Py_XDECREF(string_list); 1194 | Py_XDECREF(meta_list); 1195 | PyGILState_Release(gil_state); 1196 | 1197 | return CALLBACK_ERROR; 1198 | } 1199 | 1200 | yr_rule_tags_foreach(rule, tag) 1201 | { 1202 | object = PY_STRING(tag); 1203 | PyList_Append(tag_list, object); 1204 | Py_DECREF(object); 1205 | } 1206 | 1207 | yr_rule_metas_foreach(rule, meta) 1208 | { 1209 | if (meta->type == META_TYPE_INTEGER) 1210 | object = Py_BuildValue("i", meta->integer); 1211 | else if (meta->type == META_TYPE_BOOLEAN) 1212 | object = PyBool_FromLong((long) meta->integer); 1213 | else 1214 | object = PY_STRING(meta->string); 1215 | 1216 | if (((CALLBACK_DATA*) user_data)->allow_duplicate_metadata){ 1217 | // Check if we already have an array under this key 1218 | PyObject* existing_item = PyDict_GetItemString(meta_list, meta->identifier); 1219 | // Append object to existing list 1220 | if (existing_item) 1221 | PyList_Append(existing_item, object); 1222 | else{ 1223 | //Otherwise, instantiate array and append object as first item 1224 | PyObject* new_list = PyList_New(0); 1225 | PyList_Append(new_list, object); 1226 | PyDict_SetItemString(meta_list, meta->identifier, new_list); 1227 | Py_DECREF(new_list); 1228 | } 1229 | } 1230 | else{ 1231 | PyDict_SetItemString(meta_list, meta->identifier, object); 1232 | Py_DECREF(object); 1233 | } 1234 | } 1235 | 1236 | yr_rule_strings_foreach(rule, string) 1237 | { 1238 | // If this string is not a match, skip it. We have to check for this here 1239 | // and not rely on it in yr_string_matches_foreach macro because we need 1240 | // to create the string match instance list before we make the items that 1241 | // go in it. 1242 | if (context->matches[string->idx].head == NULL) 1243 | continue; 1244 | 1245 | string_instance_list = PyList_New(0); 1246 | 1247 | if (string_instance_list == NULL) 1248 | { 1249 | PyErr_Format(PyExc_TypeError, "out of memory"); 1250 | return CALLBACK_ERROR; 1251 | } 1252 | 1253 | 1254 | yr_string_matches_foreach(context, string, m) 1255 | { 1256 | object = PyBytes_FromStringAndSize((char*) m->data, m->data_length); 1257 | 1258 | string_match_instance = StringMatchInstance_NEW( 1259 | m->base + m->offset, 1260 | object, 1261 | m->match_length, 1262 | m->xor_key); 1263 | 1264 | if (string_match_instance == NULL) 1265 | { 1266 | Py_DECREF(object); 1267 | PyErr_Format(PyExc_TypeError, "out of memory"); 1268 | return CALLBACK_ERROR; 1269 | } 1270 | 1271 | PyList_Append(string_instance_list, string_match_instance); 1272 | 1273 | Py_DECREF(object); 1274 | Py_DECREF(string_match_instance); 1275 | } 1276 | 1277 | object = StringMatch_NEW( 1278 | string->identifier, 1279 | string->flags, 1280 | string_instance_list); 1281 | 1282 | if (object == NULL) 1283 | { 1284 | PyErr_Format(PyExc_TypeError, "out of memory"); 1285 | return CALLBACK_ERROR; 1286 | } 1287 | 1288 | 1289 | Py_DECREF(string_instance_list); 1290 | 1291 | PyList_Append(string_list, object); 1292 | Py_DECREF(object); 1293 | } 1294 | 1295 | if (message == CALLBACK_MSG_RULE_MATCHING) 1296 | { 1297 | match = Match_NEW( 1298 | rule->identifier, 1299 | rule->ns->name, 1300 | tag_list, 1301 | meta_list, 1302 | string_list); 1303 | 1304 | if (match != NULL) 1305 | { 1306 | PyList_Append(matches, match); 1307 | Py_DECREF(match); 1308 | } 1309 | else 1310 | { 1311 | Py_DECREF(tag_list); 1312 | Py_DECREF(string_list); 1313 | Py_DECREF(meta_list); 1314 | PyGILState_Release(gil_state); 1315 | 1316 | return CALLBACK_ERROR; 1317 | } 1318 | } 1319 | 1320 | if (callback != NULL && 1321 | ((message == CALLBACK_MSG_RULE_MATCHING && (which & CALLBACK_MATCHES)) || 1322 | (message == CALLBACK_MSG_RULE_NOT_MATCHING && (which & CALLBACK_NON_MATCHES)))) 1323 | { 1324 | Py_INCREF(callback); 1325 | 1326 | callback_dict = PyDict_New(); 1327 | 1328 | object = PyBool_FromLong(message == CALLBACK_MSG_RULE_MATCHING); 1329 | PyDict_SetItemString(callback_dict, "matches", object); 1330 | Py_DECREF(object); 1331 | 1332 | object = PY_STRING(rule->identifier); 1333 | PyDict_SetItemString(callback_dict, "rule", object); 1334 | Py_DECREF(object); 1335 | 1336 | object = PY_STRING(rule->ns->name); 1337 | PyDict_SetItemString(callback_dict, "namespace", object); 1338 | Py_DECREF(object); 1339 | 1340 | PyDict_SetItemString(callback_dict, "tags", tag_list); 1341 | PyDict_SetItemString(callback_dict, "meta", meta_list); 1342 | PyDict_SetItemString(callback_dict, "strings", string_list); 1343 | 1344 | callback_result = PyObject_CallFunctionObjArgs( 1345 | callback, 1346 | callback_dict, 1347 | NULL); 1348 | 1349 | if (callback_result != NULL) 1350 | { 1351 | #if PY_MAJOR_VERSION >= 3 1352 | if (PyLong_Check(callback_result)) 1353 | #else 1354 | if (PyLong_Check(callback_result) || PyInt_Check(callback_result)) 1355 | #endif 1356 | { 1357 | result = (int) PyLong_AsLong(callback_result); 1358 | } 1359 | 1360 | Py_DECREF(callback_result); 1361 | } 1362 | else 1363 | { 1364 | result = CALLBACK_ERROR; 1365 | } 1366 | 1367 | Py_DECREF(callback_dict); 1368 | Py_DECREF(callback); 1369 | } 1370 | 1371 | Py_DECREF(tag_list); 1372 | Py_DECREF(string_list); 1373 | Py_DECREF(meta_list); 1374 | PyGILState_Release(gil_state); 1375 | 1376 | return result; 1377 | } 1378 | 1379 | 1380 | /* YR_STREAM read method for "file-like objects" */ 1381 | 1382 | static size_t flo_read( 1383 | void* ptr, 1384 | size_t size, 1385 | size_t count, 1386 | void* user_data) 1387 | { 1388 | size_t i; 1389 | 1390 | for (i = 0; i < count; i++) 1391 | { 1392 | PyGILState_STATE gil_state = PyGILState_Ensure(); 1393 | 1394 | PyObject* bytes = PyObject_CallMethod( 1395 | (PyObject*) user_data, "read", "n", (Py_ssize_t) size); 1396 | 1397 | if (bytes == NULL) 1398 | { 1399 | PyGILState_Release(gil_state); 1400 | return i; 1401 | } 1402 | 1403 | Py_ssize_t len; 1404 | char* buffer; 1405 | 1406 | int result = PyBytes_AsStringAndSize(bytes, &buffer, &len); 1407 | 1408 | if (result == -1 || (size_t) len < size) 1409 | { 1410 | Py_DECREF(bytes); 1411 | PyGILState_Release(gil_state); 1412 | return i; 1413 | } 1414 | 1415 | memcpy((char*) ptr + i * size, buffer, size); 1416 | 1417 | Py_DECREF(bytes); 1418 | PyGILState_Release(gil_state); 1419 | } 1420 | 1421 | return count; 1422 | } 1423 | 1424 | 1425 | /* YR_STREAM write method for "file-like objects" */ 1426 | 1427 | static size_t flo_write( 1428 | const void* ptr, 1429 | size_t size, 1430 | size_t count, 1431 | void* user_data) 1432 | { 1433 | size_t i; 1434 | 1435 | for (i = 0; i < count; i++) 1436 | { 1437 | PyGILState_STATE gil_state = PyGILState_Ensure(); 1438 | 1439 | PyObject* result = PyObject_CallMethod( 1440 | #if PY_MAJOR_VERSION >= 3 1441 | (PyObject*) user_data, "write", "y#", (char*) ptr + i * size, size); 1442 | #else 1443 | (PyObject*) user_data, "write", "s#", (char*) ptr + i * size, size); 1444 | #endif 1445 | 1446 | Py_XDECREF(result); 1447 | PyGILState_Release(gil_state); 1448 | 1449 | if (result == NULL) 1450 | return i; 1451 | } 1452 | 1453 | return count; 1454 | } 1455 | 1456 | 1457 | PyObject* handle_error( 1458 | int error, 1459 | char* extra) 1460 | { 1461 | switch(error) 1462 | { 1463 | case ERROR_COULD_NOT_ATTACH_TO_PROCESS: 1464 | return PyErr_Format( 1465 | YaraError, 1466 | "access denied"); 1467 | case ERROR_INSUFFICIENT_MEMORY: 1468 | return PyErr_NoMemory(); 1469 | case ERROR_COULD_NOT_OPEN_FILE: 1470 | return PyErr_Format( 1471 | YaraError, 1472 | "could not open file \"%s\"", 1473 | extra); 1474 | case ERROR_COULD_NOT_MAP_FILE: 1475 | return PyErr_Format( 1476 | YaraError, 1477 | "could not map file \"%s\" into memory", 1478 | extra); 1479 | case ERROR_INVALID_FILE: 1480 | return PyErr_Format( 1481 | YaraError, 1482 | "invalid rules file \"%s\"", 1483 | extra); 1484 | case ERROR_CORRUPT_FILE: 1485 | return PyErr_Format( 1486 | YaraError, 1487 | "corrupt rules file \"%s\"", 1488 | extra); 1489 | case ERROR_SCAN_TIMEOUT: 1490 | return PyErr_Format( 1491 | YaraTimeoutError, 1492 | "scanning timed out"); 1493 | case ERROR_INVALID_EXTERNAL_VARIABLE_TYPE: 1494 | return PyErr_Format( 1495 | YaraError, 1496 | "external variable \"%s\" was already defined with a different type", 1497 | extra); 1498 | case ERROR_UNSUPPORTED_FILE_VERSION: 1499 | return PyErr_Format( 1500 | YaraError, 1501 | "rules file \"%s\" is incompatible with this version of YARA", 1502 | extra); 1503 | default: 1504 | return PyErr_Format( 1505 | YaraError, 1506 | "internal error: %d", 1507 | error); 1508 | } 1509 | } 1510 | 1511 | 1512 | int process_compile_externals( 1513 | PyObject* externals, 1514 | YR_COMPILER* compiler) 1515 | { 1516 | PyObject* key; 1517 | PyObject* value; 1518 | Py_ssize_t pos = 0; 1519 | 1520 | char* identifier = NULL; 1521 | int result; 1522 | 1523 | while (PyDict_Next(externals, &pos, &key, &value)) 1524 | { 1525 | if (!PY_STRING_CHECK(key)) { 1526 | PyErr_Format( 1527 | PyExc_TypeError, 1528 | "keys of externals dict must be strings"); 1529 | 1530 | return ERROR_INVALID_ARGUMENT; 1531 | } 1532 | identifier = PY_STRING_TO_C(key); 1533 | 1534 | if (PyBool_Check(value)) 1535 | { 1536 | result = yr_compiler_define_boolean_variable( 1537 | compiler, 1538 | identifier, 1539 | PyObject_IsTrue(value)); 1540 | } 1541 | #if PY_MAJOR_VERSION >= 3 1542 | else if (PyLong_Check(value)) 1543 | #else 1544 | else if (PyLong_Check(value) || PyInt_Check(value)) 1545 | #endif 1546 | { 1547 | result = yr_compiler_define_integer_variable( 1548 | compiler, 1549 | identifier, 1550 | PyLong_AsLongLong(value)); 1551 | } 1552 | else if (PyFloat_Check(value)) 1553 | { 1554 | result = yr_compiler_define_float_variable( 1555 | compiler, 1556 | identifier, 1557 | PyFloat_AsDouble(value)); 1558 | } 1559 | else if (PY_STRING_CHECK(value)) 1560 | { 1561 | char* str = PY_STRING_TO_C(value); 1562 | 1563 | if (str == NULL) 1564 | return ERROR_INVALID_ARGUMENT; 1565 | 1566 | result = yr_compiler_define_string_variable( 1567 | compiler, identifier, str); 1568 | } 1569 | else 1570 | { 1571 | PyErr_Format( 1572 | PyExc_TypeError, 1573 | "external values must be of type integer, float, boolean or string"); 1574 | 1575 | return ERROR_INVALID_ARGUMENT; 1576 | } 1577 | 1578 | if (result != ERROR_SUCCESS) 1579 | { 1580 | handle_error(result, identifier); 1581 | return result; 1582 | } 1583 | } 1584 | 1585 | return ERROR_SUCCESS; 1586 | } 1587 | 1588 | 1589 | int process_match_externals( 1590 | PyObject* externals, 1591 | YR_SCANNER* scanner) 1592 | { 1593 | PyObject* key; 1594 | PyObject* value; 1595 | Py_ssize_t pos = 0; 1596 | 1597 | char* identifier = NULL; 1598 | int result; 1599 | 1600 | while (PyDict_Next(externals, &pos, &key, &value)) 1601 | { 1602 | if (!PY_STRING_CHECK(key)) { 1603 | PyErr_Format( 1604 | PyExc_TypeError, 1605 | "keys of externals dict must be strings"); 1606 | 1607 | return ERROR_INVALID_ARGUMENT; 1608 | } 1609 | identifier = PY_STRING_TO_C(key); 1610 | 1611 | if (PyBool_Check(value)) 1612 | { 1613 | result = yr_scanner_define_boolean_variable( 1614 | scanner, 1615 | identifier, 1616 | PyObject_IsTrue(value)); 1617 | } 1618 | #if PY_MAJOR_VERSION >= 3 1619 | else if (PyLong_Check(value)) 1620 | #else 1621 | else if (PyLong_Check(value) || PyInt_Check(value)) 1622 | #endif 1623 | { 1624 | result = yr_scanner_define_integer_variable( 1625 | scanner, 1626 | identifier, 1627 | PyLong_AsLongLong(value)); 1628 | } 1629 | else if (PyFloat_Check(value)) 1630 | { 1631 | result = yr_scanner_define_float_variable( 1632 | scanner, 1633 | identifier, 1634 | PyFloat_AsDouble(value)); 1635 | } 1636 | else if (PY_STRING_CHECK(value)) 1637 | { 1638 | char* str = PY_STRING_TO_C(value); 1639 | 1640 | if (str == NULL) 1641 | return ERROR_INVALID_ARGUMENT; 1642 | 1643 | result = yr_scanner_define_string_variable( 1644 | scanner, identifier, str); 1645 | } 1646 | else 1647 | { 1648 | PyErr_Format( 1649 | PyExc_TypeError, 1650 | "external values must be of type integer, float, boolean or string"); 1651 | 1652 | return ERROR_INVALID_ARGUMENT; 1653 | } 1654 | 1655 | // yr_scanner_define_xxx_variable returns ERROR_INVALID_ARGUMENT if the 1656 | // variable wasn't previously defined in the compilation phase. Ignore 1657 | // those errors because we don't want the "scan" method being aborted 1658 | // because of the "externals" dictionary having more keys than those used 1659 | // during compilation. 1660 | 1661 | if (result != ERROR_SUCCESS && 1662 | result != ERROR_INVALID_ARGUMENT) 1663 | { 1664 | handle_error(result, identifier); 1665 | return result; 1666 | } 1667 | } 1668 | 1669 | return ERROR_SUCCESS; 1670 | } 1671 | 1672 | 1673 | static PyObject* Match_NEW( 1674 | const char* rule, 1675 | const char* ns, 1676 | PyObject* tags, 1677 | PyObject* meta, 1678 | PyObject* strings) 1679 | { 1680 | Match* object = PyObject_NEW(Match, &Match_Type); 1681 | 1682 | if (object != NULL) 1683 | { 1684 | object->rule = PY_STRING(rule); 1685 | object->ns = PY_STRING(ns); 1686 | object->tags = tags; 1687 | object->meta = meta; 1688 | object->strings = strings; 1689 | 1690 | Py_INCREF(tags); 1691 | Py_INCREF(meta); 1692 | Py_INCREF(strings); 1693 | } 1694 | 1695 | return (PyObject*) object; 1696 | } 1697 | 1698 | 1699 | static void Match_dealloc( 1700 | PyObject* self) 1701 | { 1702 | Match* object = (Match*) self; 1703 | 1704 | Py_DECREF(object->rule); 1705 | Py_DECREF(object->ns); 1706 | Py_DECREF(object->tags); 1707 | Py_DECREF(object->meta); 1708 | Py_DECREF(object->strings); 1709 | 1710 | PyObject_Del(self); 1711 | } 1712 | 1713 | 1714 | static PyObject* Match_repr( 1715 | PyObject* self) 1716 | { 1717 | Match* object = (Match*) self; 1718 | Py_INCREF(object->rule); 1719 | return object->rule; 1720 | } 1721 | 1722 | 1723 | static PyObject* Match_getattro( 1724 | PyObject* self, 1725 | PyObject* name) 1726 | { 1727 | return PyObject_GenericGetAttr(self, name); 1728 | } 1729 | 1730 | 1731 | static PyObject* Match_richcompare( 1732 | PyObject* self, 1733 | PyObject* other, 1734 | int op) 1735 | { 1736 | PyObject* result = NULL; 1737 | 1738 | Match* a = (Match*) self; 1739 | Match* b = (Match*) other; 1740 | 1741 | if(PyObject_TypeCheck(other, &Match_Type)) 1742 | { 1743 | switch(op) 1744 | { 1745 | case Py_EQ: 1746 | if (PyObject_RichCompareBool(a->rule, b->rule, Py_EQ) && 1747 | PyObject_RichCompareBool(a->ns, b->ns, Py_EQ)) 1748 | result = Py_True; 1749 | else 1750 | result = Py_False; 1751 | 1752 | Py_INCREF(result); 1753 | break; 1754 | 1755 | case Py_NE: 1756 | if (PyObject_RichCompareBool(a->rule, b->rule, Py_NE) || 1757 | PyObject_RichCompareBool(a->ns, b->ns, Py_NE)) 1758 | result = Py_True; 1759 | else 1760 | result = Py_False; 1761 | 1762 | Py_INCREF(result); 1763 | break; 1764 | 1765 | case Py_LT: 1766 | case Py_LE: 1767 | case Py_GT: 1768 | case Py_GE: 1769 | if (PyObject_RichCompareBool(a->rule, b->rule, Py_EQ)) 1770 | result = PyObject_RichCompare(a->ns, b->ns, op); 1771 | else 1772 | result = PyObject_RichCompare(a->rule, b->rule, op); 1773 | 1774 | break; 1775 | } 1776 | } 1777 | else 1778 | { 1779 | result = PyErr_Format( 1780 | PyExc_TypeError, 1781 | "'Match' objects must be compared with objects of the same class"); 1782 | } 1783 | 1784 | return result; 1785 | } 1786 | 1787 | 1788 | static Py_hash_t Match_hash( 1789 | PyObject* self) 1790 | { 1791 | Match* match = (Match*) self; 1792 | return PyObject_Hash(match->rule) + PyObject_Hash(match->ns); 1793 | } 1794 | 1795 | 1796 | //////////////////////////////////////////////////////////////////////////////// 1797 | 1798 | 1799 | static PyObject* StringMatch_NEW( 1800 | const char* identifier, 1801 | uint64_t flags, 1802 | PyObject* instance_list) 1803 | { 1804 | StringMatch* object = PyObject_NEW(StringMatch, &StringMatch_Type); 1805 | 1806 | if (object != NULL) 1807 | { 1808 | object->identifier = PY_STRING(identifier); 1809 | object->flags = flags; 1810 | object->instances = instance_list; 1811 | 1812 | Py_INCREF(instance_list); 1813 | } 1814 | 1815 | return (PyObject*) object; 1816 | } 1817 | 1818 | 1819 | static void StringMatch_dealloc( 1820 | PyObject* self) 1821 | { 1822 | StringMatch* object = (StringMatch*) self; 1823 | 1824 | Py_DECREF(object->identifier); 1825 | Py_DECREF(object->instances); 1826 | 1827 | PyObject_Del(self); 1828 | } 1829 | 1830 | 1831 | static PyObject* StringMatch_repr( 1832 | PyObject* self) 1833 | { 1834 | StringMatch* object = (StringMatch*) self; 1835 | Py_INCREF(object->identifier); 1836 | return object->identifier; 1837 | } 1838 | 1839 | 1840 | static PyObject* StringMatch_getattro( 1841 | PyObject* self, 1842 | PyObject* name) 1843 | { 1844 | return PyObject_GenericGetAttr(self, name); 1845 | } 1846 | 1847 | 1848 | // Hashing on just identifiers can be tricky as there can be duplicate 1849 | // identifiers between rules and there are anonymous strings too. Be careful 1850 | // when using this! 1851 | static Py_hash_t StringMatch_hash( 1852 | PyObject* self) 1853 | { 1854 | return PyObject_Hash(((StringMatch*) self)->identifier); 1855 | } 1856 | 1857 | 1858 | static PyObject* StringMatch_is_xor( 1859 | PyObject* self, 1860 | PyObject* args) 1861 | { 1862 | if (((StringMatch*) self)->flags & STRING_FLAGS_XOR) 1863 | Py_RETURN_TRUE; 1864 | 1865 | Py_RETURN_FALSE; 1866 | } 1867 | 1868 | 1869 | //////////////////////////////////////////////////////////////////////////////// 1870 | 1871 | 1872 | static PyObject* StringMatchInstance_NEW( 1873 | uint64_t offset, 1874 | PyObject* matched_data, 1875 | int32_t match_length, 1876 | uint8_t xor_key) 1877 | { 1878 | StringMatchInstance* object = PyObject_NEW(StringMatchInstance, &StringMatchInstance_Type); 1879 | 1880 | if (object != NULL) 1881 | { 1882 | object->offset = PyLong_FromLongLong(offset); 1883 | object->matched_data = matched_data; 1884 | object->matched_length = PyLong_FromLong(match_length); 1885 | object->xor_key = PyLong_FromUnsignedLong((uint32_t) xor_key); 1886 | 1887 | Py_INCREF(matched_data); 1888 | } 1889 | 1890 | return (PyObject*) object; 1891 | } 1892 | 1893 | 1894 | static void StringMatchInstance_dealloc( 1895 | PyObject* self) 1896 | { 1897 | StringMatchInstance* object = (StringMatchInstance*) self; 1898 | 1899 | Py_DECREF(object->offset); 1900 | Py_DECREF(object->matched_data); 1901 | Py_DECREF(object->matched_length); 1902 | Py_DECREF(object->xor_key); 1903 | 1904 | PyObject_Del(self); 1905 | } 1906 | 1907 | 1908 | static PyObject* StringMatchInstance_repr( 1909 | PyObject* self) 1910 | { 1911 | StringMatchInstance* object = (StringMatchInstance*) self; 1912 | return PyCodec_Decode(object->matched_data, "utf-8", "backslashreplace"); 1913 | } 1914 | 1915 | 1916 | static PyObject* StringMatchInstance_getattro( 1917 | PyObject* self, 1918 | PyObject* name) 1919 | { 1920 | return PyObject_GenericGetAttr(self, name); 1921 | } 1922 | 1923 | 1924 | static Py_hash_t StringMatchInstance_hash( 1925 | PyObject* self) 1926 | { 1927 | return PyObject_Hash(((StringMatchInstance*) self)->matched_data); 1928 | } 1929 | 1930 | 1931 | static PyObject* StringMatchInstance_plaintext( 1932 | PyObject* self, 1933 | PyObject* args) 1934 | { 1935 | char* pb; 1936 | Py_ssize_t length; 1937 | 1938 | StringMatchInstance* instance = (StringMatchInstance*) self; 1939 | uint64_t xor_key = PyLong_AsUnsignedLongLong(instance->xor_key); 1940 | if (xor_key == 0) 1941 | { 1942 | Py_INCREF(instance->matched_data); 1943 | return instance->matched_data; 1944 | } 1945 | 1946 | int result = PyBytes_AsStringAndSize(instance->matched_data, &pb, &length); 1947 | if (result == -1) 1948 | return NULL; 1949 | 1950 | // pb points to an internal buffer of the bytes object which we can not 1951 | // modify. Allocate a new buffer, copy the contents over and do the xor, then 1952 | // create a new bytes object to return. 1953 | uint8_t* buf = (uint8_t*) calloc(length, sizeof(uint8_t)); 1954 | if (buf == NULL) 1955 | return PyErr_Format(PyExc_TypeError, "Out of memory"); 1956 | 1957 | memcpy(buf, pb, length); 1958 | for (size_t i = 0; i < length; i++) { 1959 | buf[i] = ((uint8_t) pb[i]) ^ xor_key; 1960 | } 1961 | 1962 | PyObject* object = PyBytes_FromStringAndSize((char*) buf, length); 1963 | free(buf); 1964 | 1965 | return object; 1966 | } 1967 | 1968 | 1969 | //////////////////////////////////////////////////////////////////////////////// 1970 | 1971 | 1972 | static void Rule_dealloc( 1973 | PyObject* self) 1974 | { 1975 | Rule* object = (Rule*) self; 1976 | Py_XDECREF(object->identifier); 1977 | Py_XDECREF(object->tags); 1978 | Py_XDECREF(object->meta); 1979 | Py_XDECREF(object->global); 1980 | Py_XDECREF(object->private); 1981 | PyObject_Del(self); 1982 | } 1983 | 1984 | static PyObject* Rule_getattro( 1985 | PyObject* self, 1986 | PyObject* name) 1987 | { 1988 | return PyObject_GenericGetAttr(self, name); 1989 | } 1990 | 1991 | 1992 | static Rules* Rules_NEW(void) 1993 | { 1994 | Rules* rules = PyObject_NEW(Rules, &Rules_Type); 1995 | 1996 | if (rules != NULL) 1997 | { 1998 | rules->rules = NULL; 1999 | rules->externals = NULL; 2000 | rules->warnings = NULL; 2001 | } 2002 | 2003 | return rules; 2004 | } 2005 | 2006 | static void Rules_dealloc( 2007 | PyObject* self) 2008 | { 2009 | Rules* object = (Rules*) self; 2010 | 2011 | Py_XDECREF(object->externals); 2012 | Py_XDECREF(object->warnings); 2013 | 2014 | if (object->rules != NULL) 2015 | yr_rules_destroy(object->rules); 2016 | 2017 | PyObject_Del(self); 2018 | } 2019 | 2020 | static PyObject* Rules_next( 2021 | PyObject* self) 2022 | { 2023 | PyObject* tag_list; 2024 | PyObject* object; 2025 | PyObject* meta_list; 2026 | 2027 | YR_META* meta; 2028 | const char* tag; 2029 | 2030 | Rule* rule; 2031 | Rules* rules = (Rules *) self; 2032 | 2033 | // Generate new Rule object based upon iter_current_rule and increment 2034 | // iter_current_rule. 2035 | 2036 | if (RULE_IS_NULL(rules->iter_current_rule)) 2037 | { 2038 | rules->iter_current_rule = rules->rules->rules_table; 2039 | PyErr_SetNone(PyExc_StopIteration); 2040 | return NULL; 2041 | } 2042 | 2043 | rule = PyObject_NEW(Rule, &Rule_Type); 2044 | tag_list = PyList_New(0); 2045 | meta_list = PyDict_New(); 2046 | 2047 | if (rule != NULL && tag_list != NULL && meta_list != NULL) 2048 | { 2049 | yr_rule_tags_foreach(rules->iter_current_rule, tag) 2050 | { 2051 | object = PY_STRING(tag); 2052 | PyList_Append(tag_list, object); 2053 | Py_DECREF(object); 2054 | } 2055 | 2056 | yr_rule_metas_foreach(rules->iter_current_rule, meta) 2057 | { 2058 | if (meta->type == META_TYPE_INTEGER) 2059 | object = Py_BuildValue("i", meta->integer); 2060 | else if (meta->type == META_TYPE_BOOLEAN) 2061 | object = PyBool_FromLong((long) meta->integer); 2062 | else 2063 | object = PY_STRING(meta->string); 2064 | 2065 | PyDict_SetItemString(meta_list, meta->identifier, object); 2066 | Py_DECREF(object); 2067 | 2068 | } 2069 | 2070 | rule->global = PyBool_FromLong(rules->iter_current_rule->flags & RULE_FLAGS_GLOBAL); 2071 | rule->private = PyBool_FromLong(rules->iter_current_rule->flags & RULE_FLAGS_PRIVATE); 2072 | rule->identifier = PY_STRING(rules->iter_current_rule->identifier); 2073 | rule->tags = tag_list; 2074 | rule->meta = meta_list; 2075 | rules->iter_current_rule++; 2076 | return (PyObject*) rule; 2077 | } 2078 | else 2079 | { 2080 | Py_XDECREF(tag_list); 2081 | Py_XDECREF(meta_list); 2082 | return PyErr_Format(PyExc_TypeError, "Out of memory"); 2083 | } 2084 | } 2085 | 2086 | static PyObject* Rules_match( 2087 | PyObject* self, 2088 | PyObject* args, 2089 | PyObject* keywords) 2090 | { 2091 | static char* kwlist[] = { 2092 | "filepath", "pid", "data", "externals", 2093 | "callback", "fast", "timeout", "modules_data", 2094 | "modules_callback", "which_callbacks", "warnings_callback", 2095 | "console_callback", "allow_duplicate_metadata", NULL 2096 | }; 2097 | 2098 | char* filepath = NULL; 2099 | Py_buffer data = {0}; 2100 | 2101 | int pid = -1; 2102 | int timeout = 0; 2103 | int error = ERROR_SUCCESS; 2104 | 2105 | PyObject* externals = NULL; 2106 | PyObject* fast = NULL; 2107 | 2108 | Rules* object = (Rules*) self; 2109 | 2110 | YR_SCANNER* scanner; 2111 | CALLBACK_DATA callback_data; 2112 | 2113 | callback_data.matches = NULL; 2114 | callback_data.callback = NULL; 2115 | callback_data.modules_data = NULL; 2116 | callback_data.modules_callback = NULL; 2117 | callback_data.warnings_callback = NULL; 2118 | callback_data.console_callback = NULL; 2119 | callback_data.which = CALLBACK_ALL; 2120 | callback_data.allow_duplicate_metadata = false; 2121 | 2122 | if (PyArg_ParseTupleAndKeywords( 2123 | args, 2124 | keywords, 2125 | "|sis*OOOiOOiOOb", 2126 | kwlist, 2127 | &filepath, 2128 | &pid, 2129 | &data, 2130 | &externals, 2131 | &callback_data.callback, 2132 | &fast, 2133 | &timeout, 2134 | &callback_data.modules_data, 2135 | &callback_data.modules_callback, 2136 | &callback_data.which, 2137 | &callback_data.warnings_callback, 2138 | &callback_data.console_callback, 2139 | &callback_data.allow_duplicate_metadata)) 2140 | { 2141 | if (filepath == NULL && data.buf == NULL && pid == -1) 2142 | { 2143 | return PyErr_Format( 2144 | PyExc_TypeError, 2145 | "match() takes at least one argument"); 2146 | } 2147 | 2148 | if (callback_data.callback != NULL) 2149 | { 2150 | if (!PyCallable_Check(callback_data.callback)) 2151 | { 2152 | PyBuffer_Release(&data); 2153 | return PyErr_Format( 2154 | PyExc_TypeError, 2155 | "'callback' must be callable"); 2156 | } 2157 | } 2158 | 2159 | if (callback_data.modules_callback != NULL) 2160 | { 2161 | if (!PyCallable_Check(callback_data.modules_callback)) 2162 | { 2163 | PyBuffer_Release(&data); 2164 | return PyErr_Format( 2165 | PyExc_TypeError, 2166 | "'modules_callback' must be callable"); 2167 | } 2168 | } 2169 | 2170 | if (callback_data.warnings_callback != NULL) 2171 | { 2172 | if (!PyCallable_Check(callback_data.warnings_callback)) 2173 | { 2174 | PyBuffer_Release(&data); 2175 | return PyErr_Format( 2176 | PyExc_TypeError, 2177 | "'warnings_callback' must be callable"); 2178 | } 2179 | } 2180 | 2181 | if (callback_data.console_callback != NULL) 2182 | { 2183 | if (!PyCallable_Check(callback_data.console_callback)) 2184 | { 2185 | PyBuffer_Release(&data); 2186 | return PyErr_Format( 2187 | PyExc_TypeError, 2188 | "'console_callback' must be callable"); 2189 | } 2190 | } 2191 | 2192 | if (callback_data.modules_data != NULL) 2193 | { 2194 | if (!PyDict_Check(callback_data.modules_data)) 2195 | { 2196 | PyBuffer_Release(&data); 2197 | return PyErr_Format( 2198 | PyExc_TypeError, 2199 | "'modules_data' must be a dictionary"); 2200 | } 2201 | } 2202 | 2203 | if (callback_data.allow_duplicate_metadata == NULL) 2204 | callback_data.allow_duplicate_metadata = false; 2205 | 2206 | if (yr_scanner_create(object->rules, &scanner) != 0) 2207 | { 2208 | return PyErr_Format( 2209 | PyExc_Exception, 2210 | "could not create scanner"); 2211 | } 2212 | 2213 | if (externals != NULL && externals != Py_None) 2214 | { 2215 | if (PyDict_Check(externals)) 2216 | { 2217 | if (process_match_externals(externals, scanner) != ERROR_SUCCESS) 2218 | { 2219 | PyBuffer_Release(&data); 2220 | yr_scanner_destroy(scanner); 2221 | return NULL; 2222 | } 2223 | } 2224 | else 2225 | { 2226 | PyBuffer_Release(&data); 2227 | yr_scanner_destroy(scanner); 2228 | return PyErr_Format( 2229 | PyExc_TypeError, 2230 | "'externals' must be a dictionary"); 2231 | } 2232 | } 2233 | 2234 | if (fast != NULL && PyObject_IsTrue(fast) == 1) 2235 | { 2236 | yr_scanner_set_flags(scanner, SCAN_FLAGS_FAST_MODE); 2237 | } 2238 | 2239 | yr_scanner_set_timeout(scanner, timeout); 2240 | yr_scanner_set_callback(scanner, yara_callback, &callback_data); 2241 | 2242 | if (filepath != NULL) 2243 | { 2244 | callback_data.matches = PyList_New(0); 2245 | 2246 | Py_BEGIN_ALLOW_THREADS 2247 | 2248 | error = yr_scanner_scan_file(scanner, filepath); 2249 | 2250 | Py_END_ALLOW_THREADS 2251 | } 2252 | else if (data.buf != NULL) 2253 | { 2254 | callback_data.matches = PyList_New(0); 2255 | 2256 | Py_BEGIN_ALLOW_THREADS 2257 | 2258 | error = yr_scanner_scan_mem( 2259 | scanner, 2260 | (unsigned char*) data.buf, 2261 | (size_t) data.len); 2262 | 2263 | Py_END_ALLOW_THREADS 2264 | } 2265 | else if (pid != -1) 2266 | { 2267 | callback_data.matches = PyList_New(0); 2268 | 2269 | Py_BEGIN_ALLOW_THREADS 2270 | 2271 | error = yr_scanner_scan_proc(scanner, pid); 2272 | 2273 | Py_END_ALLOW_THREADS 2274 | } 2275 | 2276 | PyBuffer_Release(&data); 2277 | yr_scanner_destroy(scanner); 2278 | 2279 | if (error != ERROR_SUCCESS) 2280 | { 2281 | Py_DECREF(callback_data.matches); 2282 | 2283 | if (error != ERROR_CALLBACK_ERROR) 2284 | { 2285 | if (filepath != NULL) 2286 | { 2287 | handle_error(error, filepath); 2288 | } 2289 | else if (pid != -1) 2290 | { 2291 | handle_error(error, ""); 2292 | } 2293 | else 2294 | { 2295 | handle_error(error, ""); 2296 | } 2297 | 2298 | #ifdef PROFILING_ENABLED 2299 | PyObject* exception = PyErr_Occurred(); 2300 | 2301 | if (exception != NULL && error == ERROR_SCAN_TIMEOUT) 2302 | { 2303 | PyObject_SetAttrString( 2304 | exception, 2305 | "profiling_info", 2306 | Rules_profiling_info(self, NULL)); 2307 | } 2308 | #endif 2309 | } 2310 | 2311 | return NULL; 2312 | } 2313 | } 2314 | 2315 | return callback_data.matches; 2316 | } 2317 | 2318 | 2319 | static PyObject* Rules_save( 2320 | PyObject* self, 2321 | PyObject* args, 2322 | PyObject* keywords) 2323 | { 2324 | static char* kwlist[] = { 2325 | "filepath", "file", NULL 2326 | }; 2327 | 2328 | char* filepath = NULL; 2329 | PyObject* file = NULL; 2330 | Rules* rules = (Rules*) self; 2331 | 2332 | int error; 2333 | 2334 | if (!PyArg_ParseTupleAndKeywords( 2335 | args, 2336 | keywords, 2337 | "|sO", 2338 | kwlist, 2339 | &filepath, 2340 | &file)) 2341 | { 2342 | return NULL; 2343 | } 2344 | 2345 | if (filepath != NULL) 2346 | { 2347 | Py_BEGIN_ALLOW_THREADS 2348 | error = yr_rules_save(rules->rules, filepath); 2349 | Py_END_ALLOW_THREADS 2350 | 2351 | if (error != ERROR_SUCCESS) 2352 | return handle_error(error, filepath); 2353 | } 2354 | else if (file != NULL && PyObject_HasAttrString(file, "write")) 2355 | { 2356 | YR_STREAM stream; 2357 | 2358 | stream.user_data = file; 2359 | stream.write = flo_write; 2360 | 2361 | Py_BEGIN_ALLOW_THREADS; 2362 | error = yr_rules_save_stream(rules->rules, &stream); 2363 | Py_END_ALLOW_THREADS; 2364 | 2365 | if (error != ERROR_SUCCESS) 2366 | return handle_error(error, ""); 2367 | } 2368 | else 2369 | { 2370 | return PyErr_Format( 2371 | PyExc_TypeError, 2372 | "load() expects either a file path or a file-like object"); 2373 | } 2374 | 2375 | Py_RETURN_NONE; 2376 | } 2377 | 2378 | 2379 | static PyObject* Rules_profiling_info( 2380 | PyObject* self, 2381 | PyObject* args) 2382 | { 2383 | 2384 | #ifdef PROFILING_ENABLED 2385 | PyObject* object; 2386 | PyObject* result; 2387 | 2388 | YR_RULES* rules = ((Rules*) self)->rules; 2389 | YR_RULE* rule; 2390 | YR_STRING* string; 2391 | 2392 | char key[512]; 2393 | uint64_t clock_ticks; 2394 | 2395 | result = PyDict_New(); 2396 | 2397 | yr_rules_foreach(rules, rule) 2398 | { 2399 | clock_ticks = rule->clock_ticks; 2400 | 2401 | yr_rule_strings_foreach(rule, string) 2402 | { 2403 | clock_ticks += string->clock_ticks; 2404 | } 2405 | 2406 | snprintf(key, sizeof(key), "%s:%s", rule->ns->name, rule->identifier); 2407 | 2408 | object = PyLong_FromLongLong(clock_ticks); 2409 | PyDict_SetItemString(result, key, object); 2410 | Py_DECREF(object); 2411 | } 2412 | 2413 | return result; 2414 | #else 2415 | return PyErr_Format(YaraError, "libyara compiled without profiling support"); 2416 | #endif 2417 | } 2418 | 2419 | 2420 | static PyObject* Rules_getattro( 2421 | PyObject* self, 2422 | PyObject* name) 2423 | { 2424 | return PyObject_GenericGetAttr(self, name); 2425 | } 2426 | 2427 | 2428 | void raise_exception_on_error( 2429 | int error_level, 2430 | const char* file_name, 2431 | int line_number, 2432 | const YR_RULE* rule, 2433 | const char* message, 2434 | void* user_data) 2435 | { 2436 | PyGILState_STATE gil_state = PyGILState_Ensure(); 2437 | 2438 | if (error_level == YARA_ERROR_LEVEL_ERROR) 2439 | { 2440 | if (file_name != NULL) 2441 | PyErr_Format( 2442 | YaraSyntaxError, 2443 | "%s(%d): %s", 2444 | file_name, 2445 | line_number, 2446 | message); 2447 | else 2448 | PyErr_Format( 2449 | YaraSyntaxError, 2450 | "line %d: %s", 2451 | line_number, 2452 | message); 2453 | } 2454 | else 2455 | { 2456 | PyObject* warnings = (PyObject*)user_data; 2457 | PyObject* warning_msg; 2458 | if (file_name != NULL) 2459 | warning_msg = PY_STRING_FORMAT( 2460 | "%s(%d): %s", 2461 | file_name, 2462 | line_number, 2463 | message); 2464 | else 2465 | warning_msg = PY_STRING_FORMAT( 2466 | "line %d: %s", 2467 | line_number, 2468 | message); 2469 | PyList_Append(warnings, warning_msg); 2470 | Py_DECREF(warning_msg); 2471 | } 2472 | 2473 | PyGILState_Release(gil_state); 2474 | } 2475 | 2476 | 2477 | //////////////////////////////////////////////////////////////////////////////// 2478 | 2479 | const char* yara_include_callback( 2480 | const char* include_name, 2481 | const char* calling_rule_filename, 2482 | const char* calling_rule_namespace, 2483 | void* user_data) 2484 | { 2485 | PyObject* result; 2486 | PyObject* callback = (PyObject*) user_data; 2487 | PyObject* py_incl_name = NULL; 2488 | PyObject* py_calling_fn = NULL; 2489 | PyObject* py_calling_ns = NULL; 2490 | PyObject* type = NULL; 2491 | PyObject* value = NULL; 2492 | PyObject* traceback = NULL; 2493 | 2494 | const char* cstring_result = NULL; 2495 | 2496 | PyGILState_STATE gil_state = PyGILState_Ensure(); 2497 | 2498 | if (include_name != NULL) 2499 | { 2500 | py_incl_name = PY_STRING(include_name); 2501 | } 2502 | else //safeguard: should never happen for 'include_name' 2503 | { 2504 | py_incl_name = Py_None; 2505 | Py_INCREF(py_incl_name); 2506 | } 2507 | 2508 | if (calling_rule_filename != NULL) 2509 | { 2510 | py_calling_fn = PY_STRING(calling_rule_filename); 2511 | } 2512 | else 2513 | { 2514 | py_calling_fn = Py_None; 2515 | Py_INCREF(py_calling_fn); 2516 | } 2517 | 2518 | if (calling_rule_namespace != NULL) 2519 | { 2520 | py_calling_ns = PY_STRING(calling_rule_namespace); 2521 | } 2522 | else 2523 | { 2524 | py_calling_ns = Py_None; 2525 | Py_INCREF(py_calling_ns); 2526 | } 2527 | 2528 | PyErr_Fetch(&type, &value, &traceback); 2529 | 2530 | result = PyObject_CallFunctionObjArgs( 2531 | callback, 2532 | py_incl_name, 2533 | py_calling_fn, 2534 | py_calling_ns, 2535 | NULL); 2536 | 2537 | PyErr_Restore(type, value, traceback); 2538 | 2539 | Py_DECREF(py_incl_name); 2540 | Py_DECREF(py_calling_fn); 2541 | Py_DECREF(py_calling_ns); 2542 | 2543 | if (result != NULL && result != Py_None && PY_STRING_CHECK(result)) 2544 | { 2545 | //transferring string ownership to C code 2546 | cstring_result = strdup(PY_STRING_TO_C(result)); 2547 | } 2548 | else 2549 | { 2550 | if (PyErr_Occurred() == NULL) 2551 | { 2552 | PyErr_Format(PyExc_TypeError, 2553 | "'include_callback' function must return a yara rules as an ascii " 2554 | "or unicode string"); 2555 | } 2556 | } 2557 | 2558 | Py_XDECREF(result); 2559 | PyGILState_Release(gil_state); 2560 | 2561 | return cstring_result; 2562 | } 2563 | 2564 | void yara_include_free( 2565 | const char* result_ptr, 2566 | void* user_data) 2567 | { 2568 | if (result_ptr != NULL) 2569 | { 2570 | free((void*) result_ptr); 2571 | } 2572 | } 2573 | 2574 | //////////////////////////////////////////////////////////////////////////////// 2575 | 2576 | static PyObject* yara_set_config( 2577 | PyObject* self, 2578 | PyObject* args, 2579 | PyObject* keywords) 2580 | { 2581 | 2582 | /* 2583 | * It is recommended that this be kept up to date with the config 2584 | * options present in yara/libyara.c yr_set_configuration(...) - ck 2585 | */ 2586 | static char *kwlist[] = { 2587 | "stack_size", "max_strings_per_rule", "max_match_data", NULL}; 2588 | 2589 | unsigned int stack_size = 0; 2590 | unsigned int max_strings_per_rule = 0; 2591 | unsigned int max_match_data = 0; 2592 | 2593 | int error = 0; 2594 | 2595 | if (PyArg_ParseTupleAndKeywords( 2596 | args, 2597 | keywords, 2598 | "|III", 2599 | kwlist, 2600 | &stack_size, 2601 | &max_strings_per_rule, 2602 | &max_match_data)) 2603 | { 2604 | if (stack_size != 0) 2605 | { 2606 | error = yr_set_configuration( 2607 | YR_CONFIG_STACK_SIZE, 2608 | &stack_size); 2609 | 2610 | if ( error != ERROR_SUCCESS) 2611 | return handle_error(error, NULL); 2612 | } 2613 | 2614 | if (max_strings_per_rule != 0) 2615 | { 2616 | error = yr_set_configuration( 2617 | YR_CONFIG_MAX_STRINGS_PER_RULE, 2618 | &max_strings_per_rule); 2619 | 2620 | if (error != ERROR_SUCCESS) 2621 | return handle_error(error, NULL); 2622 | } 2623 | 2624 | if (max_match_data != 0) 2625 | { 2626 | error = yr_set_configuration( 2627 | YR_CONFIG_MAX_MATCH_DATA, 2628 | &max_match_data); 2629 | 2630 | if (error != ERROR_SUCCESS) 2631 | return handle_error(error, NULL); 2632 | } 2633 | } 2634 | 2635 | Py_RETURN_NONE; 2636 | } 2637 | 2638 | static PyObject* yara_compile( 2639 | PyObject* self, 2640 | PyObject* args, 2641 | PyObject* keywords) 2642 | { 2643 | static char *kwlist[] = { 2644 | "filepath", "source", "file", "filepaths", "sources", 2645 | "includes", "externals", "error_on_warning", "strict_escape", "include_callback", NULL}; 2646 | 2647 | YR_COMPILER* compiler; 2648 | YR_RULES* yara_rules; 2649 | FILE* fh; 2650 | 2651 | Rules* rules; 2652 | 2653 | PyObject* key; 2654 | PyObject* value; 2655 | PyObject* result = NULL; 2656 | PyObject* file = NULL; 2657 | PyObject* sources_dict = NULL; 2658 | PyObject* filepaths_dict = NULL; 2659 | PyObject* includes = NULL; 2660 | PyObject* externals = NULL; 2661 | PyObject* error_on_warning = NULL; 2662 | PyObject* strict_escape = NULL; 2663 | PyObject* include_callback = NULL; 2664 | 2665 | Py_ssize_t pos = 0; 2666 | 2667 | int fd; 2668 | int error = 0; 2669 | 2670 | char* filepath = NULL; 2671 | char* source = NULL; 2672 | char* ns = NULL; 2673 | PyObject* warnings = PyList_New(0); 2674 | bool warning_error = false; 2675 | 2676 | if (PyArg_ParseTupleAndKeywords( 2677 | args, 2678 | keywords, 2679 | "|ssOOOOOOOO", 2680 | kwlist, 2681 | &filepath, 2682 | &source, 2683 | &file, 2684 | &filepaths_dict, 2685 | &sources_dict, 2686 | &includes, 2687 | &externals, 2688 | &error_on_warning, 2689 | &strict_escape, 2690 | &include_callback)) 2691 | { 2692 | char num_args = 0; 2693 | 2694 | if (filepath != NULL) 2695 | num_args++; 2696 | 2697 | if (source != NULL) 2698 | num_args++; 2699 | 2700 | if (file != NULL) 2701 | num_args++; 2702 | 2703 | if (filepaths_dict != NULL) 2704 | num_args++; 2705 | 2706 | if (sources_dict != NULL) 2707 | num_args++; 2708 | 2709 | if (num_args > 1) 2710 | return PyErr_Format( 2711 | PyExc_TypeError, 2712 | "compile is receiving too many arguments"); 2713 | 2714 | error = yr_compiler_create(&compiler); 2715 | 2716 | if (error != ERROR_SUCCESS) 2717 | return handle_error(error, NULL); 2718 | 2719 | yr_compiler_set_callback(compiler, raise_exception_on_error, warnings); 2720 | 2721 | if (error_on_warning != NULL) 2722 | { 2723 | if (PyBool_Check(error_on_warning)) 2724 | { 2725 | if (PyObject_IsTrue(error_on_warning) == 1) 2726 | { 2727 | warning_error = true; 2728 | } 2729 | } 2730 | else 2731 | { 2732 | yr_compiler_destroy(compiler); 2733 | return PyErr_Format( 2734 | PyExc_TypeError, 2735 | "'error_on_warning' param must be of boolean type"); 2736 | } 2737 | } 2738 | 2739 | if (strict_escape != NULL) 2740 | { 2741 | if (PyBool_Check(strict_escape)) 2742 | { 2743 | compiler->strict_escape = PyObject_IsTrue(strict_escape); 2744 | } 2745 | else 2746 | { 2747 | yr_compiler_destroy(compiler); 2748 | return PyErr_Format( 2749 | PyExc_TypeError, 2750 | "'strict_escape' param must be of boolean type"); 2751 | } 2752 | } 2753 | 2754 | if (includes != NULL) 2755 | { 2756 | if (PyBool_Check(includes)) 2757 | { 2758 | // PyObject_IsTrue can return -1 in case of error 2759 | if (PyObject_IsTrue(includes) == 0) 2760 | yr_compiler_set_include_callback(compiler, NULL, NULL, NULL); 2761 | } 2762 | else 2763 | { 2764 | yr_compiler_destroy(compiler); 2765 | return PyErr_Format( 2766 | PyExc_TypeError, 2767 | "'includes' param must be of boolean type"); 2768 | } 2769 | } 2770 | 2771 | if (include_callback != NULL) 2772 | { 2773 | if (!PyCallable_Check(include_callback)) 2774 | { 2775 | yr_compiler_destroy(compiler); 2776 | return PyErr_Format( 2777 | PyExc_TypeError, 2778 | "'include_callback' must be callable"); 2779 | } 2780 | 2781 | yr_compiler_set_include_callback( 2782 | compiler, 2783 | yara_include_callback, 2784 | yara_include_free, 2785 | include_callback); 2786 | } 2787 | 2788 | if (externals != NULL && externals != Py_None) 2789 | { 2790 | if (PyDict_Check(externals)) 2791 | { 2792 | if (process_compile_externals(externals, compiler) != ERROR_SUCCESS) 2793 | { 2794 | yr_compiler_destroy(compiler); 2795 | return NULL; 2796 | } 2797 | } 2798 | else 2799 | { 2800 | yr_compiler_destroy(compiler); 2801 | return PyErr_Format( 2802 | PyExc_TypeError, 2803 | "'externals' must be a dictionary"); 2804 | } 2805 | } 2806 | 2807 | Py_XINCREF(include_callback); 2808 | 2809 | if (filepath != NULL) 2810 | { 2811 | fh = fopen(filepath, "r"); 2812 | 2813 | if (fh != NULL) 2814 | { 2815 | Py_BEGIN_ALLOW_THREADS 2816 | error = yr_compiler_add_file(compiler, fh, NULL, filepath); 2817 | fclose(fh); 2818 | Py_END_ALLOW_THREADS 2819 | } 2820 | else 2821 | { 2822 | result = PyErr_SetFromErrno(YaraError); 2823 | } 2824 | } 2825 | else if (source != NULL) 2826 | { 2827 | Py_BEGIN_ALLOW_THREADS 2828 | error = yr_compiler_add_string(compiler, source, NULL); 2829 | Py_END_ALLOW_THREADS 2830 | } 2831 | else if (file != NULL) 2832 | { 2833 | fd = PyObject_AsFileDescriptor(file); 2834 | 2835 | if (fd != -1) 2836 | { 2837 | Py_BEGIN_ALLOW_THREADS 2838 | fh = fdopen(dup(fd), "r"); 2839 | error = yr_compiler_add_file(compiler, fh, NULL, NULL); 2840 | fclose(fh); 2841 | Py_END_ALLOW_THREADS 2842 | } 2843 | else 2844 | { 2845 | result = PyErr_Format( 2846 | PyExc_TypeError, 2847 | "'file' is not a file object"); 2848 | } 2849 | } 2850 | else if (sources_dict != NULL) 2851 | { 2852 | if (PyDict_Check(sources_dict)) 2853 | { 2854 | while (PyDict_Next(sources_dict, &pos, &key, &value)) 2855 | { 2856 | source = PY_STRING_TO_C(value); 2857 | ns = PY_STRING_TO_C(key); 2858 | 2859 | if (source != NULL && ns != NULL) 2860 | { 2861 | Py_BEGIN_ALLOW_THREADS 2862 | error = yr_compiler_add_string(compiler, source, ns); 2863 | Py_END_ALLOW_THREADS 2864 | 2865 | if (error > 0) 2866 | break; 2867 | } 2868 | else 2869 | { 2870 | result = PyErr_Format( 2871 | PyExc_TypeError, 2872 | "keys and values of the 'sources' dictionary must be " 2873 | "of string type"); 2874 | break; 2875 | } 2876 | } 2877 | } 2878 | else 2879 | { 2880 | result = PyErr_Format( 2881 | PyExc_TypeError, 2882 | "'sources' must be a dictionary"); 2883 | } 2884 | } 2885 | else if (filepaths_dict != NULL) 2886 | { 2887 | if (PyDict_Check(filepaths_dict)) 2888 | { 2889 | while (PyDict_Next(filepaths_dict, &pos, &key, &value)) 2890 | { 2891 | filepath = PY_STRING_TO_C(value); 2892 | ns = PY_STRING_TO_C(key); 2893 | 2894 | if (filepath != NULL && ns != NULL) 2895 | { 2896 | fh = fopen(filepath, "r"); 2897 | 2898 | if (fh != NULL) 2899 | { 2900 | Py_BEGIN_ALLOW_THREADS 2901 | error = yr_compiler_add_file(compiler, fh, ns, filepath); 2902 | fclose(fh); 2903 | Py_END_ALLOW_THREADS 2904 | 2905 | if (error > 0) 2906 | break; 2907 | } 2908 | else 2909 | { 2910 | result = PyErr_SetFromErrno(YaraError); 2911 | break; 2912 | } 2913 | } 2914 | else 2915 | { 2916 | result = PyErr_Format( 2917 | PyExc_TypeError, 2918 | "keys and values of the filepaths dictionary must be of " 2919 | "string type"); 2920 | break; 2921 | } 2922 | } 2923 | } 2924 | else 2925 | { 2926 | result = PyErr_Format( 2927 | PyExc_TypeError, 2928 | "filepaths must be a dictionary"); 2929 | } 2930 | } 2931 | else 2932 | { 2933 | result = PyErr_Format( 2934 | PyExc_TypeError, 2935 | "compile() takes 1 argument"); 2936 | } 2937 | 2938 | if (warning_error && PyList_Size(warnings) > 0) 2939 | { 2940 | PyErr_SetObject(YaraWarningError, warnings); 2941 | } 2942 | 2943 | if (PyErr_Occurred() == NULL) 2944 | { 2945 | rules = Rules_NEW(); 2946 | 2947 | if (rules != NULL) 2948 | { 2949 | Py_BEGIN_ALLOW_THREADS 2950 | error = yr_compiler_get_rules(compiler, &yara_rules); 2951 | Py_END_ALLOW_THREADS 2952 | 2953 | if (error == ERROR_SUCCESS) 2954 | { 2955 | rules->rules = yara_rules; 2956 | rules->iter_current_rule = rules->rules->rules_table; 2957 | rules->warnings = warnings; 2958 | 2959 | if (externals != NULL && externals != Py_None) 2960 | rules->externals = PyDict_Copy(externals); 2961 | 2962 | result = (PyObject*) rules; 2963 | } 2964 | else 2965 | { 2966 | Py_DECREF(rules); 2967 | result = handle_error(error, NULL); 2968 | } 2969 | } 2970 | else 2971 | { 2972 | result = handle_error(ERROR_INSUFFICIENT_MEMORY, NULL); 2973 | } 2974 | } 2975 | 2976 | yr_compiler_destroy(compiler); 2977 | Py_XDECREF(include_callback); 2978 | } 2979 | 2980 | return result; 2981 | } 2982 | 2983 | 2984 | static PyObject* yara_load( 2985 | PyObject* self, 2986 | PyObject* args, 2987 | PyObject* keywords) 2988 | { 2989 | static char* kwlist[] = { 2990 | "filepath", "file", NULL 2991 | }; 2992 | 2993 | YR_EXTERNAL_VARIABLE* external; 2994 | 2995 | Rules* rules = NULL; 2996 | PyObject* file = NULL; 2997 | char* filepath = NULL; 2998 | 2999 | int error; 3000 | 3001 | if (!PyArg_ParseTupleAndKeywords( 3002 | args, 3003 | keywords, 3004 | "|sO", 3005 | kwlist, 3006 | &filepath, 3007 | &file)) 3008 | { 3009 | return NULL; 3010 | } 3011 | 3012 | if (filepath != NULL) 3013 | { 3014 | rules = Rules_NEW(); 3015 | 3016 | if (rules == NULL) 3017 | return PyErr_NoMemory(); 3018 | 3019 | Py_BEGIN_ALLOW_THREADS; 3020 | error = yr_rules_load(filepath, &rules->rules); 3021 | Py_END_ALLOW_THREADS; 3022 | 3023 | if (error != ERROR_SUCCESS) 3024 | { 3025 | Py_DECREF(rules); 3026 | return handle_error(error, filepath); 3027 | } 3028 | } 3029 | else if (file != NULL && PyObject_HasAttrString(file, "read")) 3030 | { 3031 | YR_STREAM stream; 3032 | 3033 | stream.user_data = file; 3034 | stream.read = flo_read; 3035 | 3036 | rules = Rules_NEW(); 3037 | 3038 | if (rules == NULL) 3039 | return PyErr_NoMemory(); 3040 | 3041 | Py_BEGIN_ALLOW_THREADS; 3042 | error = yr_rules_load_stream(&stream, &rules->rules); 3043 | Py_END_ALLOW_THREADS; 3044 | 3045 | if (error != ERROR_SUCCESS) 3046 | { 3047 | Py_DECREF(rules); 3048 | return handle_error(error, ""); 3049 | } 3050 | } 3051 | else 3052 | { 3053 | return PyErr_Format( 3054 | PyExc_TypeError, 3055 | "load() expects either a file path or a file-like object"); 3056 | } 3057 | 3058 | external = rules->rules->ext_vars_table; 3059 | rules->iter_current_rule = rules->rules->rules_table; 3060 | 3061 | if (!EXTERNAL_VARIABLE_IS_NULL(external)) 3062 | rules->externals = PyDict_New(); 3063 | 3064 | while (!EXTERNAL_VARIABLE_IS_NULL(external)) 3065 | { 3066 | switch(external->type) 3067 | { 3068 | case EXTERNAL_VARIABLE_TYPE_BOOLEAN: 3069 | PyDict_SetItemString( 3070 | rules->externals, 3071 | external->identifier, 3072 | PyBool_FromLong((long) external->value.i)); 3073 | break; 3074 | case EXTERNAL_VARIABLE_TYPE_INTEGER: 3075 | PyDict_SetItemString( 3076 | rules->externals, 3077 | external->identifier, 3078 | PyLong_FromLong((long) external->value.i)); 3079 | break; 3080 | case EXTERNAL_VARIABLE_TYPE_FLOAT: 3081 | PyDict_SetItemString( 3082 | rules->externals, 3083 | external->identifier, 3084 | PyFloat_FromDouble(external->value.f)); 3085 | break; 3086 | case EXTERNAL_VARIABLE_TYPE_STRING: 3087 | PyDict_SetItemString( 3088 | rules->externals, 3089 | external->identifier, 3090 | PY_STRING(external->value.s)); 3091 | break; 3092 | } 3093 | 3094 | external++; 3095 | } 3096 | 3097 | return (PyObject*) rules; 3098 | } 3099 | 3100 | 3101 | void finalize(void) 3102 | { 3103 | yr_finalize(); 3104 | } 3105 | 3106 | 3107 | static PyMethodDef yara_methods[] = { 3108 | { 3109 | "compile", 3110 | (PyCFunction) yara_compile, 3111 | METH_VARARGS | METH_KEYWORDS, 3112 | "Compiles a YARA rules file and returns an instance of class Rules" 3113 | }, 3114 | { 3115 | "load", 3116 | (PyCFunction) yara_load, 3117 | METH_VARARGS | METH_KEYWORDS, 3118 | "Loads a previously saved YARA rules file and returns an instance of class Rules" 3119 | }, 3120 | { 3121 | "set_config", 3122 | (PyCFunction) yara_set_config, 3123 | METH_VARARGS | METH_KEYWORDS, 3124 | "Set a yara configuration variable (stack_size, max_strings_per_rule, or max_match_data)" 3125 | }, 3126 | { NULL, NULL } 3127 | }; 3128 | 3129 | #if PY_MAJOR_VERSION >= 3 3130 | #define MOD_ERROR_VAL NULL 3131 | #define MOD_SUCCESS_VAL(val) val 3132 | #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void) 3133 | #define MOD_DEF(ob, name, doc, methods) \ 3134 | static struct PyModuleDef moduledef = { \ 3135 | PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \ 3136 | ob = PyModule_Create(&moduledef); 3137 | #else 3138 | #define MOD_ERROR_VAL 3139 | #define MOD_SUCCESS_VAL(val) 3140 | #define MOD_INIT(name) void init##name(void) 3141 | #define MOD_DEF(ob, name, doc, methods) \ 3142 | ob = Py_InitModule3(name, methods, doc); 3143 | #endif 3144 | 3145 | static PyObject* YaraWarningError_getwarnings(PyObject *self, void* closure) 3146 | { 3147 | PyObject *args = PyObject_GetAttrString(self, "args"); 3148 | 3149 | if (!args) { 3150 | return NULL; 3151 | } 3152 | 3153 | PyObject* ret = PyTuple_GetItem(args, 0); 3154 | Py_XINCREF(ret); 3155 | Py_XDECREF(args); 3156 | 3157 | return ret; 3158 | } 3159 | 3160 | static PyGetSetDef YaraWarningError_getsetters[] = { 3161 | {"warnings", YaraWarningError_getwarnings, NULL, NULL, NULL}, 3162 | {NULL} 3163 | }; 3164 | 3165 | 3166 | MOD_INIT(yara) 3167 | { 3168 | PyObject* m; 3169 | 3170 | MOD_DEF(m, "yara", YARA_DOC, yara_methods) 3171 | 3172 | if (m == NULL) 3173 | return MOD_ERROR_VAL; 3174 | 3175 | /* initialize module variables/constants */ 3176 | 3177 | PyModule_AddIntConstant(m, "CALLBACK_CONTINUE", 0); 3178 | PyModule_AddIntConstant(m, "CALLBACK_ABORT", 1); 3179 | PyModule_AddIntConstant(m, "CALLBACK_MATCHES", CALLBACK_MATCHES); 3180 | PyModule_AddIntConstant(m, "CALLBACK_NON_MATCHES", CALLBACK_NON_MATCHES); 3181 | PyModule_AddIntConstant(m, "CALLBACK_ALL", CALLBACK_ALL); 3182 | PyModule_AddIntConstant(m, "CALLBACK_TOO_MANY_MATCHES", CALLBACK_MSG_TOO_MANY_MATCHES); 3183 | PyModule_AddStringConstant(m, "__version__", YR_VERSION); 3184 | PyModule_AddStringConstant(m, "YARA_VERSION", YR_VERSION); 3185 | PyModule_AddIntConstant(m, "YARA_VERSION_HEX", YR_VERSION_HEX); 3186 | 3187 | #if PYTHON_API_VERSION >= 1007 3188 | YaraError = PyErr_NewException("yara.Error", PyExc_Exception, NULL); 3189 | YaraSyntaxError = PyErr_NewException("yara.SyntaxError", YaraError, NULL); 3190 | YaraTimeoutError = PyErr_NewException("yara.TimeoutError", YaraError, NULL); 3191 | YaraWarningError = PyErr_NewException("yara.WarningError", YaraError, NULL); 3192 | 3193 | PyTypeObject *YaraWarningError_type = (PyTypeObject *) YaraWarningError; 3194 | PyObject* descr = PyDescr_NewGetSet(YaraWarningError_type, YaraWarningError_getsetters); 3195 | 3196 | if (PyDict_SetItem(YaraWarningError_type->tp_dict, PyDescr_NAME(descr), descr) < 0) 3197 | { 3198 | Py_DECREF(m); 3199 | Py_DECREF(descr); 3200 | } 3201 | 3202 | Py_DECREF(descr); 3203 | #else 3204 | YaraError = Py_BuildValue("s", "yara.Error"); 3205 | YaraSyntaxError = Py_BuildValue("s", "yara.SyntaxError"); 3206 | YaraTimeoutError = Py_BuildValue("s", "yara.TimeoutError"); 3207 | YaraWarningError = Py_BuildValue("s", "yara.WarningError"); 3208 | #endif 3209 | 3210 | if (PyType_Ready(&Rule_Type) < 0) 3211 | return MOD_ERROR_VAL; 3212 | 3213 | if (PyType_Ready(&Rules_Type) < 0) 3214 | return MOD_ERROR_VAL; 3215 | 3216 | if (PyType_Ready(&Match_Type) < 0) 3217 | return MOD_ERROR_VAL; 3218 | 3219 | if (PyType_Ready(&StringMatch_Type) < 0) 3220 | return MOD_ERROR_VAL; 3221 | 3222 | if (PyType_Ready(&StringMatchInstance_Type) < 0) 3223 | return MOD_ERROR_VAL; 3224 | 3225 | PyStructSequence_InitType(&RuleString_Type, &RuleString_Desc); 3226 | 3227 | PyModule_AddObject(m, "Rule", (PyObject*) &Rule_Type); 3228 | PyModule_AddObject(m, "Rules", (PyObject*) &Rules_Type); 3229 | PyModule_AddObject(m, "Match", (PyObject*) &Match_Type); 3230 | PyModule_AddObject(m, "StringMatch", (PyObject*) &StringMatch_Type); 3231 | PyModule_AddObject(m, "StringMatchInstance", (PyObject*) &StringMatchInstance_Type); 3232 | 3233 | PyModule_AddObject(m, "Error", YaraError); 3234 | PyModule_AddObject(m, "SyntaxError", YaraSyntaxError); 3235 | PyModule_AddObject(m, "TimeoutError", YaraTimeoutError); 3236 | PyModule_AddObject(m, "WarningError", YaraWarningError); 3237 | 3238 | if (yr_initialize() != ERROR_SUCCESS) 3239 | { 3240 | PyErr_SetString(YaraError, "initialization error"); 3241 | return MOD_ERROR_VAL; 3242 | } 3243 | 3244 | PyObject* module_names_list = PyList_New(0); 3245 | 3246 | if (module_names_list == NULL) 3247 | { 3248 | PyErr_SetString(YaraError, "module list error"); 3249 | return MOD_ERROR_VAL; 3250 | } 3251 | 3252 | for (YR_MODULE* module = yr_modules_get_table(); module->name != NULL; module++) 3253 | { 3254 | PyObject* module_name = PY_STRING(module->name); 3255 | if (module_name == NULL) 3256 | { 3257 | PyErr_SetString(YaraError, "module name error"); 3258 | return MOD_ERROR_VAL; 3259 | } 3260 | if (PyList_Append(module_names_list, module_name) < 0) 3261 | { 3262 | PyErr_SetString(YaraError, "module name error"); 3263 | return MOD_ERROR_VAL; 3264 | } 3265 | } 3266 | PyModule_AddObject(m, "modules", module_names_list); 3267 | 3268 | Py_AtExit(finalize); 3269 | 3270 | return MOD_SUCCESS_VAL(m); 3271 | } 3272 | --------------------------------------------------------------------------------